Skip to content

Commit

Permalink
Initialize authentication module implementation with JWT strategy and…
Browse files Browse the repository at this point in the history
… user registration
  • Loading branch information
vasapolrittideah committed Jan 9, 2025
1 parent 95d2a58 commit ba4ef59
Show file tree
Hide file tree
Showing 19 changed files with 207 additions and 30 deletions.
Binary file modified bun.lockb
Binary file not shown.
9 changes: 9 additions & 0 deletions packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,21 @@
"@nestjs/common": "^10.4.15",
"@nestjs/config": "^3.3.0",
"@nestjs/core": "^10.4.15",
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "^10.4.15",
"@nestjs/platform-fastify": "^10.4.15",
"@nestjs/swagger": "^8.1.0",
"@prisma/client": "^6.2.1",
"@supabase/supabase-js": "^2.47.12",
"argon2": "^0.41.1",
"class-validator": "^0.14.1",
"dotenv": "^16.4.7",
"dotenv-expand": "^12.0.1",
"fastify": "^5.2.1",
"normalize-email": "^1.1.1",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1"
},
Expand All @@ -45,6 +52,8 @@
"@types/express": "^5.0.0",
"@types/jest": "^29.5.14",
"@types/node": "^22.10.5",
"@types/passport-jwt": "^4.0.1",
"@types/passport-local": "^1.0.38",
"@types/supertest": "^6.0.2",
"@typescript-eslint/eslint-plugin": "latest",
"@typescript-eslint/parser": "latest",
Expand Down

This file was deleted.

3 changes: 0 additions & 3 deletions packages/server/prisma/migrations/migration_lock.toml

This file was deleted.

9 changes: 0 additions & 9 deletions packages/server/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,3 @@ datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

model User {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
fullName String
email String @unique
password String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
9 changes: 8 additions & 1 deletion packages/server/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,17 @@ import { UsersController } from './modules/users/users.controller';
import { UsersService } from './modules/users/users.service';
import { UsersModule } from './modules/users/users.module';
import { PrismaService } from './providers/prisma/prisma.service';
import { AuthModule } from './modules/auth/auth.module';
import { SupabaseModule } from './providers/supabase/supabase.module';
import configuration from './config/configuration';

@Module({
imports: [UsersModule, SupabaseModule, ConfigModule.forRoot({ load: [] })],
imports: [
UsersModule,
ConfigModule.forRoot({ load: [configuration] }),
AuthModule,
SupabaseModule,
],
controllers: [UsersController],
providers: [UsersService, PrismaService],
})
Expand Down
4 changes: 4 additions & 0 deletions packages/server/src/config/configuration.interface.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
export interface Configuration {
frontendUrl: string;

security: {
jwtSecret: string;
};

supabase: {
apiUrl: string;
anonKey: string;
Expand Down
4 changes: 4 additions & 0 deletions packages/server/src/config/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ dotenvExpand.expand(dotenv.config());
const configuration: Configuration = {
frontendUrl: process.env.FRONTEND_URL ?? 'http://localhost:7001',

security: {
jwtSecret: process.env.SUPABASE_JWT_SECRET ?? '',
},

supabase: {
apiUrl: process.env.SUPABASE_API_URL ?? '',
anonKey: process.env.SUPABASE_ANON_KEY ?? '',
Expand Down
5 changes: 5 additions & 0 deletions packages/server/src/helpers/normalize-emal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import normalize from 'normalize-email';

export const normalizeEmail = (input: string) => {
return normalize(input.toLowerCase().trim());
};
18 changes: 18 additions & 0 deletions packages/server/src/modules/auth/auth.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AuthController } from './auth.controller';

describe('AuthController', () => {
let controller: AuthController;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [AuthController],
}).compile();

controller = module.get<AuthController>(AuthController);
});

it('should be defined', () => {
expect(controller).toBeDefined();
});
});
13 changes: 13 additions & 0 deletions packages/server/src/modules/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Body, Controller, Post } from '@nestjs/common';
import { RegisterDto } from './auth.dto';
import { AuthService } from './auth.service';

@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}

