issue #39
All checks were successful
Test CI / build (push) Successful in 1m28s

- Access/Refresh 토큰 검증 및 허가 구현 중
This commit is contained in:
geonhee-min
2025-12-01 16:35:22 +09:00
parent 43868489e0
commit 56cee12c81
15 changed files with 147 additions and 53 deletions

View File

@@ -10,5 +10,5 @@ PG_DATABASE_URL=postgres://baekyangdan:qwas745478!@bkdhome.p-e.kr:15454/schedule
# Redis 설정
RD_HOST=bkdhome.p-e.kr
RD_PORT=6779
RD_PORT=16779
RD_URL=redis://bkdhome.p-e.kr:16779

View File

@@ -0,0 +1,5 @@
import { SetMetadata } from "@nestjs/common";
export const IS_PUBLIC_KEY = 'isPublic345827';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

42
src/const/HttpResponse.ts Normal file
View File

@@ -0,0 +1,42 @@
export const HttpResponse: Record<string, {code: number, title: string, message: string}> = {
"ACCESS_TOKEN_EXPIRED": {
code: 401,
title: "ACCESS_TOKEN_EXPIRED",
message: "ACCESS TOKEN EXPIRED"
},
"INVALID_TOKEN": {
code: 401,
title: "INVALID_TOKEN",
message: "INVALID TOKEN"
},
"REFRESH_TOKEN_EXPIRED": {
code: 401,
title: "REFRESH_TOKEN_EXPIRED",
message: "REFRESH TOKEN EXPIRED"
},
"UNAUTHORIZED": {
code: 401,
title: "UNAUTHORIZED",
message: "UNAUTHORIZED"
},
"OK": {
code: 200,
title: "OK",
message: "OK"
},
"CREATED": {
code: 201,
title: "CREATED",
message: "CREATED"
},
"BAD_REQUEST": {
code: 400,
title: "BAD_REQUEST",
message: "BAD REQUEST"
},
"INTERNAL_SERVER_ERROR": {
code: 500,
title: "INTERNAL_SERVER_ERROR",
message: "INTERNAL SERVER ERROR"
}
} as const;

View File

@@ -1,10 +1,10 @@
import { forwardRef, Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { JwtStrategy } from './jwt.strategy';
import { JwtModule } from '@nestjs/jwt';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { AccountModule } from 'src/modules/account/account.module';
import { JwtAccessStrategy } from './strategy/access-token.strategy';
import { JwtRefreshStrategy } from './strategy/refresh-token.strategy';
@Module({
imports: [
ConfigModule,
@@ -18,7 +18,7 @@ import { AccountModule } from 'src/modules/account/account.module';
}),
forwardRef(() => AccountModule)
],
providers: [AuthService, JwtStrategy],
providers: [AuthService, JwtAccessStrategy, JwtRefreshStrategy],
exports: [AuthService]
})
export class AuthModule{}

View File

@@ -7,7 +7,7 @@ export class AuthService {
generateTokens(payload: any) {
const accessToken = this.jwtService.sign(payload, { expiresIn: '1h' });
const refreshToken = this.jwtService.sign(payload, { expiresIn: '7d' });
const refreshToken = this.jwtService.sign({id: payload.id}, { expiresIn: '7d' });
return { accessToken, refreshToken };
}

View File

@@ -0,0 +1,5 @@
import { Injectable } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";
@Injectable()
export class JwtAccessAuthGuard extends AuthGuard('access-token') {}

View File

@@ -0,0 +1,5 @@
import { Injectable } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";
@Injectable()
export class JwtRefreshAuthGuard extends AuthGuard('refresh-token') {}

View File

@@ -1,5 +0,0 @@
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtGuard extends AuthGuard('jwt') {}

View File

@@ -1,27 +0,0 @@
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { AccountRepo } from 'src/modules/account/account.repo';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
private readonly accountRepo: AccountRepo
, private readonly configService: ConfigService
, private readonly jwtService: JwtService
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.get<string>('JWT_SECRET')!
});
}
async validate(payload: any) {
const account = await this.accountRepo.findById(payload.id);
if (!account || account.length < 1) throw new UnauthorizedException();
return account[0];
}
}

View File

@@ -0,0 +1,18 @@
import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { PassportStrategy } from "@nestjs/passport";
import { ExtractJwt, Strategy } from "passport-jwt";
@Injectable()
export class JwtAccessStrategy extends PassportStrategy(Strategy, "access-token") {
constructor(configService: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: configService.get<string>('JWT_SECRET')!
});
}
async validate(payload: any) {
return { id: payload.id };
}
}

