import { AuthTokens } from "@/types"; import { jwtDecode } from "jwt-decode"; /** * JWT 토큰의 페이로드 인터페이스 */ interface JwtPayload { exp?: number; iat?: number; [key: string]: any; } /** * 토큰 관련 유틸리티 함수들 */ export class TokenUtils { private static readonly BEARER_PREFIX = "Bearer "; /** * Response 헤더에서 인증 토큰들을 추출 * @param response Response 헤더 * @returns AuthTokens 객체 * @throws Error 인증 헤더가 없거나 유효하지 않은 경우 */ static extractTokensFromHeaders(response: Headers): AuthTokens { const authHeader = response.get("authorization"); const cookieHeader = response.get("set-cookie") || ""; if (!authHeader) { throw new Error("인증 헤더가 없습니다"); } if (!authHeader.startsWith(TokenUtils.BEARER_PREFIX)) { throw new Error("유효하지 않은 인증 헤더입니다"); } return { accessToken: authHeader.substring(TokenUtils.BEARER_PREFIX.length), cookieHeader, }; } /** * Bearer 토큰 형식인지 검증 * @param token 검증할 토큰 * @returns 유효한 Bearer 토큰 여부 */ static isBearerToken(token: string): boolean { return token.startsWith(TokenUtils.BEARER_PREFIX); } /** * Bearer 접두사를 추가합니다. * @param token Access Token * @returns Bearer 형식의 토큰 */ static formatBearerToken(token: string): string { return token.startsWith(TokenUtils.BEARER_PREFIX) ? token : `${TokenUtils.BEARER_PREFIX}${token}`; } /** * JWT 토큰이 만료되었는지 확인 (1분 안전 마진 포함) * @param token JWT 토큰 (Bearer 접두사 포함 가능) * @returns 토큰이 만료되었으면 true, 아니면 false */ static isTokenExpired(token: string): boolean { try { const cleanToken = token.startsWith(TokenUtils.BEARER_PREFIX) ? token.substring(TokenUtils.BEARER_PREFIX.length) : token; const payload = jwtDecode(cleanToken); if (!payload.exp) { return false; } const currentTime = Math.floor(Date.now() / 1000); const bufferTime = 60; // 1분 안전 마진 return payload.exp < (currentTime + bufferTime); } catch { return true; } } }