@Post('register')
async register(@Body() data: RegisterDto) {
return this.authService.register(data);
}
}
22 changes: 22 additions & 0 deletions packages/server/src/modules/auth/auth.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {
IsEmail,
IsNotEmpty,
IsOptional,
IsString,
MinLength,
} from 'class-validator';

export class RegisterDto {
@IsString()
@IsNotEmpty()
@MinLength(3)
fullName: string;

@IsEmail()
@IsNotEmpty()
email: string;

@IsString()
@IsOptional()
password?: string;
}
4 changes: 4 additions & 0 deletions packages/server/src/modules/auth/auth.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface TokenResponse {
accessToken: string;
refreshToken: string;
}
22 changes: 22 additions & 0 deletions packages/server/src/modules/auth/auth.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { SupabaseService } from '../../providers/supabase/supabase.service';
import { ConfigModule } from '@nestjs/config';
import { JwtStrategy } from './jwt.strategy';
import { PassportModule } from '@nestjs/passport';
import { SupabaseModule } from '@/providers/supabase/supabase.module';
import { PrismaService } from '@/providers/prisma/prisma.service';
import { PrismaModule } from '@/providers/prisma/prisma.modules';

@Module({
imports: [
PassportModule.register({ defaultStrategy: 'jwt' }),
SupabaseModule,
ConfigModule,
PrismaModule,
],
providers: [AuthService, JwtStrategy],
controllers: [AuthController],
})
export class AuthModule {}
18 changes: 18 additions & 0 deletions packages/server/src/modules/auth/auth.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AuthService } from './auth.service';

describe('AuthService', () => {
let service: AuthService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AuthService],
}).compile();

service = module.get<AuthService>(AuthService);
});

it('should be defined', () => {
expect(service).toBeDefined();
});
});
31 changes: 31 additions & 0 deletions packages/server/src/modules/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { SupabaseService } from '@/providers/supabase/supabase.service';
import { Injectable } from '@nestjs/common';
import { RegisterDto } from './auth.dto';
import { PrismaService } from '@/providers/prisma/prisma.service';
import { normalizeEmail } from '@/helpers/normalize-emal';
import { ConfigService } from '@nestjs/config';
import { AuthUser } from '@supabase/supabase-js';

@Injectable()
export class AuthService {
constructor(
private readonly supabase: SupabaseService,
private readonly prisma: PrismaService,
private readonly configService: ConfigService,
) {}

async register(dto: RegisterDto): Promise<AuthUser> {
const normalizedEmail = normalizeEmail(dto.email);
const { data, error } = await this.supabase.client.auth.signUp({
email: normalizedEmail,
password: dto.password,
options: {
data: {
fullName: dto.fullName,
},
},
});

return this.prisma.expose(data.user);
}
}
17 changes: 17 additions & 0 deletions packages/server/src/modules/auth/jwt-auth.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { Observable } from 'rxjs';

@Injectable()
export class JwtAuthGuard extends AuthGuard('supabase') {
constructor(private readonly reflector: Reflector) {
super();
}

public canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
return super.canActivate(context);
}
}
29 changes: 29 additions & 0 deletions packages/server/src/modules/auth/jwt.strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PassportStrategy } from '@nestjs/passport';
import { AuthUser } from '@supabase/supabase-js';
import { Request } from 'express';
import { ExtractJwt, Strategy } from 'passport-jwt';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(readonly configService: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: configService.get<String>('security.jwtSecret'),
ignoreExpiration: false,
});
}

async validate(user: AuthUser): Promise<AuthUser> {
/*
Passport automatically creates a `user` object and assigns it to the
Request object as `request.user`.
*/
return user;
}

async authenticate(request: Request): Promise<void> {
super.authenticate(request);
}
}
6 changes: 3 additions & 3 deletions packages/server/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "ES2021",
"target": "ES2022",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
Expand All @@ -17,9 +17,9 @@
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false,
"esModuleInterop": true,
"paths": {
"@/*": ["src/*"]
}
},
"include": ["**/*.spec.ts"]
}
}

0 comments on commit ba4ef59

Please sign in to comment.