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

- 자동 로그인 로직 cookie 로 변경
This commit is contained in:
2025-12-07 22:46:01 +09:00
parent 91e4f987ea
commit abee778691
13 changed files with 239 additions and 70 deletions

View File

@@ -5,11 +5,23 @@ import {
NestFastifyApplication
} from '@nestjs/platform-fastify';
import { AllExceptionsFilter } from './common/filters/all-exceptions.filter';
import fastifyCookie from '@fastify/cookie';
import * as path from 'path';
import * as fs from 'fs';
async function bootstrap() {
const isProd = process.env.NODE_ENV === 'prod';
let httpsOptions = {};
if (!isProd) {
const certPath = path.join(__dirname, "..\\..", "certs");
httpsOptions = {
key: fs.readFileSync(path.join(certPath, 'localhost+2-key.pem')),
cert: fs.readFileSync(path.join(certPath, 'localhost+2.pem'))
};
}
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter()
new FastifyAdapter({ https: httpsOptions })
);
app.enableCors({
origin: (origin, callback) => {
@@ -30,6 +42,9 @@ async function bootstrap() {
app.enableShutdownHooks();
app.useGlobalFilters(new AllExceptionsFilter());
app.register(fastifyCookie, {
secret: process.env.JWT_SECRET
});
await app.listen(process.env.PORT ?? 3000, '0.0.0.0', () => { process.env.NODE_ENV !== 'prod' && console.log(`servier is running on ${process.env.PORT}`) });
}

View File

@@ -5,19 +5,22 @@ import { JwtService } from '@nestjs/jwt';
export class AuthService {
constructor(private readonly jwtService: JwtService) {}
generateTokens(payload: any) {
const accessToken = this.jwtService.sign(payload, { expiresIn: '5s' });
const refreshToken = this.jwtService.sign({id: payload.id}, { expiresIn: '7d' });
generateTokens(id: string) {
const accessToken = this.jwtService.sign({id: id}, { expiresIn: '5s' });
const refreshToken = this.jwtService.sign({id: id}, { expiresIn: '7d' });
return { accessToken, refreshToken };
}
refreshTokens(refreshToken: string) {
refreshTokens(id: string) {
try {
const payload = this.jwtService.verify(refreshToken);
return this.generateTokens(payload);
return this.generateTokens(id);
} catch (e) {
throw new UnauthorizedException('Invalid Refresh Token');
}
}
validateToken(token: string) {
return this.jwtService.verify(token);
}
}

View File

@@ -1,26 +1,33 @@
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PassportStrategy } from '@nestjs/passport';
import { FastifyRequest } from 'fastify';
import { ExtractJwt, Strategy } from 'passport-jwt';
const extractJwtFromCookie = (req: FastifyRequest | any): string | null => {
if (req.cookies && req.cookies['refresh_token']) {
return req.cookies['refresh_token'];
}
return null;
}
@Injectable()
export class JwtRefreshStrategy extends PassportStrategy(Strategy, 'refresh-token') {
constructor(configService: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
jwtFromRequest: ExtractJwt.fromExtractors([extractJwtFromCookie]),
secretOrKey: configService.get<string>('JWT_SECRET')!,
passReqToCallback: true
});
}
async validate(payload: any) {
const token = ExtractJwt.fromAuthHeaderAsBearerToken();
if (!token) throw new UnauthorizedException('Invalid Refresh Token');
async validate(req: FastifyRequest, payload: any) {
const refreshToken = req.cookies['refresh_token'];
if (!refreshToken) throw new UnauthorizedException('Invalid Refresh Token');
return {
id: payload.id,
token
refreshToken
};
}
}

View File

@@ -1,9 +1,10 @@
import { Body, Controller, Get, Headers, Post, Query, Req, UseGuards } from "@nestjs/common";
import { Body, Controller, Get, Headers, Post, Query, Req, Res, UseGuards } from "@nestjs/common";
import { AccountService } from "./account.service";
import * as DTO from "./dto";
import { JwtAccessAuthGuard } from "src/middleware/auth/guard/access-token.guard";
import { Public } from "src/common/decorators/public.decorator";
import { JwtRefreshAuthGuard } from "src/middleware/auth/guard/refresh-token.guard";
import type { FastifyReply, FastifyRequest } from "fastify";
import { AuthGuard } from "@nestjs/passport";
import { JwtAccessAuthGuard } from "src/middleware/auth/guard/access-token.guard";
@UseGuards(JwtAccessAuthGuard)
@Controller('account')
@@ -65,18 +66,44 @@ export class AccountController {
@Public()
@Post('login')
async login(@Body() body: DTO.LoginRequest): Promise<DTO.LoginResponse> {
console.log('a');
async login(@Body() body: DTO.LoginRequest, @Res({ passthrough: true }) res: FastifyReply): Promise<DTO.LoginResponse> {
const result = await this.accountService.login(body);
return result;
if (result.success) {
res.setCookie('refresh_token', result.refreshToken!, {
httpOnly: true,
path: '/',
secure: true,
maxAge: 7 * 24 * 60 * 60 * 1000
});
}
return {
success: result.success,
message: result.message,
error: result.error,
accessToken: result.accessToken
};
}
@Public()
@UseGuards(JwtRefreshAuthGuard)
@UseGuards(AuthGuard('refresh-token'))
@Get('refresh-access-token')
async refreshAccessToken(@Req() req): Promise<DTO.RefreshAccessTokenResponse> {
const id = req.user.id;
const newAccessToken = this.accountService.refreshAccessToken(id);
return newAccessToken;
async refreshAccessToken(@Req() req, @Res({ passthrough: true }) res: FastifyReply): Promise<DTO.RefreshAccessTokenResponse> {
const result = await this.accountService.refreshAccessToken(req.user.id);
if (result.success) {
res.setCookie('refresh_token', result.refreshToken!, {
httpOnly: true,
path: '/',
secure: true,
maxAge: 7 * 24 * 60 * 60 * 1000
});
return {
success: result.success,
message: result.message,
error: result.error,
accessToken: result.accessToken
}
}
return result;
}
}

View File

@@ -95,13 +95,9 @@ export class AccountService {
}
{
const { id, accountId, status, isDeleted, birthday } = queryResult[0];
const { id } = queryResult[0];
const payload = {
id, accountId, status, isDeleted, birthday
};
const { accessToken, refreshToken } = this.authService.generateTokens(payload);
const { accessToken, refreshToken } = this.authService.generateTokens(id);
return {
success: true,

View File

@@ -2,5 +2,5 @@ import { BaseResponseDto } from "../base-response.dto";
export class RefreshAccessTokenResponseDto extends BaseResponseDto{
accessToken: string;
refreshToken: string;
refreshToken?: string;
}