Skip to content

Commit

Permalink
feat: JWT access token 및 refresh token 인증 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
minjungw00 committed Nov 19, 2024
1 parent 38d5ac4 commit 1c3b6ac
Show file tree
Hide file tree
Showing 24 changed files with 714 additions and 210 deletions.
2 changes: 1 addition & 1 deletion client/tsconfig.tsbuildinfo

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions pnpm-lock.yaml

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

2 changes: 0 additions & 2 deletions server/jest.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import type { Config } from "jest";
import { pathsToModuleNameMapper } from "ts-jest";
import { compilerOptions } from "./tsconfig.json";

const config: Config = {
moduleFileExtensions: ["js", "json", "ts"],
Expand Down
1 change: 1 addition & 0 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"@nestjs/websockets": "^10.4.7",
"@noctaCrdt": "workspace:*",
"bcrypt": "^5.1.1",
"express": "^4.21.1",
"mongodb-memory-server": "^10.1.2",
"mongoose": "^8.8.0",
"nanoid": "^3.0.0",
Expand Down
7 changes: 7 additions & 0 deletions server/src/@types/express/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import "express";

declare module "express" {
export interface Request {
user?: User;
}
}
52 changes: 0 additions & 52 deletions server/src/auth/auth.controller.spec.ts

This file was deleted.

99 changes: 72 additions & 27 deletions server/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
import { Controller, Post, Body, UseGuards, Req, Res, Request } from "@nestjs/common";
import {
Controller,
Get,
Post,
Body,
UseGuards,
Request,
Response,
UnauthorizedException,
ConflictException,
} from "@nestjs/common";
import { Response as ExpressResponse, Request as ExpressRequest } from "express";
import { AuthService } from "./auth.service";
import { JwtAuthGuard } from "./jwt-auth.guard";
import { ApiTags, ApiOperation, ApiBody, ApiResponse } from "@nestjs/swagger";
import { JwtAuthGuard } from "./guards/jwt-auth.guard";
import {
ApiTags,
ApiOperation,
ApiBody,
ApiResponse,
ApiBearerAuth,
ApiCookieAuth,
} from "@nestjs/swagger";
import { UserDto } from "./dto/user.dto";

@ApiTags("auth")
Expand All @@ -24,19 +42,18 @@ export class AuthController {
@ApiResponse({
status: 201,
description: "The user has been successfully created.",
type: UserDto,
})
@ApiResponse({ status: 400, description: "Bad Request" })
async register(
@Body() body: { email: string; password: string; name: string },
): Promise<UserDto> {
@ApiResponse({ status: 409, description: "Conflict: Email already exists" })
async register(@Body() body: { email: string; password: string; name: string }): Promise<void> {
const { email, password, name } = body;
const user = await this.authService.register(email, password, name);
return {
id: user.id,
email: user.email,
name: user.name,
};

const existingUser = await this.authService.findByEmail(email);
if (existingUser) {
throw new ConflictException("Email already exists");
}

await this.authService.register(email, password, name);
}

@Post("login")
Expand All @@ -52,36 +69,64 @@ export class AuthController {
})
@ApiResponse({ status: 200, description: "The user has been successfully logged in." })
@ApiResponse({ status: 401, description: "Unauthorized" })
async login(@Body() body: { email: string; password: string }, @Res({ passthrough: true }) res) {
async login(
@Body() body: { email: string; password: string },
@Response({ passthrough: true }) res: ExpressResponse,
): Promise<UserDto> {
const user = await this.authService.validateUser(body.email, body.password);
if (!user) {
throw new Error("Invalid credentials");
throw new UnauthorizedException("Invalid credentials");
}

return this.authService.login(user, res);
}

@Post("logout")
// TODO access token 검증 과정....
@UseGuards(JwtAuthGuard)
public async logout(@Req() req) {
@Post("logout")
@ApiOperation({ summary: "Logout a user" })
@ApiBearerAuth()
@ApiCookieAuth("refreshToken")
@ApiResponse({ status: 200, description: "The user has been successfully logged out." })
@ApiResponse({ status: 401, description: "Unauthorized" })
async logout(@Request() req: ExpressRequest): Promise<void> {
const { user } = req;
if (!user) {
throw new UnauthorizedException("User not found");
}

// DB에서 refresh token 삭제
await this.authService.removeRefreshToken(user);
await this.authService.removeRefreshToken(user.id);

// TODO access token 블랙리스트 추가
const authHeader = req.headers.authorization;
if (!authHeader) {
throw new UnauthorizedException("Authorization header not found");
}
const [, token] = authHeader.split(" ");
if (!token) {
throw new UnauthorizedException("Token not found");
}
this.authService.blacklistToken(token, new Date());

// 쿠키 삭제
this.authService.clearCookie(req.res);
return {};
}

// @Post("refresh")
// TODO access token 검증 과정
// TODO refresh token을 이용해서 access token 재발급

@UseGuards(JwtAuthGuard)
@Post("profile")
@Get("profile")
@ApiOperation({ summary: "Get user profile" })
@ApiBearerAuth()
@ApiResponse({ status: 200, description: "The user profile has been successfully retrieved." })
@ApiResponse({ status: 401, description: "Unauthorized" })
getProfile(@Request() req) {
return req.user;
async getProfile(@Request() req: ExpressRequest): Promise<UserDto> {
const user = await this.authService.getProfile(req.user.id);
if (!user) {
throw new UnauthorizedException("User not found");
}
return {
id: user.id,
email: user.email,
name: user.name,
};
}
}
6 changes: 6 additions & 0 deletions server/src/auth/auth.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface IJwtPayload {
sub?: string; // 사용자 ID
email?: string; // 사용자 이메일
iat: number; // 발급 시간
exp: number; // 만료 시간
}
64 changes: 0 additions & 64 deletions server/src/auth/auth.module.spec.ts

This file was deleted.

19 changes: 16 additions & 3 deletions server/src/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,19 @@ import { AuthService } from "./auth.service";
import { AuthController } from "./auth.controller";
import { JwtModule } from "@nestjs/jwt";
import { PassportModule } from "@nestjs/passport";
import { JwtStrategy } from "./jwt.strategy";
import { JwtStrategy } from "./strategies/jwt.strategy";
import { JwtRefreshTokenStrategy } from "./strategies/jwt-refresh-token.strategy";
import { ConfigModule, ConfigService } from "@nestjs/config";
import { JwtAuthGuard } from "./guards/jwt-auth.guard";
import { JwtRefreshTokenAuthGuard } from "./guards/jwt-refresh-token-auth.guard";
import { BlacklistedToken, BlacklistedTokenSchema } from "./schemas/blacklisted-token.schema";

@Module({
imports: [
MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]),
MongooseModule.forFeature([
{ name: User.name, schema: UserSchema },
{ name: BlacklistedToken.name, schema: BlacklistedTokenSchema },
]),
PassportModule,
JwtModule.registerAsync({
global: true,
Expand All @@ -23,7 +30,13 @@ import { ConfigModule, ConfigService } from "@nestjs/config";
}),
],
exports: [AuthService, JwtModule],
providers: [AuthService, JwtStrategy],
providers: [
AuthService,
JwtStrategy,
JwtRefreshTokenStrategy,
JwtAuthGuard,
JwtRefreshTokenAuthGuard,
],
controllers: [AuthController],
})
export class AuthModule {}
Loading

0 comments on commit 1c3b6ac

Please sign in to comment.