@point3/logto-module 1.0.22 → 1.1.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.
package/module.ts CHANGED
@@ -1,93 +1,237 @@
1
- import { DynamicModule, Module, Provider, Type } from '@nestjs/common';
1
+ import { DynamicModule, LoggerService, Provider, Type } from '@nestjs/common';
2
2
  import { ConfigModule, ConfigService } from '@nestjs/config';
3
- import {
4
- LogtoLoggerServiceToken,
5
- LogtoLoginSessionToken,
6
- LogtoM2MClientToken,
7
- OAuthClient,
8
- OAuthClientToken,
9
- LogtoLoginSession,
10
- LogtoM2MClient,
3
+ import {
4
+ LogtoLoggerServiceToken,
5
+ LogtoLoginSessionToken,
6
+ LogtoM2MClientToken,
7
+ OAuthClient,
8
+ OAuthClientToken,
9
+ LogtoLoginSession,
10
+ LogtoM2MClient,
11
+ Prompt,
11
12
  } from './client';
12
13
  import { LogtoTokenVerifier, LogtoTokenVerifierToken } from './token';
13
14
  import { LogtoTokenGuard, LogtoTokenGuardToken } from './stateless';
14
15
 
16
+ /** LogtoModule 옵션 주입 토큰 */
17
+ export const LOGTO_MODULE_OPTIONS = Symbol('LOGTO_MODULE_OPTIONS');
18
+
19
+ /** 로거 설정 */
20
+ export interface LogtoLoggerOptions {
21
+ /** 로거 모듈 (forRoot에서만 필요) */
22
+ module?: Type<any>;
23
+ /** 로거 서비스의 Injection Token */
24
+ token: Symbol | string;
25
+ }
26
+
27
+ /** forRoot 옵션 */
28
+ export interface LogtoModuleOptions {
29
+ /** 모듈을 전역으로 설정할지 여부 */
30
+ global?: boolean;
31
+ /** 클라이언트 기능 활성화 (OAuthClient, LogtoM2MClient, LogtoLoginSession) */
32
+ enableClient?: boolean;
33
+ /** 로거 설정 */
34
+ logger: LogtoLoggerOptions & { module: Type<any> };
35
+ }
36
+
37
+ /** forRootAsync useFactory 반환 타입 */
38
+ export interface LogtoModuleFactoryOptions {
39
+ /** 클라이언트 기능 활성화 */
40
+ enableClient?: boolean;
41
+ }
42
+
43
+ /** forRootAsync 옵션 */
44
+ export interface LogtoModuleAsyncOptions {
45
+ /** 모듈을 전역으로 설정할지 여부 */
46
+ global?: boolean;
47
+ /** 의존 모듈 목록 (로거 모듈 포함) */
48
+ imports?: Type<any>[];
49
+ /** 로거 서비스의 Injection Token */
50
+ loggerToken: Symbol | string;
51
+ /** 옵션 팩토리 함수 */
52
+ useFactory: (...args: any[]) => LogtoModuleFactoryOptions | Promise<LogtoModuleFactoryOptions>;
53
+ /** 팩토리 함수에 주입할 의존성 */
54
+ inject?: any[];
55
+ }
56
+
15
57
  /**
16
58
  * LogtoModule
17
59
  *
18
60
  * Logto 인증 및 토큰 검증, 클라이언트 기능을 NestJS 모듈로 제공합니다.
19
61
  *
20
- * 모듈은 `LOGTO_CLIENT` 환경 변수 값에 따라 동적으로 구성됩니다.
21
- * - **Stateless 모드 (`LOGTO_CLIENT=false` 또는 미설정)**:
22
- * API 서버와 같이 토큰 검증만 필요한 경우 사용합니다. `@LogtoProtected()` 가드와 `LogtoTokenVerifier`만 활성화되어 최소한의 리소스를 사용합니다.
23
- *
24
- * - **Stateful 모드 (`LOGTO_CLIENT=true`)**:
25
- * 로그인/로그아웃 처리가 필요한 웹 애플리케이션이나, M2M 통신으로 Logto의 User/Role 관리 API를 사용해야 할 경우에 사용합니다.
26
- * `OAuthClient`, `LogtoM2MClient` 등 모든 클라이언트 기능이 추가로 활성화됩니다.
62
+ * - **Stateless 모드 (enableClient=false)**:
63
+ * API 서버와 같이 토큰 검증만 필요한 경우 사용합니다.
64
+ * `@LogtoProtected()` 가드와 `LogtoTokenVerifier`만 활성화됩니다.
27
65
  *
28
- *외부 로거 모듈과 연동하여 일관된 로깅을 지원합니다.
29
- **/
66
+ * - **Stateful 모드 (enableClient=true)**:
67
+ * 로그인/로그아웃 처리가 필요한 웹 애플리케이션이나,
68
+ * M2M 통신으로 Logto의 User/Role 관리 API를 사용해야 할 경우에 사용합니다.
69
+ * `OAuthClient`, `LogtoM2MClient` 등 모든 클라이언트 기능이 활성화됩니다.
70
+ */
30
71
  export class LogtoModule {
31
-
32
72
  /**
33
- * forLogger
73
+ * forRoot
34
74
  *
35
- * 외부 로거와 연동하여 LogtoModule을 초기화합니다.
75
+ * LogtoModule을 초기화합니다. (동기 설정)
36
76
  *
37
- * @param loggerModule - DI에 등록된 로거 모듈 (예: WinstonLoggerModule)
38
- * @param loggerToken - DI에 등록된 로거 서비스의 Injection Token
39
- * @param global - 모듈을 전역(Global)으로 설정할지 여부 (기본값: false)
77
+ * @param options - 모듈 설정 옵션
40
78
  * @returns DynamicModule
41
79
  *
42
80
  * @example
43
- * // src/app.module.ts
44
- *
45
- * import { Module } from '@nestjs/common';
46
- * import { ConfigModule } from '@nestjs/config';
47
- * import { LogtoModule } from 'point3-logto-module';
48
- * // `MyLoggerModule`과 `MY_LOGGER_TOKEN`은 사용하는 로깅 시스템에 맞게 구현해야 합니다.
49
- * import { MyLoggerModule, MY_LOGGER_TOKEN } from './common/logger';
50
- *
51
- * \@Module({
52
- * imports: [
53
- * // .env 파일을 읽기 위해 ConfigModule을 먼저 임포트합니다.
54
- * ConfigModule.forRoot({ isGlobal: true }),
55
- *
56
- * // LogtoModule을 설정합니다.
57
- * // `LOGTO_CLIENT` 환경변수 값에 따라 필요한 서비스만 주입됩니다.
58
- * LogtoModule.forLogger(MyLoggerModule, MY_LOGGER_TOKEN, true),
59
- * ],
81
+ * // Stateless 모드: 토큰 검증만 필요한 경우
82
+ * LogtoModule.forRoot({
83
+ * global: true,
84
+ * logger: { module: WinstonLoggerModule, token: 'LOGGER' },
60
85
  * })
61
- * export class AppModule {}
62
86
  *
63
87
  * @example
64
- * // src/my-api.controller.ts (Stateless 모드 사용 예시)
88
+ * // Stateful 모드: 클라이언트 기능이 필요한 경우
89
+ * LogtoModule.forRoot({
90
+ * global: true,
91
+ * enableClient: true,
92
+ * logger: { module: WinstonLoggerModule, token: 'LOGGER' },
93
+ * })
94
+ */
95
+ static forRoot(options: LogtoModuleOptions): DynamicModule {
96
+ const { global = false, enableClient = false, logger } = options;
97
+
98
+ const baseProviders: Provider[] = [
99
+ {
100
+ provide: LogtoLoggerServiceToken,
101
+ useExisting: logger.token,
102
+ },
103
+ {
104
+ provide: LogtoTokenVerifierToken,
105
+ useFactory: (configService: ConfigService) => {
106
+ return new LogtoTokenVerifier({
107
+ jwksUri: configService.get<string>('LOGTO_JWKS_URI') ?? 'http://localhost:3001/oidc/jwks',
108
+ issuer: configService.getOrThrow<string>('LOGTO_AUTH_ISSUER'),
109
+ });
110
+ },
111
+ inject: [ConfigService],
112
+ },
113
+ {
114
+ provide: LogtoTokenGuardToken,
115
+ useClass: LogtoTokenGuard,
116
+ },
117
+ ];
118
+
119
+ const clientProviders: Provider[] = enableClient
120
+ ? [
121
+ {
122
+ provide: OAuthClientToken,
123
+ useFactory: (configService: ConfigService, loggerService: LoggerService) => {
124
+ return new OAuthClient(
125
+ {
126
+ endpoint: configService.getOrThrow<string>('LOGTO_AUTH_ENDPOINT'),
127
+ clientId: configService.getOrThrow<string>('LOGTO_CLIENT_ID'),
128
+ clientSecret: configService.getOrThrow<string>('LOGTO_CLIENT_SECRET'),
129
+ resources: [configService.getOrThrow<string>('LOGTO_RESOURCES')],
130
+ scopes: configService.getOrThrow<string>('LOGTO_SCOPES').split(','),
131
+ prompt: configService.getOrThrow<string>('LOGTO_PROMPT') as Prompt,
132
+ redirectUri: configService.getOrThrow<string>('LOGTO_REDIRECT_URI'),
133
+ signInUri: configService.getOrThrow<string>('LOGTO_SIGN_IN_URI'),
134
+ dashboardSignInUri: configService.get<string>('LOGTO_DASHBOARD_SIGN_IN_URI'),
135
+ },
136
+ loggerService,
137
+ );
138
+ },
139
+ inject: [ConfigService, LogtoLoggerServiceToken],
140
+ },
141
+ {
142
+ provide: LogtoLoginSessionToken,
143
+ useFactory: (
144
+ configService: ConfigService,
145
+ loggerService: LoggerService,
146
+ oauthClient: OAuthClient,
147
+ ) => {
148
+ return new LogtoLoginSession(
149
+ configService.getOrThrow<string>('LOGTO_M2M_API_URL'),
150
+ loggerService,
151
+ oauthClient,
152
+ );
153
+ },
154
+ inject: [ConfigService, LogtoLoggerServiceToken, OAuthClientToken],
155
+ },
156
+ {
157
+ provide: LogtoM2MClientToken,
158
+ useFactory: (
159
+ configService: ConfigService,
160
+ tokenVerifier: LogtoTokenVerifier,
161
+ loggerService: LoggerService,
162
+ ) => {
163
+ return new LogtoM2MClient(
164
+ {
165
+ endpoint: configService.getOrThrow<string>('LOGTO_AUTH_ENDPOINT'),
166
+ clientId: configService.getOrThrow<string>('LOGTO_M2M_CLIENT_ID'),
167
+ clientSecret: configService.getOrThrow<string>('LOGTO_M2M_CLIENT_SECRET'),
168
+ resource: configService.getOrThrow<string>('LOGTO_M2M_RESOURCE'),
169
+ apiUrl: configService.getOrThrow<string>('LOGTO_M2M_API_URL'),
170
+ scopes: ['all'],
171
+ },
172
+ tokenVerifier,
173
+ loggerService,
174
+ );
175
+ },
176
+ inject: [ConfigService, LogtoTokenVerifierToken, LogtoLoggerServiceToken],
177
+ },
178
+ ]
179
+ : [];
180
+
181
+ const providers = [...baseProviders, ...clientProviders];
182
+
183
+ return {
184
+ module: LogtoModule,
185
+ global,
186
+ imports: [ConfigModule, logger.module],
187
+ providers,
188
+ exports: providers,
189
+ };
190
+ }
191
+
192
+ /**
193
+ * forRootAsync
194
+ *
195
+ * LogtoModule을 초기화합니다. (비동기 설정, ConfigService 활용 가능)
65
196
  *
66
- * import { Controller, Get } from '@nestjs/common';
67
- * import { LogtoProtected } from 'point3-logto-module';
197
+ * @param options - 비동기 모듈 설정 옵션
198
+ * @returns DynamicModule
68
199
  *
69
- * \@Controller('items')
70
- * export class MyApiController {
71
- * \@Get('protected')
72
- * \@LogtoProtected() // 헤더의 Bearer 토큰을 자동으로 검증합니다.
73
- * getProtectedResource() {
74
- * return { message: 'This is a protected resource.' };
75
- * }
76
- * }
200
+ * @example
201
+ * LogtoModule.forRootAsync({
202
+ * global: true,
203
+ * imports: [WinstonLoggerModule],
204
+ * loggerToken: 'LOGGER',
205
+ * useFactory: (configService: ConfigService) => ({
206
+ * enableClient: configService.get('LOGTO_CLIENT') === 'true',
207
+ * }),
208
+ * inject: [ConfigService],
209
+ * })
77
210
  */
78
- static forLogger(
79
- loggerModule: Type<any>,
80
- loggerToken: Symbol | string,
81
- global: boolean = false,
82
- ): DynamicModule {
211
+ static forRootAsync(options: LogtoModuleAsyncOptions): DynamicModule {
212
+ const { global = false, imports = [], loggerToken, useFactory, inject = [] } = options;
213
+
214
+ const asyncOptionsProvider: Provider = {
215
+ provide: LOGTO_MODULE_OPTIONS,
216
+ useFactory,
217
+ inject,
218
+ };
219
+
83
220
  const baseProviders: Provider[] = [
221
+ asyncOptionsProvider,
84
222
  {
85
223
  provide: LogtoLoggerServiceToken,
86
224
  useExisting: loggerToken,
87
225
  },
88
226
  {
89
227
  provide: LogtoTokenVerifierToken,
90
- useClass: LogtoTokenVerifier,
228
+ useFactory: (configService: ConfigService) => {
229
+ return new LogtoTokenVerifier({
230
+ jwksUri: configService.get<string>('LOGTO_JWKS_URI') ?? 'http://localhost:3001/oidc/jwks',
231
+ issuer: configService.getOrThrow<string>('LOGTO_AUTH_ISSUER'),
232
+ });
233
+ },
234
+ inject: [ConfigService],
91
235
  },
92
236
  {
93
237
  provide: LogtoTokenGuardToken,
@@ -95,49 +239,89 @@ export class LogtoModule {
95
239
  },
96
240
  ];
97
241
 
98
- const statefulProviders: Provider[] = [
242
+ const clientProviders: Provider[] = [
99
243
  {
100
244
  provide: OAuthClientToken,
101
- useFactory: (configService: ConfigService, logger: any) => {
102
- if (configService.get<string>('LOGTO_CLIENT')?.toLowerCase() === 'true') {
103
- return new OAuthClient(configService, logger);
245
+ useFactory: (
246
+ opts: LogtoModuleFactoryOptions,
247
+ configService: ConfigService,
248
+ loggerService: LoggerService,
249
+ ) => {
250
+ if (opts.enableClient) {
251
+ return new OAuthClient(
252
+ {
253
+ endpoint: configService.getOrThrow<string>('LOGTO_AUTH_ENDPOINT'),
254
+ clientId: configService.getOrThrow<string>('LOGTO_CLIENT_ID'),
255
+ clientSecret: configService.getOrThrow<string>('LOGTO_CLIENT_SECRET'),
256
+ resources: [configService.getOrThrow<string>('LOGTO_RESOURCES')],
257
+ scopes: configService.getOrThrow<string>('LOGTO_SCOPES').split(','),
258
+ prompt: configService.getOrThrow<string>('LOGTO_PROMPT') as Prompt,
259
+ redirectUri: configService.getOrThrow<string>('LOGTO_REDIRECT_URI'),
260
+ signInUri: configService.getOrThrow<string>('LOGTO_SIGN_IN_URI'),
261
+ dashboardSignInUri: configService.get<string>('LOGTO_DASHBOARD_SIGN_IN_URI'),
262
+ },
263
+ loggerService,
264
+ );
104
265
  }
105
266
  return null;
106
267
  },
107
- inject: [ConfigService, LogtoLoggerServiceToken],
268
+ inject: [LOGTO_MODULE_OPTIONS, ConfigService, LogtoLoggerServiceToken],
108
269
  },
109
270
  {
110
271
  provide: LogtoLoginSessionToken,
111
- useFactory: (configService: ConfigService, logger: any, oauthClient: OAuthClient) => {
112
- if (configService.get<string>('LOGTO_CLIENT')?.toLowerCase() === 'true') {
113
- return new LogtoLoginSession(logger, configService, oauthClient);
272
+ useFactory: (
273
+ opts: LogtoModuleFactoryOptions,
274
+ configService: ConfigService,
275
+ loggerService: LoggerService,
276
+ oauthClient: OAuthClient,
277
+ ) => {
278
+ if (opts.enableClient) {
279
+ return new LogtoLoginSession(
280
+ configService.getOrThrow<string>('LOGTO_M2M_API_URL'),
281
+ loggerService,
282
+ oauthClient,
283
+ );
114
284
  }
115
285
  return null;
116
286
  },
117
- inject: [ConfigService, LogtoLoggerServiceToken, OAuthClientToken],
287
+ inject: [LOGTO_MODULE_OPTIONS, ConfigService, LogtoLoggerServiceToken, OAuthClientToken],
118
288
  },
119
289
  {
120
290
  provide: LogtoM2MClientToken,
121
- useFactory: (configService: ConfigService, tokenVerifier: LogtoTokenVerifier, logger: any) => {
122
- if (configService.get<string>('LOGTO_CLIENT')?.toLowerCase() === 'true') {
123
- return new LogtoM2MClient(configService, tokenVerifier, logger);
291
+ useFactory: (
292
+ opts: LogtoModuleFactoryOptions,
293
+ configService: ConfigService,
294
+ tokenVerifier: LogtoTokenVerifier,
295
+ loggerService: LoggerService,
296
+ ) => {
297
+ if (opts.enableClient) {
298
+ return new LogtoM2MClient(
299
+ {
300
+ endpoint: configService.getOrThrow<string>('LOGTO_AUTH_ENDPOINT'),
301
+ clientId: configService.getOrThrow<string>('LOGTO_M2M_CLIENT_ID'),
302
+ clientSecret: configService.getOrThrow<string>('LOGTO_M2M_CLIENT_SECRET'),
303
+ resource: configService.getOrThrow<string>('LOGTO_M2M_RESOURCE'),
304
+ apiUrl: configService.getOrThrow<string>('LOGTO_M2M_API_URL'),
305
+ scopes: ['all'],
306
+ },
307
+ tokenVerifier,
308
+ loggerService,
309
+ );
124
310
  }
125
311
  return null;
126
312
  },
127
- inject: [ConfigService, LogtoTokenVerifierToken, LogtoLoggerServiceToken],
313
+ inject: [LOGTO_MODULE_OPTIONS, ConfigService, LogtoTokenVerifierToken, LogtoLoggerServiceToken],
128
314
  },
129
315
  ];
130
-
131
- const providers = [...baseProviders, ...statefulProviders];
316
+
317
+ const providers = [...baseProviders, ...clientProviders];
132
318
 
133
319
  return {
134
320
  module: LogtoModule,
135
- global: global,
136
- imports: [
137
- loggerModule,
138
- ],
139
- providers: providers,
321
+ global,
322
+ imports: [ConfigModule, ...imports],
323
+ providers,
140
324
  exports: providers,
141
325
  };
142
326
  }
143
- };
327
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@point3/logto-module",
3
- "version": "1.0.22",
3
+ "version": "1.1.0",
4
4
  "description": "포인트3 내부 logto Authentication 모듈입니다",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/token/verifier.ts CHANGED
@@ -1,17 +1,14 @@
1
- import { Inject, Injectable, UnauthorizedException } from "@nestjs/common";
2
- import { ConfigService } from "@nestjs/config";
1
+ import { Injectable, UnauthorizedException } from "@nestjs/common";
3
2
  import { jwtVerify, createRemoteJWKSet } from "jose";
4
3
 
4
+ import { LogtoVerifierConfig } from "../client/config";
5
5
  import * as token from "./access-token";
6
6
 
7
7
  export const LogtoTokenVerifierToken = Symbol.for("LogtoTokenVerifier");
8
8
 
9
9
  @Injectable()
10
10
  export class LogtoTokenVerifier {
11
- constructor(
12
- @Inject(ConfigService)
13
- private readonly configService: ConfigService
14
- ) { }
11
+ constructor(private readonly config: LogtoVerifierConfig) { }
15
12
 
16
13
  /**
17
14
  * 토큰을 검증하고 필요에 따라 필수 스코프와 역할을 확인합니다.
@@ -26,12 +23,9 @@ export class LogtoTokenVerifier {
26
23
  public async verifyToken(token: string, requiredScopes?: string[], requiredRoles?: string[]): Promise<token.AccessTokenPayload> {
27
24
  if (!token) throw new UnauthorizedException('엑세스 토큰이 존재하지 않습니다.');
28
25
 
29
- const jwksUri = this.configService.get<string>("LOGTO_JWKS_URI") ?? 'http://localhost:3001/oidc/jwks';
30
- const issuer = this.configService.get<string>("LOGTO_AUTH_ISSUER");
31
-
32
26
  const { payload } = await jwtVerify(
33
- token, createRemoteJWKSet(new URL(jwksUri)),
34
- { issuer }
27
+ token, createRemoteJWKSet(new URL(this.config.jwksUri)),
28
+ { issuer: this.config.issuer }
35
29
  );
36
30
 
37
31
  const tokenPayload = payload as token.AccessTokenPayload;
@@ -50,13 +44,10 @@ export class LogtoTokenVerifier {
50
44
  * @returns id token 페이로드입니다.
51
45
  */
52
46
  public async verifyIdToken(token: string): Promise<token.IdTokenPayload> {
53
- const jwksUri = process.env.LOGTO_JWKS_URI ?? 'http://localhost:3001/oidc/jwks';
54
- const issuer = process.env.LOGTO_AUTH_ISSUER;
55
-
56
47
  const { payload } = await jwtVerify(
57
48
  token,
58
- createRemoteJWKSet(new URL(jwksUri)),
59
- { issuer }
49
+ createRemoteJWKSet(new URL(this.config.jwksUri)),
50
+ { issuer: this.config.issuer }
60
51
  );
61
52
  return payload as token.IdTokenPayload;
62
53
  }
@@ -97,4 +88,4 @@ export class LogtoTokenVerifier {
97
88
  private hasInsufficientRoles(requiredRoles: string[] | undefined, userRoles: string[]): boolean {
98
89
  return !!(requiredRoles && requiredRoles.length > 0 && !requiredRoles.some(role => userRoles.includes(role)));
99
90
  }
100
- }
91
+ }