View File

@@ -0,0 +1,26 @@
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
@Injectable()
export class JwtRefreshStrategy extends PassportStrategy(Strategy, 'refresh-token') {
constructor(configService: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: configService.get<string>('JWT_SECRET')!,
passReqToCallback: true
});
}
async validate(req: any, payload: any) {
const token = ExtractJwt.fromAuthHeaderAsBearerToken()(req);
if (!token) throw new UnauthorizedException();
return {
id: payload.id,
token
};
}
}

View File

@@ -1,49 +1,60 @@
import { Body, Controller, Get, Post, Query } from "@nestjs/common";
import { Body, Controller, Get, Headers, Post, Query, Req, UseGuards } from "@nestjs/common";
import { AccountService } from "./account.service";
import {
CheckDuplicationRequest, CheckDuplicationResponse,
SendVerificationCodeRequest, SendVerificationCodeResponse,
VerifyCodeRequest, VerifyCodeResponse,
LoginRequest, LoginResponse,
SignupRequest, SignupResponse
} from "./dto";
import * as DTO from "./dto";
import { JwtAccessAuthGuard } from "src/middleware/auth/guard/access-token.guard";
import { Public } from "src/common/decorators/public.decorator";
@UseGuards(JwtAccessAuthGuard)
@Controller('account')
export class AccountController {
constructor(private readonly accountService: AccountService) {}
@Public()
@Get('/')
async test() {
return "Test"
}
@Public()
@Get('check-duplication')
async checkDuplication(@Query() query: CheckDuplicationRequest): Promise<CheckDuplicationResponse> {
async checkDuplication(@Query() query: DTO.CheckDuplicationRequest): Promise<DTO.CheckDuplicationResponse> {
return await this.accountService.checkDuplication(query);
}
@Public()
@Post('send-verification-code')
async sendVerificationCode(@Body() body: SendVerificationCodeRequest): Promise<SendVerificationCodeResponse> {
async sendVerificationCode(@Body() body: DTO.SendVerificationCodeRequest): Promise<DTO.SendVerificationCodeResponse> {
const result = await this.accountService.sendVerificationCode(body);
return result;
}
@Public()
@Post('verify-code')
async verifyCode(@Body() body: VerifyCodeRequest): Promise<VerifyCodeResponse> {
async verifyCode(@Body() body: DTO.VerifyCodeRequest): Promise<DTO.VerifyCodeResponse> {
console.log(body.email);
const result = await this.accountService.verifyCode(body);
return result;
}
@Public()
@Post('signup')
async signup(@Body() body: SignupRequest): Promise<SignupResponse> {
async signup(@Body() body: DTO.SignupRequest): Promise<DTO.SignupResponse> {
const result = await this.accountService.signup(body);
return result;
}
@Public()
@Post('login')
async login(@Body() body: LoginRequest): Promise<LoginResponse> {
async login(@Body() body: DTO.LoginRequest): Promise<DTO.LoginResponse> {
const result = await this.accountService.login(body);
return result;
}
@Public()
@Post('refresh-access-token')
async refreshAccessToken(@Req() req): Promise<DTO.RefreshAccessTokenResponse> {
const id = req.user.id;
const newAccessToken = this.accountService.refreshAccessToken(id);
return newAccessToken;
}
}

View File

@@ -94,10 +94,10 @@ export class AccountService {
}
{
const { id, accountId, name, nickname, email, status, isDeleted, birthday } = queryResult[0];
const { id, accountId, status, isDeleted, birthday } = queryResult[0];
const payload = {
id, accountId, name, nickname, email, status, isDeleted, birthday
id, accountId, status, isDeleted, birthday
};
const { accessToken, refreshToken } = this.authService.generateTokens(payload);
@@ -109,4 +109,12 @@ export class AccountService {
};
}
}
async refreshAccessToken(id: string): Promise<DTO.RefreshAccessTokenResponse> {
const { accessToken, refreshToken } = this.authService.refreshTokens(id);
return {
accessToken: accessToken,
refreshToken: refreshToken
};
}
}

View File

@@ -12,3 +12,5 @@ export { SignupResponseDto as SignupResponse } from './signup/signup-response.dt
export { LoginRequestDto as LoginRequest } from './login/login-request.dto';
export { LoginResponseDto as LoginResponse } from './login/login-response.dto'
export { RefreshAccessTokenResponseDto as RefreshAccessTokenResponse } from './refreshAccessToken/refresh-access-token-response.dto';

View File

@@ -0,0 +1,4 @@
export class RefreshAccessTokenResponseDto {
accessToken: string;
refreshToken: string;
}