@nauth-toolkit/nestjs 0.1.13 → 0.1.17
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/dist/auth.module.d.ts +48 -0
- package/dist/auth.module.d.ts.map +1 -1
- package/dist/auth.module.js +161 -19
- package/dist/auth.module.js.map +1 -1
- package/dist/decorators/client-info.decorator.d.ts +39 -0
- package/dist/decorators/client-info.decorator.d.ts.map +1 -1
- package/dist/decorators/client-info.decorator.js +41 -0
- package/dist/decorators/client-info.decorator.js.map +1 -1
- package/dist/decorators/current-user.decorator.d.ts +6 -0
- package/dist/decorators/current-user.decorator.d.ts.map +1 -1
- package/dist/decorators/current-user.decorator.js +6 -0
- package/dist/decorators/current-user.decorator.js.map +1 -1
- package/dist/decorators/public.decorator.d.ts +7 -0
- package/dist/decorators/public.decorator.d.ts.map +1 -1
- package/dist/decorators/public.decorator.js +7 -0
- package/dist/decorators/public.decorator.js.map +1 -1
- package/dist/decorators/token-delivery.decorator.d.ts +20 -0
- package/dist/decorators/token-delivery.decorator.d.ts.map +1 -1
- package/dist/dto/index.d.ts +9 -0
- package/dist/dto/index.d.ts.map +1 -1
- package/dist/dto/index.js +10 -0
- package/dist/dto/index.js.map +1 -1
- package/dist/factories/storage-adapter.factory.d.ts +107 -0
- package/dist/factories/storage-adapter.factory.d.ts.map +1 -1
- package/dist/factories/storage-adapter.factory.js +129 -0
- package/dist/factories/storage-adapter.factory.js.map +1 -1
- package/dist/filters/nauth-http-exception.filter.d.ts +80 -0
- package/dist/filters/nauth-http-exception.filter.d.ts.map +1 -1
- package/dist/filters/nauth-http-exception.filter.js +96 -0
- package/dist/filters/nauth-http-exception.filter.js.map +1 -1
- package/dist/guards/auth.guard.d.ts +26 -0
- package/dist/guards/auth.guard.d.ts.map +1 -1
- package/dist/guards/auth.guard.js +44 -0
- package/dist/guards/auth.guard.js.map +1 -1
- package/dist/guards/csrf.guard.d.ts +21 -0
- package/dist/guards/csrf.guard.d.ts.map +1 -1
- package/dist/guards/csrf.guard.js +30 -1
- package/dist/guards/csrf.guard.js.map +1 -1
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +46 -0
- package/dist/index.js.map +1 -1
- package/dist/interceptors/client-info.interceptor.d.ts +37 -0
- package/dist/interceptors/client-info.interceptor.d.ts.map +1 -1
- package/dist/interceptors/client-info.interceptor.js +89 -1
- package/dist/interceptors/client-info.interceptor.js.map +1 -1
- package/dist/interceptors/cookie-token.interceptor.d.ts +16 -0
- package/dist/interceptors/cookie-token.interceptor.d.ts.map +1 -1
- package/dist/interceptors/cookie-token.interceptor.js +80 -16
- package/dist/interceptors/cookie-token.interceptor.js.map +1 -1
- package/dist/providers/nestjs-logger.adapter.d.ts +96 -0
- package/dist/providers/nestjs-logger.adapter.d.ts.map +1 -1
- package/dist/providers/nestjs-logger.adapter.js +105 -1
- package/dist/providers/nestjs-logger.adapter.js.map +1 -1
- package/dist/services/csrf.service.d.ts +61 -0
- package/dist/services/csrf.service.d.ts.map +1 -1
- package/dist/services/csrf.service.js +62 -1
- package/dist/services/csrf.service.js.map +1 -1
- package/dist/services/migrations-bootstrap.service.d.ts +6 -0
- package/dist/services/migrations-bootstrap.service.d.ts.map +1 -1
- package/dist/services/migrations-bootstrap.service.js +6 -0
- package/dist/services/migrations-bootstrap.service.js.map +1 -1
- package/package.json +14 -2
package/dist/auth.module.d.ts
CHANGED
|
@@ -1,10 +1,58 @@
|
|
|
1
1
|
import { DynamicModule } from '@nestjs/common';
|
|
2
2
|
import { NAuthConfig } from '@nauth-toolkit/core';
|
|
3
|
+
/**
|
|
4
|
+
* Extended NAuth Configuration (includes optional entities)
|
|
5
|
+
*
|
|
6
|
+
* Entities are optional - AuthModule will auto-discover them from DataSource if not provided.
|
|
7
|
+
* Consumer apps should NOT need to configure entities if they're already in TypeORM.forRoot().
|
|
8
|
+
*/
|
|
3
9
|
export interface NAuthModuleConfig extends NAuthConfig {
|
|
10
|
+
/**
|
|
11
|
+
* Optional: TypeORM entities from database package
|
|
12
|
+
*
|
|
13
|
+
* **Note:** Entities are optional if already registered in `TypeOrmModule.forRoot()`.
|
|
14
|
+
* AuthModule will auto-discover entities from DataSource metadata.
|
|
15
|
+
*
|
|
16
|
+
* Only provide if you're not using `TypeOrmModule.forRoot()` with entities.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* // PREFERRED - Auto-discovery (entities in TypeORM.forRoot())
|
|
21
|
+
* TypeOrmModule.forRoot({ entities: getNAuthEntities(), ... })
|
|
22
|
+
* AuthModule.forRoot({ jwt: {...} }) // entities not needed
|
|
23
|
+
*
|
|
24
|
+
* // ALSO VALID - Explicit entities (if not using TypeORM.forRoot())
|
|
25
|
+
* AuthModule.forRoot({ entities: getNAuthEntities(), jwt: {...} })
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
4
28
|
entities?: Function[];
|
|
5
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Main Authentication Module (v2.0 - Modular)
|
|
32
|
+
*
|
|
33
|
+
* Core provides base authentication services.
|
|
34
|
+
* Features like MFA, social auth, phone verification are in separate packages.
|
|
35
|
+
*/
|
|
6
36
|
export declare class AuthModule {
|
|
37
|
+
/**
|
|
38
|
+
* Configure module with static configuration
|
|
39
|
+
*
|
|
40
|
+
* @param config - NAuthModuleConfig with entities from database package
|
|
41
|
+
*/
|
|
7
42
|
static forRoot(config: NAuthModuleConfig): DynamicModule;
|
|
43
|
+
/**
|
|
44
|
+
* Validate configuration using Zod schema
|
|
45
|
+
*
|
|
46
|
+
* Validates all configuration sections and cross-dependencies:
|
|
47
|
+
* - Email/phone verification requires respective providers
|
|
48
|
+
* - MFA enforcement modes require specific configurations
|
|
49
|
+
* - Social providers require credentials when enabled
|
|
50
|
+
* - JWT algorithm requires appropriate keys
|
|
51
|
+
* - MaxMind geolocation requires credentials for downloads
|
|
52
|
+
*
|
|
53
|
+
* @param config - Configuration to validate
|
|
54
|
+
* @throws {NAuthException} If validation fails with detailed error messages
|
|
55
|
+
*/
|
|
8
56
|
private static validateConfig;
|
|
9
57
|
}
|
|
10
58
|
//# sourceMappingURL=auth.module.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.module.d.ts","sourceRoot":"","sources":["../src/auth.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,aAAa,EAAU,MAAM,gBAAgB,CAAC;AAK/D,OAAO,EAYL,WAAW,EAoBZ,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.module.d.ts","sourceRoot":"","sources":["../src/auth.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,aAAa,EAAU,MAAM,gBAAgB,CAAC;AAK/D,OAAO,EAYL,WAAW,EAoBZ,MAAM,qBAAqB,CAAC;AAgD7B;;;;;GAKG;AACH,MAAM,WAAW,iBAAkB,SAAQ,WAAW;IACpD;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC;CACvB;AAED;;;;;GAKG;AACH,qBAEa,UAAU;IACrB;;;;OAIG;IACH,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,iBAAiB,GAAG,aAAa;IAwpCxD;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,MAAM,CAAC,cAAc;CAuB9B"}
|
package/dist/auth.module.js
CHANGED
|
@@ -45,7 +45,9 @@ const common_1 = require("@nestjs/common");
|
|
|
45
45
|
const core_1 = require("@nestjs/core");
|
|
46
46
|
const typeorm_1 = require("@nestjs/typeorm");
|
|
47
47
|
const typeorm_2 = require("typeorm");
|
|
48
|
+
// Public API imports
|
|
48
49
|
const core_2 = require("@nauth-toolkit/core");
|
|
50
|
+
// Internal API imports (for framework adapter use only)
|
|
49
51
|
const internal_1 = require("@nauth-toolkit/core/internal");
|
|
50
52
|
const client_info_interceptor_1 = require("./interceptors/client-info.interceptor");
|
|
51
53
|
const cookie_token_interceptor_1 = require("./interceptors/cookie-token.interceptor");
|
|
@@ -53,27 +55,48 @@ const auth_guard_1 = require("./guards/auth.guard");
|
|
|
53
55
|
const csrf_guard_1 = require("./guards/csrf.guard");
|
|
54
56
|
const csrf_service_1 = require("./services/csrf.service");
|
|
55
57
|
const migrations_bootstrap_service_1 = require("./services/migrations-bootstrap.service");
|
|
58
|
+
/**
|
|
59
|
+
* Main Authentication Module (v2.0 - Modular)
|
|
60
|
+
*
|
|
61
|
+
* Core provides base authentication services.
|
|
62
|
+
* Features like MFA, social auth, phone verification are in separate packages.
|
|
63
|
+
*/
|
|
56
64
|
let AuthModule = AuthModule_1 = class AuthModule {
|
|
65
|
+
/**
|
|
66
|
+
* Configure module with static configuration
|
|
67
|
+
*
|
|
68
|
+
* @param config - NAuthModuleConfig with entities from database package
|
|
69
|
+
*/
|
|
57
70
|
static forRoot(config) {
|
|
71
|
+
// Validate configuration
|
|
58
72
|
this.validateConfig(config);
|
|
73
|
+
// Initialize logger wrapper (silent by default if no logger provided)
|
|
59
74
|
const nauthLogger = new core_2.NAuthLogger(config.logger);
|
|
75
|
+
// Log initialization
|
|
60
76
|
if (nauthLogger.isEnabled()) {
|
|
61
77
|
nauthLogger.log('Initializing nauth-toolkit...');
|
|
62
78
|
nauthLogger.debug(`Table prefix: ${config.tablePrefix || 'nauth_'}`);
|
|
63
79
|
nauthLogger.debug(`JWT algorithm: ${config.jwt.algorithm || 'HS256'}`);
|
|
64
80
|
}
|
|
81
|
+
// Storage adapter will be initialized in useFactory below
|
|
82
|
+
// Use config.storageAdapter if provided, otherwise default to MemoryStorageAdapter
|
|
83
|
+
// Determine entities - use provided or discover from DataSource
|
|
65
84
|
const entities = config.entities || [];
|
|
66
85
|
return {
|
|
67
86
|
module: AuthModule_1,
|
|
68
87
|
imports: [
|
|
88
|
+
// TypeORM entities - only if provided (otherwise entities from forRoot() are used)
|
|
69
89
|
...(entities.length > 0 ? [typeorm_1.TypeOrmModule.forFeature(entities)] : []),
|
|
70
90
|
],
|
|
71
91
|
providers: [
|
|
92
|
+
// Auto-run nauth-toolkit migrations on startup (no consumer burden)
|
|
72
93
|
migrations_bootstrap_service_1.nauthMigrationsBootstrapProvider,
|
|
94
|
+
// Global interceptor for automatic client info extraction
|
|
73
95
|
{
|
|
74
96
|
provide: core_1.APP_INTERCEPTOR,
|
|
75
97
|
useClass: client_info_interceptor_1.ClientInfoInterceptor,
|
|
76
98
|
},
|
|
99
|
+
// Global interceptor for cookie token delivery (no-op in JSON mode)
|
|
77
100
|
{
|
|
78
101
|
provide: core_1.APP_INTERCEPTOR,
|
|
79
102
|
useFactory: (config, jwtService, reflector, csrfService) => {
|
|
@@ -83,9 +106,10 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
83
106
|
'NAUTH_CONFIG',
|
|
84
107
|
internal_1.JwtService,
|
|
85
108
|
core_1.Reflector,
|
|
86
|
-
{ token: csrf_service_1.CsrfService, optional: true },
|
|
109
|
+
{ token: csrf_service_1.CsrfService, optional: true }, // Optional - only available when CSRF is enabled
|
|
87
110
|
],
|
|
88
111
|
},
|
|
112
|
+
// CSRF Service (always provided, but only used when tokenDelivery.method === 'cookies' or 'hybrid')
|
|
89
113
|
{
|
|
90
114
|
provide: csrf_service_1.CsrfService,
|
|
91
115
|
useFactory: (config) => {
|
|
@@ -93,6 +117,7 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
93
117
|
},
|
|
94
118
|
inject: ['NAUTH_CONFIG'],
|
|
95
119
|
},
|
|
120
|
+
// CSRF Guard (conditionally applied when using cookie-based token delivery)
|
|
96
121
|
...(config.tokenDelivery?.method === 'cookies' || config.tokenDelivery?.method === 'hybrid'
|
|
97
122
|
? [
|
|
98
123
|
{
|
|
@@ -101,23 +126,34 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
101
126
|
},
|
|
102
127
|
]
|
|
103
128
|
: []),
|
|
129
|
+
// Configuration
|
|
104
130
|
{
|
|
105
131
|
provide: 'NAUTH_CONFIG',
|
|
106
132
|
useValue: config,
|
|
107
133
|
},
|
|
134
|
+
// Logger
|
|
108
135
|
{
|
|
109
136
|
provide: 'NAUTH_LOGGER',
|
|
110
137
|
useValue: nauthLogger,
|
|
111
138
|
},
|
|
139
|
+
// Storage adapter - use config or default to DatabaseStorageAdapter if repositories available
|
|
140
|
+
// WARNING: PRODUCTION REQUIREMENT - MemoryStorageAdapter is NOT safe for production
|
|
141
|
+
// - Data lost on server restart
|
|
142
|
+
// - NOT shared across multiple server instances
|
|
143
|
+
// - Rate limiting per-container (not global in multi-container setups)
|
|
144
|
+
// - Token reuse detection fails in multi-server deployments
|
|
112
145
|
{
|
|
113
146
|
provide: 'STORAGE_ADAPTER',
|
|
114
147
|
useFactory: async (config, logger, rateLimitRepo, storageLockRepo) => {
|
|
148
|
+
// If storage adapter is explicitly provided, use it
|
|
115
149
|
if (config.storageAdapter) {
|
|
116
150
|
const adapter = config.storageAdapter;
|
|
151
|
+
// Inject logger into adapter if it supports setLogger (for factory-created adapters)
|
|
117
152
|
if (adapter &&
|
|
118
153
|
typeof adapter.setLogger === 'function') {
|
|
119
154
|
adapter.setLogger(logger);
|
|
120
155
|
}
|
|
156
|
+
// Inject repositories into DatabaseStorageAdapter if it supports setRepositories
|
|
121
157
|
if (adapter &&
|
|
122
158
|
typeof adapter.setRepositories === 'function') {
|
|
123
159
|
if (rateLimitRepo && storageLockRepo) {
|
|
@@ -127,8 +163,11 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
127
163
|
await adapter.initialize();
|
|
128
164
|
return adapter;
|
|
129
165
|
}
|
|
166
|
+
// No storage adapter provided - try to use DatabaseStorageAdapter if repositories available
|
|
130
167
|
if (rateLimitRepo && storageLockRepo) {
|
|
168
|
+
// Default to DatabaseStorageAdapter when repositories are available (most apps have a database)
|
|
131
169
|
try {
|
|
170
|
+
// Lazy import to avoid bundling if not used
|
|
132
171
|
const { DatabaseStorageAdapter } = await Promise.resolve().then(() => __importStar(require('@nauth-toolkit/storage-database')));
|
|
133
172
|
const adapter = new DatabaseStorageAdapter(null, null, logger);
|
|
134
173
|
adapter.setRepositories(rateLimitRepo, storageLockRepo);
|
|
@@ -138,9 +177,11 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
138
177
|
return adapter;
|
|
139
178
|
}
|
|
140
179
|
catch (error) {
|
|
180
|
+
// If DatabaseStorageAdapter import fails, fall through to error
|
|
141
181
|
logger?.error?.('Failed to create DatabaseStorageAdapter. Please explicitly configure storageAdapter in your config.', { error });
|
|
142
182
|
}
|
|
143
183
|
}
|
|
184
|
+
// No storage adapter provided and no repositories available - REQUIRE explicit configuration
|
|
144
185
|
throw new Error('Storage adapter is REQUIRED for production deployments. ' +
|
|
145
186
|
'MemoryStorageAdapter is NOT safe for production (data lost on restart, not shared across instances). ' +
|
|
146
187
|
'Please configure storageAdapter in your NAuthConfig:\n\n' +
|
|
@@ -156,51 +197,65 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
156
197
|
},
|
|
157
198
|
inject: ['NAUTH_CONFIG', 'NAUTH_LOGGER', 'RateLimitRepository', 'StorageLockRepository'],
|
|
158
199
|
},
|
|
200
|
+
// Rate Limit Repository (optional - only needed for DatabaseStorageAdapter)
|
|
159
201
|
{
|
|
160
202
|
provide: 'RateLimitRepository',
|
|
161
203
|
useFactory: (dataSource) => {
|
|
204
|
+
// Try to find entity from config first
|
|
162
205
|
const entityFromConfig = entities.find((e) => e.name === 'RateLimit');
|
|
163
206
|
if (entityFromConfig) {
|
|
164
207
|
return dataSource.getRepository(entityFromConfig);
|
|
165
208
|
}
|
|
209
|
+
// Try to find by table name in DataSource metadata
|
|
166
210
|
const metadata = dataSource.entityMetadatas.find((m) => m.tableName === 'nauth_rate_limits');
|
|
167
211
|
if (metadata) {
|
|
168
212
|
return dataSource.getRepository(metadata.target);
|
|
169
213
|
}
|
|
214
|
+
// Try to find by class name in metadata
|
|
170
215
|
const metadataByName = dataSource.entityMetadatas.find((m) => typeof m.target === 'function' && m.target.name === 'RateLimit');
|
|
171
216
|
if (metadataByName && typeof metadataByName.target === 'function') {
|
|
172
217
|
return dataSource.getRepository(metadataByName.target);
|
|
173
218
|
}
|
|
219
|
+
// Return null if not found (storage adapter might not be DatabaseStorageAdapter)
|
|
174
220
|
return null;
|
|
175
221
|
},
|
|
176
222
|
inject: [typeorm_2.DataSource],
|
|
177
223
|
},
|
|
224
|
+
// Storage Lock Repository (optional - only needed for DatabaseStorageAdapter)
|
|
178
225
|
{
|
|
179
226
|
provide: 'StorageLockRepository',
|
|
180
227
|
useFactory: (dataSource) => {
|
|
228
|
+
// Try to find entity from config first
|
|
181
229
|
const entityFromConfig = entities.find((e) => e.name === 'StorageLock');
|
|
182
230
|
if (entityFromConfig) {
|
|
183
231
|
return dataSource.getRepository(entityFromConfig);
|
|
184
232
|
}
|
|
233
|
+
// Try to find by table name in DataSource metadata
|
|
185
234
|
const metadata = dataSource.entityMetadatas.find((m) => m.tableName === 'nauth_storage_locks');
|
|
186
235
|
if (metadata) {
|
|
187
236
|
return dataSource.getRepository(metadata.target);
|
|
188
237
|
}
|
|
238
|
+
// Try to find by class name in metadata
|
|
189
239
|
const metadataByName = dataSource.entityMetadatas.find((m) => typeof m.target === 'function' && m.target.name === 'StorageLock');
|
|
190
240
|
if (metadataByName && typeof metadataByName.target === 'function') {
|
|
191
241
|
return dataSource.getRepository(metadataByName.target);
|
|
192
242
|
}
|
|
243
|
+
// Return null if not found (storage adapter might not be DatabaseStorageAdapter)
|
|
193
244
|
return null;
|
|
194
245
|
},
|
|
195
246
|
inject: [typeorm_2.DataSource],
|
|
196
247
|
},
|
|
248
|
+
// Repository Tokens - discover entities from DataSource metadata
|
|
249
|
+
// This allows entities to be auto-discovered if registered in TypeORM.forRoot()
|
|
197
250
|
{
|
|
198
251
|
provide: 'UserRepository',
|
|
199
252
|
useFactory: (dataSource) => {
|
|
253
|
+
// Try to find entity from provided config first
|
|
200
254
|
const entityFromConfig = entities.find((e) => e.name === 'User');
|
|
201
255
|
if (entityFromConfig) {
|
|
202
256
|
return dataSource.getRepository(entityFromConfig);
|
|
203
257
|
}
|
|
258
|
+
// Otherwise, find by table name from DataSource metadata
|
|
204
259
|
const metadata = dataSource.entityMetadatas.find((m) => m.tableName === 'nauth_users');
|
|
205
260
|
if (!metadata) {
|
|
206
261
|
throw new core_2.NAuthException(core_2.AuthErrorCode.VALIDATION_FAILED, 'User entity not found. Register entities in TypeORM.forRoot() or provide in config.entities');
|
|
@@ -323,12 +378,14 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
323
378
|
}
|
|
324
379
|
const metadata = dataSource.entityMetadatas.find((m) => m.tableName === 'nauth_trusted_devices');
|
|
325
380
|
if (!metadata) {
|
|
381
|
+
// Return null if not found (rememberDevice might be disabled)
|
|
326
382
|
return null;
|
|
327
383
|
}
|
|
328
384
|
return dataSource.getRepository(metadata.target);
|
|
329
385
|
},
|
|
330
386
|
inject: [typeorm_2.DataSource],
|
|
331
387
|
},
|
|
388
|
+
// Services
|
|
332
389
|
{
|
|
333
390
|
provide: internal_1.PasswordService,
|
|
334
391
|
useFactory: () => {
|
|
@@ -358,7 +415,7 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
358
415
|
core_2.ClientInfoService,
|
|
359
416
|
'NAUTH_CONFIG',
|
|
360
417
|
'NAUTH_LOGGER',
|
|
361
|
-
{ token: internal_1.AuthAuditService, optional: true },
|
|
418
|
+
{ token: internal_1.AuthAuditService, optional: true }, // Optional - only available when auditLogs.enabled is true
|
|
362
419
|
],
|
|
363
420
|
},
|
|
364
421
|
{
|
|
@@ -370,9 +427,10 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
370
427
|
'ChallengeSessionRepository',
|
|
371
428
|
core_2.ClientInfoService,
|
|
372
429
|
'NAUTH_LOGGER',
|
|
373
|
-
{ token: internal_1.AuthAuditService, optional: true },
|
|
430
|
+
{ token: internal_1.AuthAuditService, optional: true }, // Optional - only available when auditLogs.enabled is true
|
|
374
431
|
],
|
|
375
432
|
},
|
|
433
|
+
// AuthFlowContextBuilder - builds context with pre-computed values
|
|
376
434
|
{
|
|
377
435
|
provide: internal_1.AuthFlowContextBuilder,
|
|
378
436
|
useFactory: (trustedDeviceService, adaptiveMFADecisionService, clientInfoService, logger) => {
|
|
@@ -385,6 +443,7 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
385
443
|
{ token: 'NAUTH_LOGGER', optional: true },
|
|
386
444
|
],
|
|
387
445
|
},
|
|
446
|
+
// AuthFlowStateMachineService - evaluates authentication flow states
|
|
388
447
|
{
|
|
389
448
|
provide: internal_1.AuthFlowStateMachineService,
|
|
390
449
|
useFactory: (contextBuilder, logger) => {
|
|
@@ -412,7 +471,8 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
412
471
|
},
|
|
413
472
|
{
|
|
414
473
|
provide: core_2.AuthService,
|
|
415
|
-
useFactory: (userRepository, loginAttemptRepository, passwordService, jwtService, sessionService, challengeService, challengeHelper, emailVerificationService, clientInfoService, accountLockoutStorage, nauthConfig, logger, auditService,
|
|
474
|
+
useFactory: (userRepository, loginAttemptRepository, passwordService, jwtService, sessionService, challengeService, challengeHelper, emailVerificationService, clientInfoService, accountLockoutStorage, nauthConfig, logger, auditService, // Optional - only available when auditLogs.enabled is true
|
|
475
|
+
phoneVerificationService, mfaService, mfaDeviceRepository, trustedDeviceService, passwordResetService) => {
|
|
416
476
|
return new core_2.AuthService(userRepository, loginAttemptRepository, passwordService, jwtService, sessionService, challengeService, challengeHelper, emailVerificationService, clientInfoService, accountLockoutStorage, nauthConfig, logger, auditService, phoneVerificationService, mfaService, mfaDeviceRepository, trustedDeviceService, passwordResetService);
|
|
417
477
|
},
|
|
418
478
|
inject: [
|
|
@@ -428,9 +488,9 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
428
488
|
core_2.AccountLockoutStorageService,
|
|
429
489
|
'NAUTH_CONFIG',
|
|
430
490
|
'NAUTH_LOGGER',
|
|
431
|
-
{ token: internal_1.AuthAuditService, optional: true },
|
|
491
|
+
{ token: internal_1.AuthAuditService, optional: true }, // Optional - only available when auditLogs.enabled is true
|
|
432
492
|
{ token: core_2.PhoneVerificationService, optional: true },
|
|
433
|
-
{ token: core_2.MFAService, optional: true },
|
|
493
|
+
{ token: core_2.MFAService, optional: true }, // No circular dependency - MFAService no longer depends on AuthService
|
|
434
494
|
{ token: 'MFADeviceRepository', optional: true },
|
|
435
495
|
{ token: internal_1.TrustedDeviceService, optional: true },
|
|
436
496
|
{ token: internal_1.PasswordResetService, optional: true },
|
|
@@ -440,17 +500,25 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
440
500
|
provide: internal_1.TrustedDeviceService,
|
|
441
501
|
useFactory: (config, logger, trustedDeviceRepository) => {
|
|
442
502
|
if (!trustedDeviceRepository) {
|
|
503
|
+
// Return null if repository not available (rememberDevice disabled)
|
|
443
504
|
return null;
|
|
444
505
|
}
|
|
445
506
|
return new internal_1.TrustedDeviceService(config, logger, trustedDeviceRepository);
|
|
446
507
|
},
|
|
447
508
|
inject: ['NAUTH_CONFIG', 'NAUTH_LOGGER', { token: 'TrustedDeviceRepository', optional: true }],
|
|
448
509
|
},
|
|
510
|
+
// Social Auth State Store - shared Map for CSRF state validation across all providers
|
|
449
511
|
{
|
|
450
512
|
provide: 'SOCIAL_AUTH_STATE_STORE',
|
|
451
513
|
useValue: new Map(),
|
|
452
514
|
},
|
|
515
|
+
// Social Provider Registry - internal registry for social providers
|
|
516
|
+
// Provider modules (GoogleSocialAuthModule, AppleSocialAuthModule, etc.)
|
|
517
|
+
// will automatically register themselves with this registry via OnModuleInit
|
|
453
518
|
internal_1.SocialProviderRegistry,
|
|
519
|
+
// MFA Service - registry for auto-registered MFA providers
|
|
520
|
+
// Provider modules (TOTPMFAModule, SMSMFAModule, PasskeyMFAModule, etc.)
|
|
521
|
+
// will automatically register themselves with this service via OnModuleInit
|
|
454
522
|
{
|
|
455
523
|
provide: core_2.MFAService,
|
|
456
524
|
useFactory: (mfaDeviceRepository, userRepository, challengeService, nauthConfig, logger, auditService, clientInfoService) => {
|
|
@@ -477,12 +545,21 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
477
545
|
'SocialAccountRepository',
|
|
478
546
|
core_2.AuthService,
|
|
479
547
|
'NAUTH_LOGGER',
|
|
480
|
-
{ token: internal_1.AuthAuditService, optional: true },
|
|
548
|
+
{ token: internal_1.AuthAuditService, optional: true }, // Optional - only available when auditLogs.enabled is true
|
|
481
549
|
],
|
|
482
550
|
},
|
|
483
551
|
core_2.ClientInfoService,
|
|
552
|
+
// Conditionally provide AuthAuditService based on config.auditLogs.enabled
|
|
553
|
+
// Default to enabled if not specified (backward compatibility)
|
|
554
|
+
//
|
|
555
|
+
// Architecture:
|
|
556
|
+
// - Create ONE instance of InternalAuthAuditService
|
|
557
|
+
// - Export as InternalAuthAuditService for internal packages (social auth, MFA providers)
|
|
558
|
+
// - Alias as AuthAuditService for consumer apps (public API with fetch methods only)
|
|
559
|
+
// This ensures a single instance handles both recording and fetching
|
|
484
560
|
...(config.auditLogs?.enabled !== false
|
|
485
561
|
? [
|
|
562
|
+
// Primary instance: InternalAuthAuditService (has recordEvent + fetch methods)
|
|
486
563
|
{
|
|
487
564
|
provide: internal_1.AuthAuditService,
|
|
488
565
|
useFactory: (auditRepository, userRepository, logger, clientInfoService) => {
|
|
@@ -490,6 +567,9 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
490
567
|
},
|
|
491
568
|
inject: ['AuthAuditRepository', 'UserRepository', 'NAUTH_LOGGER', core_2.ClientInfoService],
|
|
492
569
|
},
|
|
570
|
+
// Alias: AuthAuditService points to the same InternalAuthAuditService instance
|
|
571
|
+
// Consumer apps inject AuthAuditService but get the full instance
|
|
572
|
+
// TypeScript types prevent them from calling recordEvent() (not in public interface)
|
|
493
573
|
{
|
|
494
574
|
provide: core_2.AuthAuditService,
|
|
495
575
|
useExisting: internal_1.AuthAuditService,
|
|
@@ -528,19 +608,25 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
528
608
|
core_2.ClientInfoService,
|
|
529
609
|
'NAUTH_CONFIG',
|
|
530
610
|
'NAUTH_LOGGER',
|
|
531
|
-
{ token: internal_1.AuthAuditService, optional: true },
|
|
611
|
+
{ token: internal_1.AuthAuditService, optional: true }, // Optional - only available when auditLogs.enabled is true
|
|
532
612
|
],
|
|
533
613
|
},
|
|
614
|
+
// MaxMind Module (optional - only provided if package is installed and config is present)
|
|
615
|
+
// Uses dynamic import pattern (no require() - follows NestJS best practices)
|
|
534
616
|
...(config.geoLocation?.maxMind
|
|
535
617
|
? [
|
|
536
618
|
{
|
|
537
619
|
provide: 'MAXMIND_MODULE',
|
|
538
620
|
useFactory: async () => {
|
|
539
621
|
try {
|
|
622
|
+
// Use dynamic import (ES modules) instead of require()
|
|
623
|
+
// This is the proper NestJS way to handle optional peer dependencies
|
|
624
|
+
// Note: Module may not be installed - catch block handles gracefully
|
|
540
625
|
const maxMindModule = await Promise.resolve().then(() => __importStar(require('@maxmind/geoip2-node')));
|
|
541
626
|
return maxMindModule;
|
|
542
627
|
}
|
|
543
628
|
catch {
|
|
629
|
+
// Package not installed - return null (service will handle gracefully)
|
|
544
630
|
return null;
|
|
545
631
|
}
|
|
546
632
|
},
|
|
@@ -552,6 +638,7 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
552
638
|
useValue: null,
|
|
553
639
|
},
|
|
554
640
|
]),
|
|
641
|
+
// GeoLocation Service (optional - only if MaxMind config provided)
|
|
555
642
|
...(config.geoLocation?.maxMind
|
|
556
643
|
? [
|
|
557
644
|
{
|
|
@@ -563,6 +650,7 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
563
650
|
},
|
|
564
651
|
]
|
|
565
652
|
: []),
|
|
653
|
+
// Email Provider (required - must be provided in config or from email package)
|
|
566
654
|
{
|
|
567
655
|
provide: 'EMAIL_PROVIDER',
|
|
568
656
|
useFactory: () => {
|
|
@@ -572,21 +660,25 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
572
660
|
' yarn add @nauth-toolkit/email-nodemailer (for production)');
|
|
573
661
|
}
|
|
574
662
|
const provider = config.emailProvider;
|
|
663
|
+
// Validate required methods (core contract)
|
|
575
664
|
if (typeof provider.sendVerificationEmail !== 'function' ||
|
|
576
665
|
typeof provider.sendPasswordResetEmail !== 'function' ||
|
|
577
666
|
typeof provider.sendWelcomeEmail !== 'function') {
|
|
578
667
|
throw new core_2.NAuthException(core_2.AuthErrorCode.VALIDATION_FAILED, 'emailProvider must implement sendVerificationEmail, sendPasswordResetEmail, and sendWelcomeEmail');
|
|
579
668
|
}
|
|
669
|
+
// Inject logger into provider if it has setLogger method
|
|
580
670
|
{
|
|
581
671
|
const maybeLoggerAware = provider;
|
|
582
672
|
if (typeof maybeLoggerAware.setLogger === 'function') {
|
|
583
673
|
maybeLoggerAware.setLogger(nauthLogger);
|
|
584
674
|
}
|
|
585
675
|
}
|
|
676
|
+
// Inject global variables from email config if provider supports it
|
|
586
677
|
if (typeof provider
|
|
587
678
|
.setGlobalVariables === 'function' &&
|
|
588
679
|
config.email) {
|
|
589
680
|
const globalVars = {};
|
|
681
|
+
// Extract top-level branding fields
|
|
590
682
|
if (config.email.appName)
|
|
591
683
|
globalVars.appName = config.email.appName;
|
|
592
684
|
if (config.email.companyName)
|
|
@@ -601,6 +693,7 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
601
693
|
globalVars.brandColor = config.email.brandColor;
|
|
602
694
|
if (config.email.footerDisclaimer)
|
|
603
695
|
globalVars.footerDisclaimer = config.email.footerDisclaimer;
|
|
696
|
+
// Merge with templates.globalVariables (templates.globalVariables takes precedence)
|
|
604
697
|
const mergedVars = {
|
|
605
698
|
...globalVars,
|
|
606
699
|
...(config.email.templates?.globalVariables || {}),
|
|
@@ -623,9 +716,10 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
623
716
|
'NAUTH_CONFIG',
|
|
624
717
|
core_2.ClientInfoService,
|
|
625
718
|
'NAUTH_LOGGER',
|
|
626
|
-
{ token: internal_1.AuthAuditService, optional: true },
|
|
719
|
+
{ token: internal_1.AuthAuditService, optional: true }, // Optional - only available when auditLogs.enabled is true
|
|
627
720
|
],
|
|
628
721
|
},
|
|
722
|
+
// SMS Provider (optional - only needed if using phone verification)
|
|
629
723
|
...(config.smsProvider
|
|
630
724
|
? [
|
|
631
725
|
{
|
|
@@ -635,26 +729,36 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
635
729
|
if (!provider || typeof provider.sendOTP !== 'function') {
|
|
636
730
|
throw new core_2.NAuthException(core_2.AuthErrorCode.VALIDATION_FAILED, 'smsProvider must implement sendOTP');
|
|
637
731
|
}
|
|
732
|
+
// Inject logger into provider if it has setLogger method
|
|
638
733
|
{
|
|
639
734
|
const maybeLoggerAware = provider;
|
|
640
735
|
if (typeof maybeLoggerAware.setLogger === 'function') {
|
|
641
736
|
maybeLoggerAware.setLogger(nauthLogger);
|
|
642
737
|
}
|
|
643
738
|
}
|
|
739
|
+
// ============================================================================
|
|
740
|
+
// Initialize SMS Template Engine
|
|
741
|
+
// ============================================================================
|
|
644
742
|
const smsTemplates = config.sms?.templates;
|
|
645
743
|
if (smsTemplates) {
|
|
744
|
+
// If user configured templates, the provider must support the template hooks.
|
|
745
|
+
// This avoids silent fallback to hard-coded messages when templates are expected.
|
|
646
746
|
if (typeof provider.setTemplateEngine !== 'function') {
|
|
647
747
|
throw new core_2.NAuthException(core_2.AuthErrorCode.VALIDATION_FAILED, 'sms.templates is configured, but smsProvider does not support templates. ' +
|
|
648
748
|
'Please upgrade your SMS provider package to a version that implements setTemplateEngine().');
|
|
649
749
|
}
|
|
750
|
+
// Use provided engine or create new one
|
|
650
751
|
const templateEngine = smsTemplates.engine ?? new core_2.SMSTemplateEngineImpl(process.cwd(), nauthLogger);
|
|
752
|
+
// Register custom templates
|
|
651
753
|
if (smsTemplates.customTemplates) {
|
|
652
754
|
for (const [type, templateDef] of Object.entries(smsTemplates.customTemplates)) {
|
|
653
755
|
try {
|
|
654
756
|
if (templateDef.content) {
|
|
757
|
+
// Inline template
|
|
655
758
|
templateEngine.registerTemplate(type, { content: templateDef.content });
|
|
656
759
|
}
|
|
657
760
|
else if (templateDef.contentPath) {
|
|
761
|
+
// File-based template
|
|
658
762
|
await templateEngine.registerTemplateFromSources(type, {
|
|
659
763
|
content: { filePath: templateDef.contentPath },
|
|
660
764
|
});
|
|
@@ -666,8 +770,11 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
666
770
|
}
|
|
667
771
|
}
|
|
668
772
|
}
|
|
773
|
+
// Inject template engine into provider
|
|
669
774
|
provider.setTemplateEngine(templateEngine);
|
|
775
|
+
// Set global variables
|
|
670
776
|
if (typeof provider.setGlobalVariables === 'function' && smsTemplates.globalVariables) {
|
|
777
|
+
// Extract top-level branding fields from email config (if available)
|
|
671
778
|
const globalVars = {};
|
|
672
779
|
if (config.email?.appName)
|
|
673
780
|
globalVars.appName = config.email.appName;
|
|
@@ -675,6 +782,8 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
675
782
|
globalVars.companyName = config.email.companyName;
|
|
676
783
|
if (config.email?.supportEmail)
|
|
677
784
|
globalVars.supportEmail = config.email.supportEmail;
|
|
785
|
+
// Merge with sms.templates.globalVariables (sms.templates.globalVariables takes precedence)
|
|
786
|
+
// Filter out non-compatible types from globalVariables
|
|
678
787
|
const smsGlobalVars = {};
|
|
679
788
|
for (const [key, value] of Object.entries(smsTemplates.globalVariables)) {
|
|
680
789
|
if (typeof value === 'string' ||
|
|
@@ -706,7 +815,7 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
706
815
|
'STORAGE_ADAPTER',
|
|
707
816
|
'NAUTH_CONFIG',
|
|
708
817
|
core_2.ClientInfoService,
|
|
709
|
-
{ token: internal_1.AuthAuditService, optional: true },
|
|
818
|
+
{ token: internal_1.AuthAuditService, optional: true }, // Optional - only available when auditLogs.enabled is true
|
|
710
819
|
'NAUTH_LOGGER',
|
|
711
820
|
],
|
|
712
821
|
},
|
|
@@ -714,7 +823,8 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
714
823
|
: []),
|
|
715
824
|
{
|
|
716
825
|
provide: internal_1.PasswordResetService,
|
|
717
|
-
useFactory: (verificationTokenRepo, emailProvider, storageAdapter, nauthConfig, clientInfoService, logger, auditService,
|
|
826
|
+
useFactory: (verificationTokenRepo, emailProvider, storageAdapter, nauthConfig, clientInfoService, logger, auditService, // Optional - only available when auditLogs.enabled is true
|
|
827
|
+
smsProvider) => {
|
|
718
828
|
return new internal_1.PasswordResetService(verificationTokenRepo, emailProvider, storageAdapter, nauthConfig, clientInfoService, logger, auditService, smsProvider);
|
|
719
829
|
},
|
|
720
830
|
inject: [
|
|
@@ -724,8 +834,8 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
724
834
|
'NAUTH_CONFIG',
|
|
725
835
|
core_2.ClientInfoService,
|
|
726
836
|
'NAUTH_LOGGER',
|
|
727
|
-
{ token: internal_1.AuthAuditService, optional: true },
|
|
728
|
-
{ token: 'SMS_PROVIDER', optional: true },
|
|
837
|
+
{ token: internal_1.AuthAuditService, optional: true }, // Optional - only available when auditLogs.enabled is true
|
|
838
|
+
{ token: 'SMS_PROVIDER', optional: true }, // Optional - only available when smsProvider is configured
|
|
729
839
|
],
|
|
730
840
|
},
|
|
731
841
|
{
|
|
@@ -742,6 +852,7 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
742
852
|
},
|
|
743
853
|
inject: ['STORAGE_ADAPTER'],
|
|
744
854
|
},
|
|
855
|
+
// Guards
|
|
745
856
|
auth_guard_1.AuthGuard,
|
|
746
857
|
...(config.tokenDelivery?.method === 'cookies' || config.tokenDelivery?.method === 'hybrid' ? [csrf_guard_1.CsrfGuard] : []),
|
|
747
858
|
],
|
|
@@ -751,9 +862,14 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
751
862
|
internal_1.JwtService,
|
|
752
863
|
internal_1.SessionService,
|
|
753
864
|
internal_1.ChallengeService,
|
|
754
|
-
internal_1.AuthChallengeHelperService,
|
|
755
|
-
internal_1.SocialProviderRegistry,
|
|
865
|
+
internal_1.AuthChallengeHelperService, // Needed by social auth providers
|
|
866
|
+
internal_1.SocialProviderRegistry, // Needed by social auth provider modules for auto-registration
|
|
756
867
|
core_2.ClientInfoService,
|
|
868
|
+
// Audit Services (conditional - only if enabled)
|
|
869
|
+
// Single instance, exported under two tokens:
|
|
870
|
+
// - AuthAuditService (public API) - For consumer apps to fetch audit logs (TypeScript prevents recordEvent)
|
|
871
|
+
// - InternalAuthAuditService - For INTERNAL toolkit packages ONLY (social auth, MFA providers)
|
|
872
|
+
// Consumer apps should inject AuthAuditService, internal packages inject InternalAuthAuditService
|
|
757
873
|
...(config.auditLogs?.enabled !== false ? [core_2.AuthAuditService, internal_1.AuthAuditService] : []),
|
|
758
874
|
auth_guard_1.AuthGuard,
|
|
759
875
|
...(config.tokenDelivery?.method === 'cookies' || config.tokenDelivery?.method === 'hybrid'
|
|
@@ -765,19 +881,42 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
765
881
|
'EMAIL_PROVIDER',
|
|
766
882
|
core_2.EmailVerificationService,
|
|
767
883
|
...(config.smsProvider ? ['SMS_PROVIDER', core_2.PhoneVerificationService] : []),
|
|
768
|
-
core_2.SocialAuthService,
|
|
769
|
-
core_2.MFAService,
|
|
884
|
+
core_2.SocialAuthService, // Always export - providers register themselves when modules are imported
|
|
885
|
+
core_2.MFAService, // Always export - MFA providers register themselves when modules are imported
|
|
886
|
+
// TrustedDeviceService is provided but not exported (used internally by AuthService)
|
|
770
887
|
'NAUTH_LOGGER',
|
|
771
|
-
'NAUTH_CONFIG',
|
|
772
|
-
'SOCIAL_AUTH_STATE_STORE',
|
|
888
|
+
'NAUTH_CONFIG', // Export config so other modules can access it
|
|
889
|
+
'SOCIAL_AUTH_STATE_STORE', // Needed by social auth providers for CSRF protection
|
|
890
|
+
// Repository tokens exported for internal toolkit packages (MFA providers, etc.)
|
|
891
|
+
// WARNING: These are for INTERNAL toolkit packages ONLY, not consumer apps
|
|
892
|
+
// Consumer apps should use service methods (AuthService, MFAService, etc.) instead of direct repository access
|
|
773
893
|
'UserRepository',
|
|
774
894
|
'MFADeviceRepository',
|
|
895
|
+
// Note: TypeOrmModule not exported to prevent consumer apps from accessing entities directly
|
|
896
|
+
// Note: MFA provider services (TOTPMFAProviderService, SMSMFAProviderService, PasskeyMFAProviderService)
|
|
897
|
+
// are in @nauth-toolkit/mfa-* packages and auto-register with MFAService
|
|
898
|
+
// These providers need repository access but are internal toolkit packages, not consumer apps
|
|
899
|
+
// Note: PhoneVerificationService is provided from core when an SMS provider is configured
|
|
775
900
|
],
|
|
776
901
|
};
|
|
777
902
|
}
|
|
903
|
+
/**
|
|
904
|
+
* Validate configuration using Zod schema
|
|
905
|
+
*
|
|
906
|
+
* Validates all configuration sections and cross-dependencies:
|
|
907
|
+
* - Email/phone verification requires respective providers
|
|
908
|
+
* - MFA enforcement modes require specific configurations
|
|
909
|
+
* - Social providers require credentials when enabled
|
|
910
|
+
* - JWT algorithm requires appropriate keys
|
|
911
|
+
* - MaxMind geolocation requires credentials for downloads
|
|
912
|
+
*
|
|
913
|
+
* @param config - Configuration to validate
|
|
914
|
+
* @throws {NAuthException} If validation fails with detailed error messages
|
|
915
|
+
*/
|
|
778
916
|
static validateConfig(config) {
|
|
779
917
|
const result = core_2.authConfigSchema.safeParse(config);
|
|
780
918
|
if (!result.success) {
|
|
919
|
+
// Format Zod errors into readable messages
|
|
781
920
|
const errors = result.error.errors
|
|
782
921
|
.map((err) => {
|
|
783
922
|
const path = err.path.length > 0 ? err.path.join('.') : 'root';
|
|
@@ -787,6 +926,9 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
787
926
|
throw new core_2.NAuthException(core_2.AuthErrorCode.VALIDATION_FAILED, `Configuration validation failed:\n\n${errors}\n\n` +
|
|
788
927
|
'Please check your auth configuration and ensure all required fields are provided.');
|
|
789
928
|
}
|
|
929
|
+
// Configuration is valid - no need to return anything
|
|
930
|
+
// TypeScript types ensure type safety at compile time
|
|
931
|
+
// Zod ensures runtime validation for JavaScript/Express apps
|
|
790
932
|
}
|
|
791
933
|
};
|
|
792
934
|
exports.AuthModule = AuthModule;
|