@point3/logto-module
Version:
포인트3 내부 logto Authentication 모듈입니다
231 lines (218 loc) • 7.58 kB
text/typescript
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");
()
/**
* 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;
}
}
}