UNPKG

@point3/logto-module

Version:

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

231 lines (218 loc) 7.58 kB
import { Injectable, LoggerService } from "@nestjs/common"; import axios, { AxiosResponse } from "axios"; import { axiosAdapter } from "point3-common-tool"; import { OAuthClient, SignInType, } from "./oauth-client"; import { LogtoOAuthRESTTemplate, } from "./types"; // DI token for LogtoLoginSession export const LogtoLoginSessionToken = Symbol.for("LogtoLoginSession"); @Injectable() /** * LogtoLoginSession * * Logto 인증 세션 플로우를 관리하는 클래스입니다. * NestJS DI 환경에서 사용되며, Logto 로그인 과정의 각 단계를 API 호출로 래핑합니다. * * 주요 역할: * - 로그인 세션 시작 및 세션 쿠키 관리 * - 인증 플로우(로그인 경험) 단계별 진행 * - 비밀번호 검증, 사용자 식별, 동의 및 제출 처리 * - 커스텀 로그인 플로우를 위한 저수준 제어 제공 * * 사용 예시: * const session = new LogtoLoginSession(...); * await session.createSignInSession(...); * ... */ export class LogtoLoginSession { /** * /api 엔드포인트 요청을 위한 REST 템플릿입니다. */ private readonly apiRestTemplate: axiosAdapter.RESTTemplate; /** * LogtoLoginSession 생성자 * * @param apiUrl - Logto API URL * @param logger - 로깅 서비스 * @param oauthClient - OAuth 클라이언트 인스턴스 */ constructor( private readonly apiUrl: string, private readonly logger: LoggerService, private readonly oauthClient: OAuthClient, ) { // API 기본 URL로 REST 템플릿 초기화 this.apiRestTemplate = new LogtoOAuthRESTTemplate(this.logger, apiUrl); } /** * Logto 로그인 세션을 시작합니다. * * 인증 URI를 요청하여 로그인 플로우를 시작하며, 응답에는 세션 쿠키가 포함됩니다. * * @param signInType - 로그인 유형(예: Admin, Dashboard 등) * @returns Axios 응답 객체와 생성된 state 문자열을 포함하는 객체 */ public async createSignInSession( signInType: SignInType, ): Promise<{ response: AxiosResponse | undefined; state: string }> { const { uri, state } = this.oauthClient.getSignInURI(signInType); const response = await axios.get(uri, { maxRedirects: 0, validateStatus: (status) => status >= 200 && status <= 400, withCredentials: true, }); return { response, state }; } /** * Logto 인증 경험(로그인 플로우 1단계)을 시작합니다. * * 로그인 UI를 트리거하고 상호작용 이벤트를 설정합니다. * * @param cookie - 초기 로그인 세션에서 받은 세션 쿠키 * @returns 경험(Experience) 엔드포인트의 응답 데이터 * @throws 시작 실패 시 에러 발생 */ public async experienceSignIn(cookie: string): Promise<any> { try { const response = await this.apiRestTemplate.put( `/experience`, { interactionEvent: 'SignIn' }, { headers: { 'Content-Type': 'application/json', Cookie: cookie, }, withCredentials: true, }, ); return response.data; } catch (error) { this.logger.error('Failed to start login experience'); throw error; } } /** * 로그인 플로우 중 사용자의 비밀번호를 검증합니다. * * @param cookie - 세션 쿠키 * @param dto - identifier(타입/값)와 비밀번호를 포함한 객체 * @returns 비밀번호 검증 엔드포인트의 응답 데이터 * @throws 검증 실패 시 에러 발생 */ public async verificationPassword( cookie: string, dto: { identifier: { type: string; value: string; }; password: string; }, ): Promise<any> { try { const response = await this.apiRestTemplate.post( `/experience/verification/password`, { identifier: dto.identifier, password: dto.password, }, { headers: { Cookie: cookie, "Accept-Language": 'ko-KR, ko;' }, withCredentials: true, }, ); return response.data; } catch (error) { throw error; } } /** * 로그인 플로우에서 verificationId를 사용해 사용자를 식별합니다. * * @param cookie - 세션 쿠키 * @param verificationId - 이전 단계에서 받은 verification ID * @returns 식별 엔드포인트의 응답 데이터 * @throws 식별 실패 시 에러 발생 */ public async identify(cookie: string, verificationId: string): Promise<any> { try { const response = await this.apiRestTemplate.post( `/experience/identification`, { verificationId }, { headers: { Cookie: cookie }, withCredentials: true, }, ); return response.data; } catch (error) { throw error; } } /** * 인증 경험(Experience)을 제출하여 로그인 플로우를 완료합니다. * * @param cookie - 세션 쿠키 * @returns 제출 엔드포인트의 응답 데이터 * @throws 제출 실패 시 에러 발생 */ public async submit(cookie: string): Promise<any> { try { const response = await this.apiRestTemplate.post( `/experience/submit`, {}, { headers: { Cookie: cookie }, withCredentials: true, }, ); return response.data; } catch (error) { throw error; } } /** * 사용자를 동의(Consent) 화면으로 리다이렉트합니다. * * 인증 후 토큰 발급 전 동의 화면으로 이동할 때 사용합니다. * * @param redirectTo - 동의 페이지로 리다이렉트할 URL * @param cookie - 세션 쿠키 * @returns 리다이렉트 요청의 Axios 응답 객체 */ public async redirectToConsent(redirectTo: string, cookie: string): Promise<AxiosResponse> { const response = await axios.get(redirectTo, { maxRedirects: 0, validateStatus: (status) => status >= 200 && status <= 400, withCredentials: true, headers: { Cookie: cookie }, }); return response; } /** * 사용자의 동의(Consent)를 Logto 서버에 제출합니다. * * @param cookie - 세션 쿠키 * @returns 동의 엔드포인트의 응답 데이터 * @throws 동의 제출 실패 시 에러 발생 */ public async consent(cookie: string): Promise<any> { try { const response = await this.apiRestTemplate.post( `/interaction/consent`, {}, { headers: { Cookie: cookie }, withCredentials: true, }, ); return response.data; } catch (error) { throw error; } } }