UNPKG

@point3/logto-module

Version:

포인트3 내부 logto Authentication 모듈입니다

92 lines (77 loc) 3.81 kB
import { Injectable, UnauthorizedException } from "@nestjs/common"; import { jwtVerify, createRemoteJWKSet } from "jose"; import { LogtoVerifierConfig } from "../client/config"; import * as token from "./access-token"; export const LogtoTokenVerifierToken = Symbol.for("LogtoTokenVerifier"); @Injectable() export class LogtoTokenVerifier { constructor(private readonly config: LogtoVerifierConfig) { } /** * 토큰을 검증하고 필요에 따라 필수 스코프와 역할을 확인합니다. * @param token 검증할 토큰입니다. * @param requiredScopes 선택적으로 확인할 스코프입니다. * @param requiredRoles 선택적으로 확인할 역할입니다. * @returns 토큰이 유효한 경우 토큰 페이로드를 반환하는 Promise입니다. * @throws UnauthorizedException 토큰이 유효하지 않은 경우 발생합니다. */ public async verifyToken(token: string): Promise<token.AccessTokenPayload>; public async verifyToken(token: string, requiredScopes: string[], requiredRoles: string[]): Promise<token.AccessTokenPayload>; public async verifyToken(token: string, requiredScopes?: string[], requiredRoles?: string[]): Promise<token.AccessTokenPayload> { if (!token) throw new UnauthorizedException('엑세스 토큰이 존재하지 않습니다.'); const { payload } = await jwtVerify( token, createRemoteJWKSet(new URL(this.config.jwksUri)), { issuer: this.config.issuer } ); const tokenPayload = payload as token.AccessTokenPayload; if (requiredScopes || requiredRoles) { this.shouldContainRequiredPrivileges( tokenPayload, requiredScopes, requiredRoles); } return tokenPayload; } /** * id token을 검증합니다. * @param token id token 문자열입니다. * @returns id token 페이로드입니다. */ public async verifyIdToken(token: string): Promise<token.IdTokenPayload> { const { payload } = await jwtVerify( token, createRemoteJWKSet(new URL(this.config.jwksUri)), { issuer: this.config.issuer } ); return payload as token.IdTokenPayload; } /** * 토큰 페이로드를 통해 필요한 스코프와 역할 등 추가적인 검사를 수행합니다. * @param payload 토큰 페이로드입니다. * @param requiredScopes 선택적으로 확인할 스코프입니다. * @param requiredRoles 선택적으로 확인할 역할입니다. */ private shouldContainRequiredPrivileges( payload: token.AccessTokenPayload, requiredScopes?: string[], requiredRoles?: string[] ): void { const { userScopes, userRoles } = payload; const scopes = userScopes?.flat() ?? []; if (this.hasInsufficientScopes(requiredScopes, scopes)) { throw new UnauthorizedException( { code: 'auth.insufficient_scope', status: 403 }, { cause: requiredScopes } ); } if (this.hasInsufficientRoles(requiredRoles, userRoles)) { throw new UnauthorizedException( { code: 'auth.role_mismatch', status: 403 }, { cause: requiredRoles } ); } } private hasInsufficientScopes(requiredScopes: string[] | undefined, userScopes: string[]): boolean { return !!(requiredScopes && requiredScopes.length > 0 && !requiredScopes.every(scope => userScopes.includes(scope))); } private hasInsufficientRoles(requiredRoles: string[] | undefined, userRoles: string[]): boolean { return !!(requiredRoles && requiredRoles.length > 0 && !requiredRoles.some(role => userRoles.includes(role))); } }