@point3/logto-module 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +224 -0
  2. package/client/__tests__/m2m-client.spec.ts +60 -0
  3. package/client/__tests__/oauth-client.spec.ts +43 -0
  4. package/client/config.ts +79 -0
  5. package/client/index.ts +5 -0
  6. package/client/logto-login-session.ts +239 -0
  7. package/client/m2m-client.ts +428 -0
  8. package/client/oauth-client.ts +231 -0
  9. package/client/types.ts +136 -0
  10. package/dist/client/__tests__/m2m-client.spec.d.ts +1 -0
  11. package/dist/client/__tests__/m2m-client.spec.js +55 -0
  12. package/dist/client/__tests__/m2m-client.spec.js.map +1 -0
  13. package/dist/client/__tests__/oauth-client.spec.d.ts +1 -0
  14. package/dist/client/__tests__/oauth-client.spec.js +40 -0
  15. package/dist/client/__tests__/oauth-client.spec.js.map +1 -0
  16. package/dist/client/config.d.ts +21 -0
  17. package/dist/client/config.js +16 -0
  18. package/dist/client/config.js.map +1 -0
  19. package/dist/client/index.d.ts +5 -0
  20. package/dist/client/index.js +22 -0
  21. package/dist/client/index.js.map +1 -0
  22. package/dist/client/logto-login-session.d.ts +28 -0
  23. package/dist/client/logto-login-session.js +128 -0
  24. package/dist/client/logto-login-session.js.map +1 -0
  25. package/dist/client/m2m-client.d.ts +34 -0
  26. package/dist/client/m2m-client.js +201 -0
  27. package/dist/client/m2m-client.js.map +1 -0
  28. package/dist/client/oauth-client.d.ts +25 -0
  29. package/dist/client/oauth-client.js +135 -0
  30. package/dist/client/oauth-client.js.map +1 -0
  31. package/dist/client/types.d.ts +45 -0
  32. package/dist/client/types.js +37 -0
  33. package/dist/client/types.js.map +1 -0
  34. package/dist/errors.d.ts +24 -0
  35. package/dist/errors.js +62 -0
  36. package/dist/errors.js.map +1 -0
  37. package/dist/index.d.ts +11 -0
  38. package/dist/index.js +47 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/module.d.ts +4 -0
  41. package/dist/module.js +47 -0
  42. package/dist/module.js.map +1 -0
  43. package/dist/stateless/decorator.d.ts +7 -0
  44. package/dist/stateless/decorator.js +10 -0
  45. package/dist/stateless/decorator.js.map +1 -0
  46. package/dist/stateless/guard.d.ts +10 -0
  47. package/dist/stateless/guard.js +102 -0
  48. package/dist/stateless/guard.js.map +1 -0
  49. package/dist/stateless/guard.spec.d.ts +1 -0
  50. package/dist/stateless/guard.spec.js +210 -0
  51. package/dist/stateless/guard.spec.js.map +1 -0
  52. package/dist/stateless/index.d.ts +2 -0
  53. package/dist/stateless/index.js +19 -0
  54. package/dist/stateless/index.js.map +1 -0
  55. package/dist/token/access-token.d.ts +31 -0
  56. package/dist/token/access-token.js +19 -0
  57. package/dist/token/access-token.js.map +1 -0
  58. package/dist/token/index.d.ts +2 -0
  59. package/dist/token/index.js +19 -0
  60. package/dist/token/index.js.map +1 -0
  61. package/dist/token/verifier.d.ts +13 -0
  62. package/dist/token/verifier.js +66 -0
  63. package/dist/token/verifier.js.map +1 -0
  64. package/dist/tsconfig.tsbuildinfo +1 -0
  65. package/errors.ts +58 -0
  66. package/index.ts +13 -0
  67. package/jest.config.js +6 -0
  68. package/module.ts +85 -0
  69. package/package.json +33 -0
  70. package/stateless/decorator.ts +16 -0
  71. package/stateless/guard.spec.ts +305 -0
  72. package/stateless/guard.ts +76 -0
  73. package/stateless/index.ts +2 -0
  74. package/token/access-token.ts +48 -0
  75. package/token/index.ts +2 -0
  76. package/token/verifier.ts +101 -0
  77. package/tsconfig.json +23 -0
