@nauth-toolkit/core 0.1.0 → 0.1.3

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 (184) hide show
  1. package/LICENSE +90 -0
  2. package/README.md +30 -0
  3. package/package.json +7 -2
  4. package/jest.config.js +0 -15
  5. package/jest.setup.ts +0 -6
  6. package/src/adapters/database-columns.ts +0 -165
  7. package/src/adapters/express.adapter.ts +0 -385
  8. package/src/adapters/fastify.adapter.ts +0 -416
  9. package/src/adapters/index.ts +0 -16
  10. package/src/adapters/storage.factory.ts +0 -143
  11. package/src/bootstrap.ts +0 -374
  12. package/src/dto/auth-challenge.dto.ts +0 -231
  13. package/src/dto/auth-response.dto.ts +0 -253
  14. package/src/dto/challenge-response.dto.ts +0 -234
  15. package/src/dto/change-password-request.dto.ts +0 -50
  16. package/src/dto/change-password-response.dto.ts +0 -29
  17. package/src/dto/change-password.dto.ts +0 -57
  18. package/src/dto/error-response.dto.ts +0 -136
  19. package/src/dto/get-available-methods.dto.ts +0 -55
  20. package/src/dto/get-challenge-data-response.dto.ts +0 -28
  21. package/src/dto/get-challenge-data.dto.ts +0 -69
  22. package/src/dto/get-client-info.dto.ts +0 -104
  23. package/src/dto/get-device-token-response.dto.ts +0 -25
  24. package/src/dto/get-events-by-type.dto.ts +0 -76
  25. package/src/dto/get-ip-address-response.dto.ts +0 -24
  26. package/src/dto/get-mfa-status.dto.ts +0 -94
  27. package/src/dto/get-risk-assessment-history.dto.ts +0 -39
  28. package/src/dto/get-session-id-response.dto.ts +0 -25
  29. package/src/dto/get-setup-data-response.dto.ts +0 -31
  30. package/src/dto/get-setup-data.dto.ts +0 -75
  31. package/src/dto/get-suspicious-activity.dto.ts +0 -42
  32. package/src/dto/get-user-agent-response.dto.ts +0 -23
  33. package/src/dto/get-user-auth-history.dto.ts +0 -95
  34. package/src/dto/get-user-by-email.dto.ts +0 -61
  35. package/src/dto/get-user-by-id.dto.ts +0 -46
  36. package/src/dto/get-user-devices.dto.ts +0 -53
  37. package/src/dto/get-user-response.dto.ts +0 -17
  38. package/src/dto/has-provider.dto.ts +0 -56
  39. package/src/dto/index.ts +0 -57
  40. package/src/dto/is-trusted-device-response.dto.ts +0 -34
  41. package/src/dto/list-providers-response.dto.ts +0 -23
  42. package/src/dto/login.dto.ts +0 -95
  43. package/src/dto/logout-all-response.dto.ts +0 -24
  44. package/src/dto/logout-all.dto.ts +0 -65
  45. package/src/dto/logout-response.dto.ts +0 -25
  46. package/src/dto/logout.dto.ts +0 -64
  47. package/src/dto/refresh-token.dto.ts +0 -36
  48. package/src/dto/remove-devices.dto.ts +0 -85
  49. package/src/dto/resend-code-response.dto.ts +0 -32
  50. package/src/dto/resend-code.dto.ts +0 -51
  51. package/src/dto/reset-password.dto.ts +0 -115
  52. package/src/dto/respond-challenge.dto.ts +0 -272
  53. package/src/dto/set-mfa-exemption.dto.ts +0 -112
  54. package/src/dto/set-must-change-password-response.dto.ts +0 -27
  55. package/src/dto/set-must-change-password.dto.ts +0 -46
  56. package/src/dto/set-preferred-method.dto.ts +0 -80
  57. package/src/dto/setup-mfa.dto.ts +0 -98
  58. package/src/dto/signup.dto.ts +0 -174
  59. package/src/dto/social-auth.dto.ts +0 -422
  60. package/src/dto/trust-device-response.dto.ts +0 -30
  61. package/src/dto/trust-device.dto.ts +0 -9
  62. package/src/dto/update-user-attributes-request.dto.ts +0 -51
  63. package/src/dto/user-response.dto.ts +0 -138
  64. package/src/dto/user-update.dto.ts +0 -222
  65. package/src/dto/verify-email.dto.ts +0 -313
  66. package/src/dto/verify-mfa-code.dto.ts +0 -103
  67. package/src/dto/verify-phone-by-sub.dto.ts +0 -78
  68. package/src/dto/verify-phone.dto.ts +0 -245
  69. package/src/entities/auth-audit.entity.ts +0 -232
  70. package/src/entities/challenge-session.entity.ts +0 -116
  71. package/src/entities/index.ts +0 -29
  72. package/src/entities/login-attempt.entity.ts +0 -64
  73. package/src/entities/mfa-device.entity.ts +0 -151
  74. package/src/entities/rate-limit.entity.ts +0 -44
  75. package/src/entities/session.entity.ts +0 -180
  76. package/src/entities/social-account.entity.ts +0 -96
  77. package/src/entities/storage-lock.entity.ts +0 -39
  78. package/src/entities/trusted-device.entity.ts +0 -112
  79. package/src/entities/user.entity.ts +0 -243
  80. package/src/entities/verification-token.entity.ts +0 -141
  81. package/src/enums/auth-audit-event-type.enum.ts +0 -360
  82. package/src/enums/error-codes.enum.ts +0 -420
  83. package/src/enums/mfa-method.enum.ts +0 -97
  84. package/src/enums/risk-factor.enum.ts +0 -111
  85. package/src/exceptions/nauth.exception.ts +0 -231
  86. package/src/handlers/auth.handler.ts +0 -260
  87. package/src/handlers/client-info.handler.ts +0 -101
  88. package/src/handlers/csrf.handler.ts +0 -156
  89. package/src/handlers/token-delivery.handler.ts +0 -118
  90. package/src/index.ts +0 -118
  91. package/src/interfaces/client-info.interface.ts +0 -85
  92. package/src/interfaces/config.interface.ts +0 -2135
  93. package/src/interfaces/entities.interface.ts +0 -226
  94. package/src/interfaces/index.ts +0 -15
  95. package/src/interfaces/logger.interface.ts +0 -283
  96. package/src/interfaces/mfa-provider.interface.ts +0 -154
  97. package/src/interfaces/oauth.interface.ts +0 -148
  98. package/src/interfaces/provider.interface.ts +0 -47
  99. package/src/interfaces/social-auth-provider.interface.ts +0 -131
  100. package/src/interfaces/storage-adapter.interface.ts +0 -82
  101. package/src/interfaces/template.interface.ts +0 -510
  102. package/src/interfaces/token-verifier.interface.ts +0 -110
  103. package/src/internal.ts +0 -178
  104. package/src/platform/interfaces.ts +0 -299
  105. package/src/schemas/auth-config.schema.ts +0 -646
  106. package/src/services/adaptive-mfa-decision.service.spec.ts +0 -1058
  107. package/src/services/adaptive-mfa-decision.service.ts +0 -457
  108. package/src/services/auth-audit.service.spec.ts +0 -675
  109. package/src/services/auth-audit.service.ts +0 -558
  110. package/src/services/auth-challenge-helper.service.spec.ts +0 -3227
  111. package/src/services/auth-challenge-helper.service.ts +0 -825
  112. package/src/services/auth-flow-context-builder.service.ts +0 -520
  113. package/src/services/auth-flow-rules.ts +0 -202
  114. package/src/services/auth-flow-state-definitions.ts +0 -190
  115. package/src/services/auth-flow-state-machine.service.ts +0 -207
  116. package/src/services/auth-flow-state-machine.types.ts +0 -316
  117. package/src/services/auth.service.spec.ts +0 -4195
  118. package/src/services/auth.service.ts +0 -3727
  119. package/src/services/challenge.service.spec.ts +0 -1363
  120. package/src/services/challenge.service.ts +0 -696
  121. package/src/services/client-info.service.spec.ts +0 -572
  122. package/src/services/client-info.service.ts +0 -374
  123. package/src/services/csrf.service.ts +0 -54
  124. package/src/services/email-verification.service.spec.ts +0 -1229
  125. package/src/services/email-verification.service.ts +0 -578
  126. package/src/services/geo-location.service.spec.ts +0 -603
  127. package/src/services/geo-location.service.ts +0 -599
  128. package/src/services/index.ts +0 -13
  129. package/src/services/jwt.service.spec.ts +0 -882
  130. package/src/services/jwt.service.ts +0 -621
  131. package/src/services/mfa-base.service.spec.ts +0 -246
  132. package/src/services/mfa-base.service.ts +0 -611
  133. package/src/services/mfa.service.spec.ts +0 -693
  134. package/src/services/mfa.service.ts +0 -960
  135. package/src/services/password.service.spec.ts +0 -166
  136. package/src/services/password.service.ts +0 -309
  137. package/src/services/phone-verification.service.spec.ts +0 -1120
  138. package/src/services/phone-verification.service.ts +0 -751
  139. package/src/services/risk-detection.service.spec.ts +0 -1292
  140. package/src/services/risk-detection.service.ts +0 -1012
  141. package/src/services/risk-scoring.service.spec.ts +0 -204
  142. package/src/services/risk-scoring.service.ts +0 -131
  143. package/src/services/session.service.spec.ts +0 -1293
  144. package/src/services/session.service.ts +0 -803
  145. package/src/services/social-account.service.spec.ts +0 -725
  146. package/src/services/social-auth-base.service.spec.ts +0 -418
  147. package/src/services/social-auth-base.service.ts +0 -581
  148. package/src/services/social-auth.service.spec.ts +0 -238
  149. package/src/services/social-auth.service.ts +0 -436
  150. package/src/services/social-provider-registry.service.spec.ts +0 -238
  151. package/src/services/social-provider-registry.service.ts +0 -122
  152. package/src/services/trusted-device.service.spec.ts +0 -505
  153. package/src/services/trusted-device.service.ts +0 -339
  154. package/src/storage/account-lockout-storage.service.spec.ts +0 -310
  155. package/src/storage/account-lockout-storage.service.ts +0 -89
  156. package/src/storage/index.ts +0 -3
  157. package/src/storage/memory-storage.adapter.ts +0 -443
  158. package/src/storage/rate-limit-storage.service.spec.ts +0 -247
  159. package/src/storage/rate-limit-storage.service.ts +0 -38
  160. package/src/templates/html-template.engine.spec.ts +0 -161
  161. package/src/templates/html-template.engine.ts +0 -688
  162. package/src/templates/index.ts +0 -7
  163. package/src/utils/common-passwords.spec.ts +0 -230
  164. package/src/utils/common-passwords.ts +0 -170
  165. package/src/utils/context-storage.ts +0 -188
  166. package/src/utils/cookie-names.util.ts +0 -67
  167. package/src/utils/cookies.util.ts +0 -94
  168. package/src/utils/index.ts +0 -12
  169. package/src/utils/ip-extractor.spec.ts +0 -330
  170. package/src/utils/ip-extractor.ts +0 -220
  171. package/src/utils/nauth-logger.spec.ts +0 -388
  172. package/src/utils/nauth-logger.ts +0 -215
  173. package/src/utils/pii-redactor.spec.ts +0 -130
  174. package/src/utils/pii-redactor.ts +0 -288
  175. package/src/utils/setup/get-repositories.ts +0 -140
  176. package/src/utils/setup/init-services.ts +0 -422
  177. package/src/utils/setup/init-social.ts +0 -189
  178. package/src/utils/setup/init-storage.ts +0 -94
  179. package/src/utils/setup/register-mfa.ts +0 -165
  180. package/src/utils/setup/run-nauth-migrations.ts +0 -61
  181. package/src/utils/token-delivery-policy.ts +0 -38
  182. package/src/validators/template.validator.ts +0 -219
  183. package/tsconfig.json +0 -37
  184. package/tsconfig.lint.json +0 -6
