Skip to content

Commit

Permalink
refactor: authentication (#90)
Browse files Browse the repository at this point in the history
* refactor: authentication

* fix: remove console logs and prints

* fix: lint
  • Loading branch information
invertedEcho authored Jul 27, 2024
1 parent 7a34856 commit 83cb099
Show file tree
Hide file tree
Showing 15 changed files with 125 additions and 243 deletions.
7 changes: 0 additions & 7 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,14 @@
"@nestjs/common": "^10.0.0",
"@nestjs/core": "^10.0.0",
"@nestjs/jwt": "^10.2.0",
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/schedule": "^4.0.2",
"@nestjs/serve-static": "^4.0.2",
"bcrypt": "^5.1.1",
"drizzle-orm": "^0.30.9",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0",
"pg": "^8.11.5",
"postgres": "^3.4.4",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1",
"zod": "^3.23.4"
},
"devDependencies": {
Expand All @@ -54,8 +49,6 @@
"@types/express": "^4.17.17",
"@types/jest": "^29.5.2",
"@types/node": "^20.3.1",
"@types/passport-jwt": "^4.0.1",
"@types/passport-local": "^1.0.38",
"@types/supertest": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
Expand Down
97 changes: 0 additions & 97 deletions backend/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 0 additions & 3 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { Module } from '@nestjs/common';
import { AuthModule } from './auth/auth.module';
import { AuthController } from './auth/auth.controller';
import { APP_GUARD } from '@nestjs/core';
import { JwtAuthGuard } from './auth/jwt-auth.guard';
import { ScheduleModule } from '@nestjs/schedule';
import { ServeStaticModule } from '@nestjs/serve-static';
import { join } from 'path';
Expand Down Expand Up @@ -31,6 +29,5 @@ const rootPathStatic = join(__dirname, '../../src/client/public/');
TaskGroupController,
UserGroupController,
],
providers: [{ provide: APP_GUARD, useClass: JwtAuthGuard }],
})
export class AppModule {}
30 changes: 18 additions & 12 deletions backend/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,27 @@ import {
Body,
Controller,
Get,
HttpCode,
HttpException,
HttpStatus,
Post,
Query,
Request,
UseGuards,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import * as bcrypt from 'bcrypt';
import { db } from 'src/db';
import { userUserGroupTable, userTable } from 'src/db/schema';
import { AuthService, User } from './auth.service';
import { Public } from './public.decorators';
import { AuthService } from './auth.service';
import {
dbAddUserToGroup,
dbGetGroupOfUser,
dbGetInviteCode,
} from 'src/db/functions/user-group';
import { eq } from 'drizzle-orm';
import { dbGetUserById } from 'src/db/functions/user';
import { AuthGuard } from './auth.guard';
import { Public } from './constants';

class RegisterDto {
username: string;
Expand All @@ -30,17 +31,22 @@ class RegisterDto {
inviteCode: string | null;
}

export class LoginDto {
email: string;
password: string;
}

const SALT_ROUNDS = 10;

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

@Public()
@UseGuards(AuthGuard('local'))
@HttpCode(HttpStatus.OK)
@Post('login')
async login(@Request() req: { user: User }) {
return await this.authService.login(req.user);
async login(@Body() body: LoginDto) {
return await this.authService.login(body);
}

@Public()
Expand Down Expand Up @@ -77,15 +83,15 @@ export class AuthController {
return { username: newUser.username, email: newUser.email };
}

// TODO: This endpoint doesnt seem right...
@UseGuards(AuthGuard)
@Get('profile')
async getProfile(
@Request() req: { user: { userId: number; username: string } },
@Request() req: { user: { sub: number; username: string } },
) {
const user = await dbGetUserById(req.user.userId);
const userGroup = await dbGetGroupOfUser(req.user.userId);
const user = await dbGetUserById(req.user.sub);
const userGroup = await dbGetGroupOfUser(req.user.sub);
return {
userId: req.user.userId,
userId: req.user.sub,
userGroup: {
id: userGroup?.user_group.name,
name: userGroup?.user_group.name,
Expand Down
55 changes: 55 additions & 0 deletions backend/src/auth/auth.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {
CanActivate,
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { IS_PUBLIC_KEY, JWT_SECRET } from './constants';
import { Request } from 'express';
import { Reflector } from '@nestjs/core';

@Injectable()
export class AuthGuard implements CanActivate {
constructor(
private jwtService: JwtService,
private reflector: Reflector,
) {}

// If this function returns true, the jwt token (if existing) in the incoming request is valid,
// and the request gets added a 'user' field, which contains the decoded data from the jwt secret.
// It works by checking if the request contains an authorization header with a Bearer token,
// and validates it.
async canActivate(context: ExecutionContext): Promise<boolean> {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);

// For endpoints that use the @Public annotation, we directly return true and skip checking the access token
if (isPublic) {
return true;
}
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
if (!token) {
throw new UnauthorizedException();
}
try {
const payload = await this.jwtService.verifyAsync(token, {
secret: JWT_SECRET,
});
// We're assigning the payload to the request object here
// so that we can access it in our route handlers
request['user'] = payload;
} catch {
throw new UnauthorizedException();
}
return true;
}

private extractTokenFromHeader(request: Request): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
}
15 changes: 6 additions & 9 deletions backend/src/auth/auth.module.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { jwtConstants } from './constants';
import { JwtStrategy } from './jwt.strategy';
import { LocalStrategy } from './local.strategy';

const JWT_EXPIRATION = '2592000s';
import { JWT_EXPIRATION, JWT_SECRET } from './constants';
import { APP_GUARD } from '@nestjs/core';
import { AuthGuard } from './auth.guard';

@Module({
imports: [
PassportModule,
JwtModule.register({
secret: jwtConstants.secret,
global: true,
secret: JWT_SECRET,
signOptions: { expiresIn: JWT_EXPIRATION },
}),
],
providers: [AuthService, LocalStrategy, JwtStrategy],
providers: [AuthService, { provide: APP_GUARD, useClass: AuthGuard }],
exports: [AuthService],
controllers: [AuthController],
})
Expand Down
Loading

0 comments on commit 83cb099

Please sign in to comment.