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<JwtPayload>(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;
}
}
}