@@ -1,646 +0,0 @@
1
- /**
2
- * Zod Schema Validation for NAuth Configuration
3
- *
4
- * Provides runtime validation for auth configuration with comprehensive
5
- * cross-dependency checks. Ensures configuration is valid before module initialization.
6
- *
7
- * This schema validates:
8
- * - Required fields
9
- * - Type correctness
10
- * - Cross-dependencies (e.g., email config requires emailProvider)
11
- * - Algorithm-specific requirements (JWT symmetric vs asymmetric)
12
- * - MFA enforcement modes and their requirements
13
- * - Social provider requirements
14
- * - GeoLocation MaxMind credentials
15
- *
16
- * @example
17
- * ```typescript
18
- * import { authConfigSchema } from '@nauth-toolkit/core';
19
- *
20
- * const result = authConfigSchema.safeParse(config);
21
- * if (!result.success) {
22
- * console.error('Config validation failed:', result.error.errors);
23
- * }
24
- * ```
25
- */
26
-
27
- import { z } from 'zod';
28
-
29
- // ============================================================================
30
- // JWT Configuration Schemas
31
- // ============================================================================
32
-
33
- /**
34
- * Access token configuration schema
35
- */
36
- const accessTokenConfigSchema = z.object({
37
- secret: z.string().optional(),
38
- privateKey: z.string().optional(),
39
- publicKey: z.string().optional(),
40
- expiresIn: z.union([z.string(), z.number()]),
41
- });
42
-
43
- /**
44
- * Refresh token configuration schema
45
- */
46
- const refreshTokenConfigSchema = z.object({
47
- secret: z.string().min(32, 'Refresh token secret must be at least 32 characters (256 bits) for security'),
48
- expiresIn: z.union([z.string(), z.number()]),
49
- rotation: z.boolean().optional(),
50
- reuseDetection: z.boolean().optional(),
51
- });
52
-
53
- /**
54
- * JWT configuration schema
55
- */
56
- export const jwtConfigSchema = z
57
- .object({
58
- algorithm: z.enum(['HS256', 'HS384', 'HS512', 'RS256', 'RS384', 'RS512']).optional(),
59
- accessToken: accessTokenConfigSchema,
60
- refreshToken: refreshTokenConfigSchema,
61
- issuer: z.string().optional(),
62
- audience: z.union([z.string(), z.array(z.string())]).optional(),
63
- })
64
- .superRefine((data, ctx) => {
65
- const algorithm = data.algorithm || 'HS256';
66
- const symmetricAlgorithms = ['HS256', 'HS384', 'HS512'];
67
- const asymmetricAlgorithms = ['RS256', 'RS384', 'RS512'];
68
-
69
- if (symmetricAlgorithms.includes(algorithm)) {
70
- if (!data.accessToken.secret) {
71
- ctx.addIssue({
72
- code: z.ZodIssueCode.custom,
73
- message: `jwt.accessToken.secret is required for symmetric algorithm ${algorithm}`,
74
- path: ['accessToken', 'secret'],
75
- });
76
- } else if (data.accessToken.secret.length < 32) {
77
- ctx.addIssue({
78
- code: z.ZodIssueCode.custom,
79
- message: 'JWT access token secret must be at least 32 characters (256 bits) for security',
80
- path: ['accessToken', 'secret'],
81
- });
82
- }
83
- } else if (asymmetricAlgorithms.includes(algorithm)) {
84
- if (!data.accessToken.privateKey) {
85
- ctx.addIssue({
86
- code: z.ZodIssueCode.custom,
87
- message: `jwt.accessToken.privateKey is required for asymmetric algorithm ${algorithm}`,
88
- path: ['accessToken', 'privateKey'],
89
- });
90
- }
91
- if (!data.accessToken.publicKey) {
92
- ctx.addIssue({
93
- code: z.ZodIssueCode.custom,
94
- message: `jwt.accessToken.publicKey is required for asymmetric algorithm ${algorithm}`,
95
- path: ['accessToken', 'publicKey'],
96
- });
97
- }
98
- }
99
- });
100
-
101
- // ============================================================================
102
- // Signup Configuration Schema
103
- // ============================================================================
104
-
105
- export const signupConfigSchema = z.object({
106
- enabled: z.boolean().optional(),
107
- verificationMethod: z.enum(['none', 'email', 'phone', 'both']).optional(),
108
- allowDuplicatePhones: z.boolean().optional(),
109
- emailVerification: z
110
- .object({
111
- expiresIn: z.number().optional(),
112
- resendDelay: z.number().optional(),
113
- rateLimitMax: z.number().optional(),
114
- rateLimitWindow: z.number().optional(),
115
- maxAttemptsPerUser: z.number().optional(),
116
- maxAttemptsPerIP: z.number().optional(),
117
- attemptWindow: z.number().optional(),
118
- })
119
- .optional(),
120
- phoneVerification: z
121
- .object({
122
- codeLength: z.number().optional(),
123
- expiresIn: z.number().optional(),
124
- maxAttempts: z.number().optional(),
125
- resendDelay: z.number().optional(),
126
- rateLimitMax: z.number().optional(),
127
- rateLimitWindow: z.number().optional(),
128
- maxAttemptsPerUser: z.number().optional(),
129
- maxAttemptsPerIP: z.number().optional(),
130
- attemptWindow: z.number().optional(),
131
- })
132
- .optional(),
133
- });
134
-
135
- // ============================================================================
136
- // Login Configuration Schema
137
- // ============================================================================
138
-
139
- export const loginConfigSchema = z.object({
140
- identifierType: z.enum(['email', 'username', 'phone', 'email_or_username']).optional(),
141
- });
142
-
143
- // ============================================================================
144
- // Password Configuration Schema
145
- // ============================================================================
146
-
147
- export const passwordConfigSchema = z.object({
148
- minLength: z.number().optional(),
149
- maxLength: z.number().optional(),
150
- requireUppercase: z.boolean().optional(),
151
- requireLowercase: z.boolean().optional(),
152
- requireNumbers: z.boolean().optional(),
153
- requireSpecialChars: z.boolean().optional(),
154
- specialChars: z.string().optional(),
155
- preventCommon: z.boolean().optional(),
156
- preventUserInfo: z.boolean().optional(),
157
- historyCount: z.number().optional(),
158
- expiryDays: z.number().optional(),
159
- });
160
-
161
- // ============================================================================
162
- // Lockout Configuration Schema
163
- // ============================================================================
164
-
165
- export const lockoutConfigSchema = z.object({
166
- enabled: z.boolean().optional(),
167
- maxAttempts: z.number().optional(),
168
- duration: z.number().optional(),
169
- resetOnSuccess: z.boolean().optional(),
170
- });
171
-
172
- // ============================================================================
173
- // Session Configuration Schema
174
- // ============================================================================
175
-
176
- export const sessionConfigSchema = z.object({
177
- maxConcurrent: z.number().optional(),
178
- disallowMultipleSessions: z.boolean().optional(),
179
- maxLifetime: z.union([z.string(), z.number()]).optional(),
180
- });
181
-
182
- // ============================================================================
183
- // Security Configuration Schema
184
- // ============================================================================
185
-
186
- export const securityConfigSchema = z.object({
187
- csrf: z
188
- .object({
189
- cookieName: z.string().optional(),
190
- headerName: z.string().optional(),
191
- tokenLength: z.number().optional(),
192
- excludedPaths: z.array(z.string()).optional(),
193
- cookieOptions: z
194
- .object({
195
- // httpOnly is always false (hardcoded) - CSRF token must be readable by JavaScript
196
- secure: z.boolean().optional(),
197
- sameSite: z.enum(['strict', 'lax', 'none']).optional(),
198
- domain: z.string().optional(),
199
- path: z.string().optional(),
200
- })
201
- .optional(),
202
- })
203
- .optional(),
204
- });
205
-
206
- // ============================================================================
207
- // Lifecycle Hooks Schema
208
- // ============================================================================
209
-
210
- // Note: Functions cannot be validated by Zod, so we use z.any() for hooks
211
- export const lifecycleHooksSchema = z.object({
212
- beforeSignup: z.any().optional(),
213
- afterSignup: z.any().optional(),
214
- beforeLogin: z.any().optional(),
215
- afterLogin: z.any().optional(),
216
- afterLoginFailed: z.any().optional(),
217
- beforePasswordChange: z.any().optional(),
218
- afterPasswordChange: z.any().optional(),
219
- beforeAccountLock: z.any().optional(),
220
- afterAccountLock: z.any().optional(),
221
- onAdaptiveMFATriggered: z.any().optional(),
222
- onSignInBlocked: z.any().optional(),
223
- });
224
-
225
- // ============================================================================
226
- // Email Configuration Schema
227
- // ============================================================================
228
-
229
- /**
230
- * Custom template definition schema
231
- *
232
- * Validates template definition structure:
233
- * - Must have either htmlPath OR html content (not both)
234
- * - Can have either textPath OR text content (not both)
235
- * - Subject is optional (can be extracted from frontmatter)
236
- */
237
- const customTemplateDefinitionSchema = z
238
- .object({
239
- htmlPath: z.string().optional(),
240
- html: z.string().optional(),
241
- textPath: z.string().optional(),
242
- text: z.string().optional(),
243
- subject: z.string().optional(),
244
- })
245
- .refine((data) => !!(data.htmlPath || data.html), {
246
- message: 'Must provide either "htmlPath" or "html" content',
247
- })
248
- .refine((data) => !(data.htmlPath && data.html), {
249
- message: 'Cannot provide both "htmlPath" and "html". Use one or the other.',
250
- })
251
- .refine((data) => !(data.textPath && data.text), {
252
- message: 'Cannot provide both "textPath" and "text". Use one or the other.',
253
- });
254
-
255
- /**
256
- * Template configuration schema
257
- *
258
- * Validates template configuration:
259
- * - Global variables for branding
260
- * - Custom templates with required parameters
261
- */
262
- const templateConfigSchema = z.object({
263
- engine: z.any().optional(), // TemplateEngine instance - runtime validation
264
- globalVariables: z.record(z.union([z.string(), z.number(), z.boolean()])).optional(),
265
- customTemplates: z.record(customTemplateDefinitionSchema).optional(),
266
- });
267
-
268
- /**
269
- * Email configuration schema
270
- */
271
- export const emailConfigSchema = z.object({
272
- appName: z.string().optional(),
273
- companyName: z.string().optional(),
274
- logoUrl: z.string().optional(),
275
- supportEmail: z.string().email().optional(),
276
- dashboardUrl: z.string().url().optional(),
277
- brandColor: z.string().optional(),
278
- footerDisclaimer: z.string().optional(),
279
- templates: templateConfigSchema.optional(),
280
- });
281
-
282
- // ============================================================================
283
- // Phone Configuration Schema
284
- // ============================================================================
285
-
286
- export const phoneConfigSchema = z.object({
287
- // verification section removed - moved to signup.phoneVerification
288
- // Kept empty for backwards compatibility
289
- });
290
-
291
- // ============================================================================
292
- // Social Provider Configuration Schema
293
- // ============================================================================
294
-
295
- export const socialProviderConfigSchema = z.object({
296
- enabled: z.boolean().optional(),
297
- clientId: z.union([z.string(), z.array(z.string())]).optional(),
298
- clientSecret: z.string().optional(),
299
- callbackUrl: z.string().optional(),
300
- scopes: z.array(z.string()).optional(),
301
- autoLink: z.boolean().optional(),
302
- allowSignup: z.boolean().optional(),
303
- });
304
-
305
- export const socialConfigSchema = z.object({
306
- google: socialProviderConfigSchema.optional(),
307
- apple: socialProviderConfigSchema.optional(),
308
- facebook: socialProviderConfigSchema.optional(),
309
- });
310
-
311
- // ============================================================================
312
- // MFA Configuration Schemas
313
- // ============================================================================
314
-
315
- export const totpConfigSchema = z.object({
316
- window: z.number().optional(),
317
- stepSeconds: z.number().optional(),
318
- digits: z.number().optional(),
319
- algorithm: z.enum(['sha1', 'sha256', 'sha512']).optional(),
320
- });
321
-
322
- export const passkeyConfigSchema = z.object({
323
- rpName: z.string(),
324
- rpId: z.string(),
325
- origin: z.union([z.string(), z.array(z.string())]).optional(),
326
- timeout: z.number().optional(),
327
- userVerification: z.enum(['required', 'preferred', 'discouraged']).optional(),
328
- authenticatorAttachment: z.enum(['platform', 'cross-platform']).optional(),
329
- });
330
-
331
- export const backupCodesConfigSchema = z.object({
332
- enabled: z.boolean().optional(),
333
- codeCount: z.number().optional(),
334
- codeLength: z.number().optional(),
335
- });
336
-
337
- export const riskLevelConfigSchema = z.object({
338
- maxScore: z.number(),
339
- action: z.enum(['allow', 'require_mfa', 'block_signin']).optional(),
340
- notifyUser: z.boolean().optional(),
341
- });
342
-
343
- export const adaptiveMFAConfigSchema = z.object({
344
- triggers: z
345
- .array(z.enum(['new_device', 'new_ip', 'new_country', 'impossible_travel', 'suspicious_activity']))
346
- .optional(),
347
- riskThreshold: z.number().optional(), // Deprecated
348
- riskWeights: z.record(z.string(), z.number()).optional(),
349
- riskLevels: z
350
- .object({
351
- low: riskLevelConfigSchema.optional(),
352
- medium: riskLevelConfigSchema.optional(),
353
- high: riskLevelConfigSchema.optional(),
354
- })
355
- .optional(),
356
- blockedSignIn: z
357
- .object({
358
- blockDuration: z.number().optional(),
359
- message: z.string().optional(),
360
- errorCode: z.string().optional(),
361
- })
362
- .optional(),
363
- maxTravelSpeed: z.number().optional(),
364
- countryChangeThreshold: z.number().optional(),
365
- suspiciousActivityWindow: z.number().optional(),
366
- });
367
-
368
- export const mfaConfigSchema = z.object({
369
- enabled: z.boolean().optional(),
370
- enforcement: z.enum(['OPTIONAL', 'REQUIRED', 'ADAPTIVE']).optional(),
371
- gracePeriod: z.number().optional(),
372
- allowedMethods: z.array(z.enum(['totp', 'sms', 'email', 'passkey'])).optional(),
373
- requireForSocialLogin: z.boolean().optional(),
374
- rememberDevices: z.enum(['always', 'user_opt_in', 'never']).optional(),
375
- rememberDeviceDays: z.number().optional(),
376
- bypassMFAForTrustedDevices: z.boolean().optional(),
377
- issuer: z.string().optional(),
378
- totp: totpConfigSchema.optional(),
379
- passkey: passkeyConfigSchema.optional(),
380
- backup: backupCodesConfigSchema.optional(),
381
- adaptive: adaptiveMFAConfigSchema.optional(),
382
- });
383
-
384
- // ============================================================================
385
- // Token Delivery Configuration Schema
386
- // ============================================================================
387
-
388
- export const tokenDeliveryConfigSchema = z.object({
389
- method: z.enum(['json', 'cookies', 'hybrid']).optional(),
390
- cookieNamePrefix: z.string().optional(), // Prefix for all cookie names (default: 'nauth_')
391
- cookieOptions: z
392
- .object({
393
- secure: z.boolean().optional(),
394
- sameSite: z.enum(['strict', 'lax', 'none']).optional(),
395
- path: z.string().optional(),
396
- domain: z.string().optional(),
397
- })
398
- .optional(),
399
- hybridPolicy: z
400
- .object({
401
- webOrigins: z.array(z.string()).optional(),
402
- nativeOrigins: z.array(z.string()).optional(),
403
- })
404
- .optional(),
405
- });
406
-
407
- // ============================================================================
408
- // Challenge Configuration Schema
409
- // ============================================================================
410
-
411
- export const challengeConfigSchema = z.object({
412
- maxAttempts: z.number().int().positive().optional(),
413
- });
414
-
415
- // ============================================================================
416
- // GeoLocation Configuration Schema
417
- // ============================================================================
418
-
419
- export const geoLocationConfigSchema = z.object({
420
- maxMind: z
421
- .object({
422
- dbPath: z.string().optional(),
423
- skipDownloads: z.boolean().optional(),
424
- licenseKey: z.string().optional(),
425
- accountId: z.number().optional(),
426
- autoDownloadOnStartup: z.boolean().optional(),
427
- editions: z.array(z.string()).optional(),
428
- })
429
- .optional(),
430
- });
431
-
432
- // ============================================================================
433
- // Root Configuration Schema with Cross-Dependency Validation
434
- // ============================================================================
435
-
436
- /**
437
- * Root authentication configuration schema
438
- *
439
- * Validates all configuration sections and enforces cross-dependencies:
440
- * - Email/phone verification requires respective providers
441
- * - MFA enforcement modes require specific configurations
442
- * - Social providers require credentials when enabled
443
- * - JWT algorithm requires appropriate keys
444
- * - MaxMind geolocation requires credentials for downloads
445
- */
446
- export const authConfigSchema = z
447
- .object({
448
- tablePrefix: z.string().optional(),
449
- jwt: jwtConfigSchema,
450
- signup: signupConfigSchema.optional(),
451
- login: loginConfigSchema.optional(),
452
- password: passwordConfigSchema.optional(),
453
- lockout: lockoutConfigSchema.optional(),
454
- session: sessionConfigSchema.optional(),
455
- security: securityConfigSchema.optional(),
456
- hooks: lifecycleHooksSchema.optional(),
457
- auditLogs: z
458
- .object({
459
- enabled: z.boolean().optional(),
460
- fireAndForget: z.boolean().optional(),
461
- })
462
- .optional(),
463
- emailProvider: z.any().optional(), // Runtime instance - cannot validate type
464
- email: emailConfigSchema.optional(),
465
- smsProvider: z.any().optional(), // Runtime instance - cannot validate type
466
- phone: phoneConfigSchema.optional(),
467
- storageAdapter: z.any().optional(), // Runtime instance - cannot validate type
468
- social: socialConfigSchema.optional(),
469
- mfa: mfaConfigSchema.optional(),
470
- logger: z.any().optional(), // LoggerService or NAuthLoggerConfig - runtime instance
471
- tokenDelivery: tokenDeliveryConfigSchema.optional(),
472
- challenge: challengeConfigSchema.optional(),
473
- geoLocation: geoLocationConfigSchema.optional(),
474
- })
475
- .superRefine((data, ctx) => {
476
- // ============================================================================
477
- // 1. Email Verification Dependencies
478
- // ============================================================================
479
- const verificationMethod = data.signup?.verificationMethod || 'email';
480
- if ((verificationMethod === 'email' || verificationMethod === 'both') && !data.emailProvider) {
481
- ctx.addIssue({
482
- code: z.ZodIssueCode.custom,
483
- message: 'emailProvider is required when email verification is enabled',
484
- path: ['emailProvider'],
485
- });
486
- }
487
-
488
- // ============================================================================
489
- // 2. Phone Verification Dependencies
490
- // ============================================================================
491
- if ((verificationMethod === 'phone' || verificationMethod === 'both') && !data.smsProvider) {
492
- ctx.addIssue({
493
- code: z.ZodIssueCode.custom,
494
- message: 'smsProvider is required when phone verification is enabled',
495
- path: ['smsProvider'],
496
- });
497
- }
498
-
499
- // ============================================================================
500
- // 3. Email Config Requires Provider
501
- // ============================================================================
502
- if (data.email && !data.emailProvider) {
503
- ctx.addIssue({
504
- code: z.ZodIssueCode.custom,
505
- message: 'emailProvider is required when email configuration is provided',
506
- path: ['emailProvider'],
507
- });
508
- }
509
-
510
- // ============================================================================
511
- // 4. Phone Config Requires Provider
512
- // ============================================================================
513
- if (data.phone && !data.smsProvider) {
514
- ctx.addIssue({
515
- code: z.ZodIssueCode.custom,
516
- message: 'smsProvider is required when phone configuration is provided',
517
- path: ['smsProvider'],
518
- });
519
- }
520
-
521
- // ============================================================================
522
- // 5. MFA ADAPTIVE Enforcement Validation
523
- // ============================================================================
524
- if (data.mfa?.enforcement === 'ADAPTIVE') {
525
- if (!data.mfa.enabled) {
526
- ctx.addIssue({
527
- code: z.ZodIssueCode.custom,
528
- message: 'mfa.enabled must be true when enforcement is ADAPTIVE',
529
- path: ['mfa', 'enabled'],
530
- });
531
- }
532
- if (!data.mfa.adaptive) {
533
- ctx.addIssue({
534
- code: z.ZodIssueCode.custom,
535
- message: 'mfa.adaptive configuration is required when enforcement is ADAPTIVE',
536
- path: ['mfa', 'adaptive'],
537
- });
538
- }
539
- }
540
-
541
- // ============================================================================
542
- // 6. MFA REQUIRED Enforcement Validation
543
- // ============================================================================
544
- if (data.mfa?.enforcement === 'REQUIRED' && data.mfa.gracePeriod === undefined) {
545
- // Grace period is optional but recommended - just warn, don't fail
546
- // This is handled at runtime level, not validation level
547
- }
548
-
549
- // ============================================================================
550
- // 7. MFA SMS Method Requires SMS Provider
551
- // ============================================================================
552
- if (data.mfa?.allowedMethods?.includes('sms') && !data.smsProvider) {
553
- ctx.addIssue({
554
- code: z.ZodIssueCode.custom,
555
- message: 'smsProvider is required when MFA SMS method is enabled',
556
- path: ['smsProvider'],
557
- });
558
- }
559
-
560
- // ============================================================================
561
- // 8. MFA Email Method Requires Email Provider
562
- // ============================================================================
563
- if (data.mfa?.allowedMethods?.includes('email') && !data.emailProvider) {
564
- ctx.addIssue({
565
- code: z.ZodIssueCode.custom,
566
- message: 'emailProvider is required when MFA Email method is enabled',
567
- path: ['emailProvider'],
568
- });
569
- }
570
-
571
- // ============================================================================
572
- // 9. MFA Passkey Requires Passkey Config
573
- // ============================================================================
574
- if (data.mfa?.allowedMethods?.includes('passkey') && !data.mfa.passkey) {
575
- ctx.addIssue({
576
- code: z.ZodIssueCode.custom,
577
- message: 'mfa.passkey configuration is required when passkey method is enabled',
578
- path: ['mfa', 'passkey'],
579
- });
580
- }
581
-
582
- // ============================================================================
583
- // 10. MFA Remember Devices Validation
584
- // ============================================================================
585
- if (
586
- data.mfa?.rememberDevices &&
587
- data.mfa.rememberDevices !== 'never' &&
588
- data.mfa.rememberDeviceDays === undefined
589
- ) {
590
- // Remember device days has default - this is informational only
591
- // Validation passes, but we could add a warning if needed
592
- }
593
-
594
- // ============================================================================
595
- // 11. Social Provider Validation
596
- // ============================================================================
597
- ['google', 'apple', 'facebook'].forEach((provider) => {
598
- const providerConfig = data.social?.[provider as 'google' | 'apple' | 'facebook'];
599
- if (providerConfig?.enabled) {
600
- if (!providerConfig.clientId) {
601
- ctx.addIssue({
602
- code: z.ZodIssueCode.custom,
603
- message: `clientId is required when ${provider} provider is enabled`,
604
- path: ['social', provider, 'clientId'],
605
- });
606
- }
607
- if (!providerConfig.clientSecret) {
608
- ctx.addIssue({
609
- code: z.ZodIssueCode.custom,
610
- message: `clientSecret is required when ${provider} provider is enabled`,
611
- path: ['social', provider, 'clientSecret'],
612
- });
613
- }
614
- }
615
- });
616
-
617
- // ============================================================================
618
- // 12. MaxMind GeoLocation Validation
619
- // ============================================================================
620
- if (data.geoLocation?.maxMind) {
621
- const maxMind = data.geoLocation.maxMind;
622
- if (!maxMind.skipDownloads && maxMind.autoDownloadOnStartup) {
623
- if (!maxMind.licenseKey || !maxMind.accountId) {
624
- ctx.addIssue({
625
- code: z.ZodIssueCode.custom,
626
- message: 'MaxMind licenseKey and accountId are required when autoDownloadOnStartup is enabled',
627
- path: ['geoLocation', 'maxMind', 'licenseKey'],
628
- });
629
- }
630
- }
631
- }
632
-
633
- // ============================================================================
634
- // 13. Token Delivery Hybrid Policy Validation
635
- // ============================================================================
636
- if (data.tokenDelivery?.method === 'hybrid' && !data.tokenDelivery.hybridPolicy) {
637
- // Hybrid policy is optional but recommended - validation passes
638
- // Runtime will handle defaults
639
- }
640
- });
641
-
642
- /**
643
- * Type inference from Zod schema
644
- * This provides type safety while Zod provides runtime validation
645
- */
646
- export type NAuthConfig = z.infer<typeof authConfigSchema>;