package/README.md ADDED
@@ -0,0 +1,224 @@
1
+ # point3-logto-module
2
+
3
+ NestJS 기반 Logto 인증/권한 통합 모듈
4
+ (서버/클라이언트, M2M, 사용자/역할 관리, OAuth, DI 기반 확장성 제공)
5
+
6
+ ---
7
+
8
+ ## 📦 개요
9
+
10
+ `@point3-logto-module`은 [Logto](https://logto.io/) 인증 시스템을 NestJS 환경에서 손쉽게 통합할 수 있도록 설계된 모듈 번들입니다.
11
+ OAuth, M2M(Machine-to-Machine), 사용자/역할 관리, 토큰 검증, 인증 가드 등 인증/권한 관련 기능을 일관된 DI 패턴으로 제공합니다.
12
+
13
+ - **유연한 로깅 연동**: 외부 로거 모듈/토큰을 DI로 주입받아, 다양한 로깅 시스템과 쉽게 통합
14
+ - **NestJS Dynamic Module 패턴**: 환경/구성에 따라 동적으로 모듈 생성
15
+ - **실제 서비스에서 검증된 인증/권한 관리 기능**: OAuth, M2M, 사용자/역할 관리, 토큰 검증, 인증 가드 등
16
+
17
+ ---
18
+
19
+ ## 🏗️ 주요 구성요소
20
+
21
+ ### 1. LogtoModule (Dynamic Module)
22
+
23
+ - 외부에서 로거 모듈과 토큰을 주입받아, Logto 인증/권한 기능을 번들로 제공
24
+ - `LogtoModule.forLogger(loggerModule, loggerToken)` 패턴으로 사용
25
+
26
+ ### 2. 핵심 서비스/토큰
27
+
28
+ - **OAuthClient**: OAuth 인증(로그인/로그아웃 URI, 토큰 발급 등)
29
+ - **LogtoM2MClient**: 서버 간(M2M) 인증 및 사용자/역할 관리
30
+ - **LogtoLoginSession**: 세션 기반 로그인 플로우 관리
31
+ - **LogtoTokenVerifier**: JWT 토큰 검증 및 권한 체크
32
+ - **LogtoTokenGuard**: NestJS 인증 가드(컨트롤러 보호)
33
+ - **LogtoLoggerServiceToken**: DI로 주입받는 외부 로거 토큰
34
+
35
+ ### 3. 주요 타입/설정
36
+
37
+ - **LogtoConfig**: 인증/권한 기능을 위한 환경설정 객체
38
+ - **Prompt, GrantType**: OAuth 표준 파라미터 Enum
39
+ - **LogtoUser, LogtoRole 등**: 사용자/역할 관리용 타입
40
+
41
+ ---
42
+
43
+ ## ⚙️ 설치
44
+
45
+ ```bash
46
+ npm install @point3-logto-module
47
+ ```
48
+
49
+ ---
50
+
51
+ ## 🚀 사용법
52
+
53
+ ### 1. 외부 로거 모듈 준비
54
+
55
+ ```ts
56
+ // my-logger.module.ts
57
+ import { Module } from '@nestjs/common';
58
+ import { WinstonLoggerService } from './winston-logger.service';
59
+
60
+ export const MY_LOGGER_TOKEN = Symbol.for('LOGGER');
61
+
62
+ @Module({
63
+ providers: [
64
+ {
65
+ provide: MY_LOGGER_TOKEN,
66
+ useClass: WinstonLoggerService,
67
+ },
68
+ ],
69
+ exports: [MY_LOGGER_TOKEN],
70
+ })
71
+ export class MyLoggerModule {}
72
+ ```
73
+
74
+ ### 2. AppModule에서 LogtoModule 동적 생성
75
+
76
+ ```ts
77
+ import { Module } from '@nestjs/common';
78
+ import { LogtoModule } from '@point3-logto-module';
79
+ import { MyLoggerModule, MY_LOGGER_TOKEN } from './my-logger.module';
80
+
81
+ @Module({
82
+ imports: [
83
+ LogtoModule.forLogger(MyLoggerModule, MY_LOGGER_TOKEN),
84
+ ],
85
+ })
86
+ export class AppModule {}
87
+ ```
88
+
89
+ ### 3. 서비스 DI 및 사용 예시
90
+
91
+ ```ts
92
+ import { Injectable } from '@nestjs/common';
93
+ import { OAuthClient, LogtoM2MClient, LogtoLoginSession } from '@point3-logto-module';
94
+
95
+ @Injectable()
96
+ export class AuthService {
97
+ constructor(
98
+ private readonly oauthClient: OAuthClient,
99
+ private readonly m2mClient: LogtoM2MClient,
100
+ private readonly loginSession: LogtoLoginSession,
101
+ ) {}
102
+
103
+ async loginUri() {
104
+ return this.oauthClient.getSignInURI('admin');
105
+ }
106
+
107
+ async createUser(user) {
108
+ return this.m2mClient.createUser(user);
109
+ }
110
+ }
111
+ ```
112
+
113
+ ---
114
+
115
+ ## 🛠️ 주요 기능 및 역할
116
+
117
+ ### LogtoModule
118
+
119
+ - 외부 로거 모듈/토큰을 받아, 인증/권한 관련 서비스와 가드를 번들로 제공
120
+ - DynamicModule 패턴으로 다양한 환경에 유연하게 적용 가능
121
+
122
+ ### OAuthClient
123
+
124
+ - OAuth 로그인/로그아웃 URI 생성
125
+ - 인증 코드로 토큰 발급
126
+ - 토큰 해지(로그아웃)
127
+
128
+ ### LogtoM2MClient
129
+
130
+ - M2M 인증(서버 간 토큰 발급)
131
+ - 사용자/역할 생성, 조회, 수정, 삭제, 할당 등 관리
132
+ - 인증코드 발송/검증, 비밀번호 변경 등 부가 기능
133
+
134
+ ### LogtoLoginSession
135
+
136
+ - 세션 기반 로그인 플로우(단계별 API 호출)
137
+ - 비밀번호 검증, 사용자 식별, 동의 처리 등
138
+
139
+ ### LogtoTokenVerifier
140
+
141
+ - JWT 토큰 검증 및 권한 체크
142
+ - 인증 가드(LogtoTokenGuard)에서 활용
143
+
144
+ ---
145
+
146
+ ## 📝 환경설정 예시 (`LogtoConfig`)
147
+
148
+ ```ts
149
+ const config: LogtoConfig = {
150
+ endpoint: 'https://auth.example.com/oidc',
151
+ appId: 'my-client-id',
152
+ appSecret: 'my-client-secret',
153
+ grantType: GrantType.AuthorizationCode,
154
+ scopes: ['openid', 'profile', 'email'],
155
+ resources: ['https://api.example.com'],
156
+ prompt: Prompt.Login,
157
+ redirectUri: 'https://myapp.com/callback',
158
+ };
159
+ ```
160
+
161
+ ---
162
+
163
+ ## 🗂️ 환경변수 목록 및 설명
164
+
165
+ | 환경변수명 | 설명 | 예시 값 |
166
+ |-----------------------------------|---------------------------------------------------|------------------------------------------|
167
+ | LOGTO_AUTH_ENDPOINT | Logto 인증 서버 OIDC 엔드포인트 | https://auth.example.com/oidc |
168
+ | LOGTO_CLIENT_ID | OAuth 클라이언트 ID | my-client-id |
169
+ | LOGTO_CLIENT_SECRET | OAuth 클라이언트 시크릿 | my-client-secret |
170
+ | LOGTO_RESOURCES | 접근할 리소스 서버(여러 개일 경우 쉼표로 구분) | https://api.example.com |
171
+ | LOGTO_SCOPES | 요청할 OAuth 스코프(쉼표로 구분) | openid,profile,email |
172
+ | LOGTO_PROMPT | OAuth prompt 파라미터 | login |
173
+ | LOGTO_REDIRECT_URI | 인증 후 리다이렉트될 URI | https://myapp.com/callback |
174
+ | LOGTO_SIGN_IN_URI | 기본 로그인 URI | https://auth.example.com |
175
+ | LOGTO_DASHBOARD_SIGN_IN_URI | 대시보드 로그인 URI(선택) | https://dashboard.example.com |
176
+ | LOGTO_M2M_CLIENT_ID | M2M 인증용 클라이언트 ID | my-m2m-client-id |
177
+ | LOGTO_M2M_CLIENT_SECRET | M2M 인증용 클라이언트 시크릿 | my-m2m-client-secret |
178
+ | LOGTO_M2M_RESOURCE | M2M 인증용 리소스 | https://api.example.com |
179
+ | LOGTO_M2M_API_URL | M2M API 서버의 base URL | https://api.example.com/api |
180
+ | LOGTO_JWKS_URI | JWT 검증용 JWKS 엔드포인트 | https://auth.example.com/oidc/jwks |
181
+ | LOGTO_AUTH_ISSUER | JWT 발급자(iss) | https://auth.example.com/oidc |
182
+
183
+ > ⚠️ 실제로 필요한 환경변수는 사용하는 서비스(OAuth, M2M, 세션 등)에 따라 다를 수 있습니다. 위 표를 참고하여 .env 파일 또는 환경설정에 추가하세요.
184
+
185
+ ---
186
+
187
+ ## 🧩 확장/커스터마이징
188
+
189
+ - 외부 로깅 시스템과의 연동이 필요할 경우, 원하는 로거 모듈/토큰을 DI로 주입
190
+ - 각 서비스별로 NestJS의 DI/Provider 패턴을 그대로 활용 가능
191
+ - 필요시 각 서비스/가드를 직접 DI 받아 커스텀 비즈니스 로직 구현 가능
192
+
193
+ ---
194
+
195
+ ## ❓ FAQ
196
+
197
+ - **Q. 로거 토큰이 다르면 어떻게 하나요?**
198
+ → `forLogger`의 두 번째 인자로 원하는 토큰(Symbol)을 넘기면 됩니다.
199
+
200
+ - **Q. 환경변수 기반 설정은 어떻게 하나요?**
201
+ → 각 서비스는 DI로 `ConfigService`를 받아 환경변수 기반으로 설정을 자동 주입받습니다.
202
+
203
+ - **Q. 인증/권한 외에 사용자/역할 관리도 가능한가요?**
204
+ → 네, `LogtoM2MClient`를 통해 사용자/역할 생성, 조회, 수정, 삭제, 할당 등 모든 관리가 가능합니다.
205
+
206
+ ---
207
+
208
+ ## 🏷️ 내보내는 토큰/서비스
209
+
210
+ - `OAuthClientToken`, `LogtoM2MClientToken`, `LogtoTokenVerifierToken`, `LogtoLoginSessionToken`, `LogtoTokenGuard`
211
+ - 각 서비스/가드는 NestJS DI로 바로 주입받아 사용 가능
212
+
213
+ ---
214
+
215
+ ## 📚 참고
216
+
217
+ - [Logto 공식 문서](https://logto.io/)
218
+ - [NestJS 공식 문서](https://docs.nestjs.com/)
219
+
220
+ ---
221
+
222
+ ## 📝 라이선스
223
+
224
+ MIT
@@ -0,0 +1,60 @@
1
+
2
+ import { Test, TestingModule } from "@nestjs/testing";
3
+ import { ConfigModule } from "@nestjs/config";
4
+ import { p3Values } from "point3-common-tool";
5
+
6
+ import { LogtoLoggerServiceToken, LogtoM2MClient, LogtoM2MClientToken, LogtoUser } from "..";
7
+ import { LogtoTokenVerifier, LogtoTokenVerifierToken } from "../../token";
8
+ import { ConsoleLogger } from "@nestjs/common";
9
+
10
+ describe('M2mclient를 이용한 Logto API 테스트', () => {
11
+ let m2mClient: LogtoM2MClient;
12
+ let testUser : LogtoUser;
13
+ let testUserId : string = 'bead71jr45u1';
14
+ let testRoleId : string = '0fwcgs8okjy7lav216sb3';
15
+ beforeAll(async () => {
16
+ const module: TestingModule = await Test.createTestingModule({
17
+ imports: [
18
+ ConfigModule.forRoot({
19
+ envFilePath: `env/.env.${process.env.NODE_ENV}`,
20
+ isGlobal: true,
21
+ })
22
+ ],
23
+ providers: [
24
+ {
25
+ provide: LogtoTokenVerifierToken,
26
+ useClass: LogtoTokenVerifier
27
+ },
28
+ {
29
+ provide: LogtoLoggerServiceToken,
30
+ useClass: ConsoleLogger
31
+ }
32
+ ],
33
+ }).compile();
34
+ m2mClient = module.get<LogtoM2MClient>(LogtoM2MClientToken);
35
+
36
+ // random number 4 자리
37
+ const randomNumber = Math.floor(1000 + Math.random() * 9000);
38
+ testUser = {
39
+ username: p3Values.Guid.create('test').toString().replace(/-/g, '_'), // '01958ea8-8a60-7c41-bef2-f6ed8d2d5506'
40
+ name: 'test' + randomNumber,
41
+ primaryEmail: 'test' + randomNumber + '@test.com',
42
+ password: 'test123',
43
+ primaryPhone: '0101111' + randomNumber,
44
+ };
45
+ });
46
+
47
+ it('유저 생성', async () => {
48
+ const userId = await m2mClient.createUser(testUser);
49
+ expect(userId).toBeDefined();
50
+ testUserId = userId;
51
+ });
52
+
53
+ it('토큰 발급', async () => {
54
+ await m2mClient.fetchAccessToken();
55
+ });
56
+
57
+ it('유저 정보 수정', async () => {
58
+ await m2mClient.updateUserClientInfo(testUserId);
59
+ });
60
+ });
@@ -0,0 +1,43 @@
1
+ import { Test } from "@nestjs/testing";
2
+ import { LogtoLoggerServiceToken, OAuthClient, OAuthClientToken, SignInType } from "..";
3
+ import { ConfigModule } from "@nestjs/config";
4
+ import { ConsoleLogger } from "@nestjs/common";
5
+ import { LogtoTokenVerifierToken, LogtoTokenVerifier } from "../../token";
6
+
7
+ describe('OAuthClient', () => {
8
+ let oauthClient: OAuthClient;
9
+
10
+ beforeEach(async () => {
11
+ const testingModule = await Test.createTestingModule({
12
+ imports: [
13
+ ConfigModule.forRoot({
14
+ envFilePath: `env/.env.${process.env.NODE_ENV}`, // Ensure this matches your environment file path
15
+ isGlobal: true,
16
+ }),
17
+ ],
18
+ providers: [
19
+ {
20
+ provide: LogtoLoggerServiceToken,
21
+ useClass: ConsoleLogger
22
+ },
23
+ {
24
+ provide: OAuthClientToken,
25
+ useClass: OAuthClient
26
+ },
27
+ {
28
+ provide: LogtoTokenVerifierToken,
29
+ useClass: LogtoTokenVerifier
30
+ }
31
+ ],
32
+ }).compile();
33
+
34
+ oauthClient = testingModule.get<OAuthClient>(OAuthClientToken);
35
+ });
36
+
37
+ it('로그인/ 회원가입을 위한 로그인 페이지 요청 URI 생성', () => {
38
+
39
+ const uri = oauthClient.getSignInURI(SignInType.Admin);
40
+
41
+ expect(uri).toContain('prompt=login');
42
+ });
43
+ });
@@ -0,0 +1,79 @@
1
+ /**
2
+ * LogtoConfig
3
+ *
4
+ * Logto 인증 및 OAuth 클라이언트/서버(M2M) 기능을 위한 환경설정 객체입니다.
5
+ * 각 서비스(OAuthClient, LogtoM2MClient 등)에서 DI를 통해 주입받은 환경변수 기반으로 설정됩니다.
6
+ *
7
+ * 주요 필드 설명:
8
+ * - endpoint: Logto 인증 서버의 엔드포인트 URL (예: https://auth.example.com/oidc)
9
+ * - appId: Logto 애플리케이션의 Client ID
10
+ * - appSecret: Logto 애플리케이션의 Client Secret
11
+ * - grantType: OAuth 인증 플로우에서 사용할 Grant Type (GrantType enum 참고)
12
+ * - scopes: 요청할 OAuth 스코프 목록 (예: ['openid', 'profile', 'email'])
13
+ * - resources: 접근할 리소스 서버 목록 (예: ['https://api.example.com'])
14
+ * - prompt: 인증 요청 시 사용할 prompt 파라미터 (Prompt enum 참고)
15
+ * - includeReservedScopes: Logto의 예약 스코프 포함 여부 (일반적으로 false, 필요시 true)
16
+ * - redirectUri: 인증 후 리다이렉트될 URI (Authorization Code 플로우에서 필수)
17
+ *
18
+ * 예시:
19
+ * ```ts
20
+ * const config: LogtoConfig = {
21
+ * endpoint: 'https://auth.example.com/oidc',
22
+ * appId: 'my-client-id',
23
+ * appSecret: 'my-client-secret',
24
+ * grantType: GrantType.AuthorizationCode,
25
+ * scopes: ['openid', 'profile', 'email'],
26
+ * resources: ['https://api.example.com'],
27
+ * prompt: Prompt.Login,
28
+ * redirectUri: 'https://myapp.com/callback',
29
+ * };
30
+ * ```
31
+ */
32
+ export type LogtoConfig = {
33
+ /** Logto 인증 서버의 엔드포인트 URL (예: https://auth.example.com/oidc) */
34
+ endpoint: string;
35
+ /** Logto 애플리케이션의 Client ID */
36
+ appId: string;
37
+ /** OAuth 인증 플로우에서 사용할 Grant Type (GrantType enum 참고) */
38
+ grantType: GrantType;
39
+ /** Logto 애플리케이션의 Client Secret */
40
+ appSecret: string;
41
+ /** 요청할 OAuth 스코프 목록 (예: ['openid', 'profile', 'email']) */
42
+ scopes?: string[];
43
+ /** 접근할 리소스 서버 목록 (예: ['https://api.example.com']) */
44
+ resources?: string[];
45
+ /** 인증 요청 시 사용할 prompt 파라미터 (Prompt enum 참고) */
46
+ prompt?: Prompt;
47
+ /** Logto의 예약 스코프 포함 여부 (일반적으로 false, 필요시 true) */
48
+ includeReservedScopes?: boolean;
49
+ /** 인증 후 리다이렉트될 URI (Authorization Code 플로우에서 필수) */
50
+ redirectUri?: string;
51
+ };
52
+
53
+ /**
54
+ * Prompt
55
+ *
56
+ * 인증 요청 시 prompt 파라미터로 사용할 값의 Enum.
57
+ * - None: 사용자 상호작용 없이 인증 시도
58
+ * - Consent: 동의 화면 강제 표시
59
+ * - Login: 로그인 화면 강제 표시
60
+ */
61
+ export enum Prompt {
62
+ None = "none",
63
+ Consent = "consent",
64
+ Login = "login",
65
+ }
66
+
67
+ /**
68
+ * GrantType
69
+ *
70
+ * OAuth 인증 플로우에서 사용할 Grant Type의 Enum.
71
+ * - AuthorizationCode: 일반적인 사용자 인증(프론트엔드/백엔드 연동)
72
+ * - ClientCredentials: 서버 간(M2M) 인증
73
+ * - RefreshToken: 리프레시 토큰을 통한 토큰 갱신
74
+ */
75
+ export enum GrantType {
76
+ AuthorizationCode = 'authorization_code',
77
+ ClientCredentials = 'client_credentials',
78
+ RefreshToken = 'refresh_token',
79
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./config";
2
+ export * from "./m2m-client";
3
+ export * from "./oauth-client";
4
+ export * from "./logto-login-session";
5
+ export * from "./types";
@@ -0,0 +1,239 @@
1
+ import { Inject, Injectable, LoggerService } from "@nestjs/common";
2
+ import { ConfigService } from "@nestjs/config";
3
+ import axios, { AxiosResponse } from "axios";
4
+ import { axiosAdapter } from "point3-common-tool";
5
+
6
+ import {
7
+ OAuthClient,
8
+ OAuthClientToken,
9
+ SignInType,
10
+ } from "./oauth-client";
11
+
12
+ import {
13
+ LogtoLoggerServiceToken,
14
+ LogtoOAuthRESTTemplate,
15
+ } from "./types";
16
+
17
+ // DI token for LogtoLoginSession
18
+ export const LogtoLoginSessionToken = Symbol.for("LogtoLoginSession");
19
+
20
+ @Injectable()
21
+ /**
22
+ * LogtoLoginSession
23
+ *
24
+ * Logto 인증 세션 플로우를 관리하는 클래스입니다.
25
+ * NestJS DI 환경에서 사용되며, Logto 로그인 과정의 각 단계를 API 호출로 래핑합니다.
26
+ *
27
+ * 주요 역할:
28
+ * - 로그인 세션 시작 및 세션 쿠키 관리
29
+ * - 인증 플로우(로그인 경험) 단계별 진행
30
+ * - 비밀번호 검증, 사용자 식별, 동의 및 제출 처리
31
+ * - 커스텀 로그인 플로우를 위한 저수준 제어 제공
32
+ *
33
+ * 사용 예시:
34
+ * const session = new LogtoLoginSession(...);
35
+ * await session.createSignInSession(...);
36
+ * ...
37
+ */
38
+ export class LogtoLoginSession {
39
+ /**
40
+ * /api 엔드포인트 요청을 위한 REST 템플릿입니다.
41
+ */
42
+ private readonly apiRestTemplate: axiosAdapter.RESTTemplate;
43
+
44
+ /**
45
+ * LogtoLoginSession 생성자
46
+ *
47
+ * @param logger - 로깅 서비스
48
+ * @param configService - 환경설정 서비스
49
+ * @param oauthClient - OAuth 클라이언트 인스턴스
50
+ */
51
+ constructor(
52
+ @Inject(LogtoLoggerServiceToken)
53
+ private readonly logger: LoggerService,
54
+
55
+ @Inject(ConfigService)
56
+ private readonly configService: ConfigService,
57
+
58
+ @Inject(OAuthClientToken)
59
+ private readonly oauthClient: OAuthClient,
60
+ ) {
61
+ // API 기본 URL로 REST 템플릿 초기화
62
+ const baseURL = this.configService.get<string>("LOGTO_M2M_API_URL");
63
+ this.apiRestTemplate = new LogtoOAuthRESTTemplate(this.logger, baseURL);
64
+ }
65
+
66
+ /**
67
+ * Logto 로그인 세션을 시작합니다.
68
+ *
69
+ * 인증 URI를 요청하여 로그인 플로우를 시작하며, 응답에는 세션 쿠키가 포함됩니다.
70
+ *
71
+ * @param signInType - 로그인 유형(예: Admin, Dashboard 등)
72
+ * @returns Axios 응답 객체와 생성된 state 문자열을 포함하는 객체
73
+ */
74
+ public async createSignInSession(
75
+ signInType: SignInType,
76
+ ): Promise<{ response: AxiosResponse | undefined; state: string }> {
77
+ const { uri, state } = this.oauthClient.getSignInURI(signInType);
78
+ const response = await axios.get(uri, {
79
+ maxRedirects: 0,
80
+ validateStatus: (status) => status >= 200 && status <= 400,
81
+ withCredentials: true,
82
+ });
83
+ return { response, state };
84
+ }
85
+
86
+ /**
87
+ * Logto 인증 경험(로그인 플로우 1단계)을 시작합니다.
88
+ *
89
+ * 로그인 UI를 트리거하고 상호작용 이벤트를 설정합니다.
90
+ *
91
+ * @param cookie - 초기 로그인 세션에서 받은 세션 쿠키
92
+ * @returns 경험(Experience) 엔드포인트의 응답 데이터
93
+ * @throws 시작 실패 시 에러 발생
94
+ */
95
+ public async experienceSignIn(cookie: string): Promise<any> {
96
+ try {
97
+ const response = await this.apiRestTemplate.put(
98
+ `/experience`,
99
+ { interactionEvent: 'SignIn' },
100
+ {
101
+ headers: {
102
+ 'Content-Type': 'application/json',
103
+ Cookie: cookie,
104
+ },
105
+ withCredentials: true,
106
+ },
107
+ );
108
+ return response.data;
109
+ } catch (error) {
110
+ this.logger.error('Failed to start login experience');
111
+ throw error;
112
+ }
113
+ }
114
+
115
+ /**
116
+ * 로그인 플로우 중 사용자의 비밀번호를 검증합니다.
117
+ *
118
+ * @param cookie - 세션 쿠키
119
+ * @param dto - identifier(타입/값)와 비밀번호를 포함한 객체
120
+ * @returns 비밀번호 검증 엔드포인트의 응답 데이터
121
+ * @throws 검증 실패 시 에러 발생
122
+ */
123
+ public async verificationPassword(
124
+ cookie: string,
125
+ dto: {
126
+ identifier: {
127
+ type: string;
128
+ value: string;
129
+ };
130
+ password: string;
131
+ },
132
+ ): Promise<any> {
133
+ try {
134
+ const response = await this.apiRestTemplate.post(
135
+ `/experience/verification/password`,
136
+ {
137
+ identifier: dto.identifier,
138
+ password: dto.password,
139
+ },
140
+ {
141
+ headers: { Cookie: cookie },
142
+ withCredentials: true,
143
+ },
144
+ );
145
+ return response.data;
146
+ } catch (error) {
147
+ throw error;
148
+ }
149
+ }
150
+
151
+ /**
152
+ * 로그인 플로우에서 verificationId를 사용해 사용자를 식별합니다.
153
+ *
154
+ * @param cookie - 세션 쿠키
155
+ * @param verificationId - 이전 단계에서 받은 verification ID
156
+ * @returns 식별 엔드포인트의 응답 데이터
157
+ * @throws 식별 실패 시 에러 발생
158
+ */
159
+ public async identify(cookie: string, verificationId: string): Promise<any> {
160
+ try {
161
+ const response = await this.apiRestTemplate.post(
162
+ `/experience/identification`,
163
+ { verificationId },
164
+ {
165
+ headers: { Cookie: cookie },
166
+ withCredentials: true,
167
+ },
168
+ );
169
+ return response.data;
170
+ } catch (error) {
171
+ throw error;
172
+ }
173
+ }
174
+
175
+ /**
176
+ * 인증 경험(Experience)을 제출하여 로그인 플로우를 완료합니다.
177
+ *
178
+ * @param cookie - 세션 쿠키
179
+ * @returns 제출 엔드포인트의 응답 데이터
180
+ * @throws 제출 실패 시 에러 발생
181
+ */
182
+ public async submit(cookie: string): Promise<any> {
183
+ try {
184
+ const response = await this.apiRestTemplate.post(
185
+ `/experience/submit`,
186
+ {},
187
+ {
188
+ headers: { Cookie: cookie },
189
+ withCredentials: true,
190
+ },
191
+ );
192
+ return response.data;
193
+ } catch (error) {
194
+ throw error;
195
+ }
196
+ }
197
+
198
+ /**
199
+ * 사용자를 동의(Consent) 화면으로 리다이렉트합니다.
200
+ *
201
+ * 인증 후 토큰 발급 전 동의 화면으로 이동할 때 사용합니다.
202
+ *
203
+ * @param redirectTo - 동의 페이지로 리다이렉트할 URL
204
+ * @param cookie - 세션 쿠키
205
+ * @returns 리다이렉트 요청의 Axios 응답 객체
206
+ */
207
+ public async redirectToConsent(redirectTo: string, cookie: string): Promise<AxiosResponse> {
208
+ const response = await axios.get(redirectTo, {
209
+ maxRedirects: 0,
210
+ validateStatus: (status) => status >= 200 && status <= 400,
211
+ withCredentials: true,
212
+ headers: { Cookie: cookie },
213
+ });
214
+ return response;
215
+ }
216
+
217
+ /**
218
+ * 사용자의 동의(Consent)를 Logto 서버에 제출합니다.
219
+ *
220
+ * @param cookie - 세션 쿠키
221
+ * @returns 동의 엔드포인트의 응답 데이터
222
+ * @throws 동의 제출 실패 시 에러 발생
223
+ */
224
+ public async consent(cookie: string): Promise<any> {
225
+ try {
226
+ const response = await this.apiRestTemplate.post(
227
+ `/interaction/consent`,
228
+ {},
229
+ {
230
+ headers: { Cookie: cookie },
231
+ withCredentials: true,
232
+ },
233
+ );
234
+ return response.data;
235
+ } catch (error) {
236
+ throw error;
237
+ }
238
+ }
239
+ }