diff --git a/.env.dev b/.env.dev index a30d3fa..4e0d7f3 100644 --- a/.env.dev +++ b/.env.dev @@ -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 diff --git a/src/common/decorators/public.decorator.ts b/src/common/decorators/public.decorator.ts new file mode 100644 index 0000000..aca55e2 --- /dev/null +++ b/src/common/decorators/public.decorator.ts @@ -0,0 +1,5 @@ +import { SetMetadata } from "@nestjs/common"; + +export const IS_PUBLIC_KEY = 'isPublic345827'; + +export const Public = () => SetMetadata(IS_PUBLIC_KEY, true); \ No newline at end of file diff --git a/src/const/HttpResponse.ts b/src/const/HttpResponse.ts new file mode 100644 index 0000000..46006b9 --- /dev/null +++ b/src/const/HttpResponse.ts @@ -0,0 +1,42 @@ +export const HttpResponse: Record = { + "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; \ No newline at end of file diff --git a/src/middleware/auth/auth.module.ts b/src/middleware/auth/auth.module.ts index ebfdb27..7eb92c0 100644 --- a/src/middleware/auth/auth.module.ts +++ b/src/middleware/auth/auth.module.ts @@ -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{} \ No newline at end of file diff --git a/src/middleware/auth/auth.service.ts b/src/middleware/auth/auth.service.ts index 707a659..6e67e0b 100644 --- a/src/middleware/auth/auth.service.ts +++ b/src/middleware/auth/auth.service.ts @@ -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 }; } diff --git a/src/middleware/auth/guard/access-token.guard.ts b/src/middleware/auth/guard/access-token.guard.ts new file mode 100644 index 0000000..9a745ee --- /dev/null +++ b/src/middleware/auth/guard/access-token.guard.ts @@ -0,0 +1,5 @@ +import { Injectable } from "@nestjs/common"; +import { AuthGuard } from "@nestjs/passport"; + +@Injectable() +export class JwtAccessAuthGuard extends AuthGuard('access-token') {} \ No newline at end of file diff --git a/src/middleware/auth/guard/refresh-token.guard.ts b/src/middleware/auth/guard/refresh-token.guard.ts new file mode 100644 index 0000000..9c5fb34 --- /dev/null +++ b/src/middleware/auth/guard/refresh-token.guard.ts @@ -0,0 +1,5 @@ +import { Injectable } from "@nestjs/common"; +import { AuthGuard } from "@nestjs/passport"; + +@Injectable() +export class JwtRefreshAuthGuard extends AuthGuard('refresh-token') {} \ No newline at end of file diff --git a/src/middleware/auth/jwt.guard.ts b/src/middleware/auth/jwt.guard.ts deleted file mode 100644 index 8f3bcfd..0000000 --- a/src/middleware/auth/jwt.guard.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { AuthGuard } from '@nestjs/passport'; - -@Injectable() -export class JwtGuard extends AuthGuard('jwt') {} \ No newline at end of file diff --git a/src/middleware/auth/jwt.strategy.ts b/src/middleware/auth/jwt.strategy.ts deleted file mode 100644 index d195601..0000000 --- a/src/middleware/auth/jwt.strategy.ts +++ /dev/null @@ -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('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]; - } -} \ No newline at end of file diff --git a/src/middleware/auth/strategy/access-token.strategy.ts b/src/middleware/auth/strategy/access-token.strategy.ts new file mode 100644 index 0000000..3e284ec --- /dev/null +++ b/src/middleware/auth/strategy/access-token.strategy.ts @@ -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('JWT_SECRET')! + }); + } + + async validate(payload: any) { + return { id: payload.id }; + } +} \ No newline at end of file diff --git a/src/middleware/auth/strategy/refresh-token.strategy.ts b/src/middleware/auth/strategy/refresh-token.strategy.ts new file mode 100644 index 0000000..26b5685 --- /dev/null +++ b/src/middleware/auth/strategy/refresh-token.strategy.ts @@ -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('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 + }; + } +} \ No newline at end of file diff --git a/src/modules/account/account.controller.ts b/src/modules/account/account.controller.ts index 3aa8d68..f535fa0 100644 --- a/src/modules/account/account.controller.ts +++ b/src/modules/account/account.controller.ts @@ -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 { + async checkDuplication(@Query() query: DTO.CheckDuplicationRequest): Promise { return await this.accountService.checkDuplication(query); } + @Public() @Post('send-verification-code') - async sendVerificationCode(@Body() body: SendVerificationCodeRequest): Promise { + async sendVerificationCode(@Body() body: DTO.SendVerificationCodeRequest): Promise { const result = await this.accountService.sendVerificationCode(body); return result; } + @Public() @Post('verify-code') - async verifyCode(@Body() body: VerifyCodeRequest): Promise { + async verifyCode(@Body() body: DTO.VerifyCodeRequest): Promise { console.log(body.email); const result = await this.accountService.verifyCode(body); return result; } + @Public() @Post('signup') - async signup(@Body() body: SignupRequest): Promise { + async signup(@Body() body: DTO.SignupRequest): Promise { const result = await this.accountService.signup(body); return result; } + @Public() @Post('login') - async login(@Body() body: LoginRequest): Promise { + async login(@Body() body: DTO.LoginRequest): Promise { const result = await this.accountService.login(body); return result; } + + @Public() + @Post('refresh-access-token') + async refreshAccessToken(@Req() req): Promise { + const id = req.user.id; + const newAccessToken = this.accountService.refreshAccessToken(id); + return newAccessToken; + } } \ No newline at end of file diff --git a/src/modules/account/account.service.ts b/src/modules/account/account.service.ts index 9fb6dff..9974d85 100644 --- a/src/modules/account/account.service.ts +++ b/src/modules/account/account.service.ts @@ -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 { + const { accessToken, refreshToken } = this.authService.refreshTokens(id); + return { + accessToken: accessToken, + refreshToken: refreshToken + }; + } } \ No newline at end of file diff --git a/src/modules/account/dto/index.ts b/src/modules/account/dto/index.ts index 7038ad0..4793a71 100644 --- a/src/modules/account/dto/index.ts +++ b/src/modules/account/dto/index.ts @@ -11,4 +11,6 @@ export { SignupRequestDto as SignupRequest } from './signup/signup-request.dto'; export { SignupResponseDto as SignupResponse } from './signup/signup-response.dto'; export { LoginRequestDto as LoginRequest } from './login/login-request.dto'; -export { LoginResponseDto as LoginResponse } from './login/login-response.dto' \ No newline at end of file +export { LoginResponseDto as LoginResponse } from './login/login-response.dto' + +export { RefreshAccessTokenResponseDto as RefreshAccessTokenResponse } from './refreshAccessToken/refresh-access-token-response.dto'; \ No newline at end of file diff --git a/src/modules/account/dto/refreshAccessToken/refresh-access-token-response.dto.ts b/src/modules/account/dto/refreshAccessToken/refresh-access-token-response.dto.ts new file mode 100644 index 0000000..4a8fc33 --- /dev/null +++ b/src/modules/account/dto/refreshAccessToken/refresh-access-token-response.dto.ts @@ -0,0 +1,4 @@ +export class RefreshAccessTokenResponseDto { + accessToken: string; + refreshToken: string; +} \ No newline at end of file