@nauth-toolkit/core 0.1.0 → 0.1.5

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 +9 -0
  3. package/package.json +8 -3
  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,231 +0,0 @@
1
- import { AuthErrorCode } from '../enums/error-codes.enum';
2
-
3
- /**
4
- * Custom exception for nauth-toolkit
5
- *
6
- * **Framework-Agnostic Design:**
7
- * This exception extends standard `Error`, not `HttpException`, making it
8
- * usable in any context:
9
- * - HTTP APIs (REST, NestJS)
10
- * - WebSocket connections
11
- * - GraphQL resolvers
12
- * - gRPC services
13
- * - Message queue workers
14
- * - CLI tools
15
- * - Standalone services
16
- *
17
- * **Consumer Responsibility:**
18
- * The consumer application decides how to map these domain exceptions
19
- * to their transport layer (HTTP status codes, WebSocket events, etc.)
20
- *
21
- * **Structured Error Data:**
22
- * Provides error code, message, and optional metadata. Consumer can
23
- * transform this into any response format needed.
24
- *
25
- * @example
26
- * ```typescript
27
- * // Throw domain exception
28
- * throw new NAuthException(
29
- * AuthErrorCode.RATE_LIMIT_SMS,
30
- * 'Too many verification SMS sent',
31
- * { retryAfter: 3600, maxAttempts: 3 }
32
- * );
33
- *
34
- * // Consumer maps to HTTP (if using HTTP)
35
- * catch (error) {
36
- * if (error instanceof NAuthException) {
37
- * const statusCode = this.mapErrorCodeToHttpStatus(error.code);
38
- * return res.status(statusCode).json({
39
- * code: error.code,
40
- * message: error.message,
41
- * details: error.details,
42
- * timestamp: new Date().toISOString()
43
- * });
44
- * }
45
- * }
46
- *
47
- * // Or map to WebSocket
48
- * catch (error) {
49
- * if (error instanceof NAuthException) {
50
- * socket.emit('error', {
51
- * code: error.code,
52
- * message: error.message,
53
- * details: error.details
54
- * });
55
- * }
56
- * }
57
- * ```
58
- */
59
- export class NAuthException extends Error {
60
- /**
61
- * Error code for programmatic handling
62
- */
63
- public readonly code: AuthErrorCode;
64
-
65
- /**
66
- * Additional error details/metadata
67
- */
68
- public readonly details?: Record<string, unknown>;
69
-
70
- /**
71
- * Timestamp when error was created
72
- */
73
- public readonly timestamp: string;
74
-
75
- /**
76
- * Create a new NAuthException
77
- *
78
- * @param code - Error code from AuthErrorCode enum
79
- * @param message - Human-readable error message
80
- * @param details - Optional metadata (retryAfter, validation errors, etc.)
81
- *
82
- * @example
83
- * ```typescript
84
- * throw new NAuthException(
85
- * AuthErrorCode.INVALID_CREDENTIALS,
86
- * 'Invalid email or password'
87
- * );
88
- *
89
- * throw new NAuthException(
90
- * AuthErrorCode.RATE_LIMIT_SMS,
91
- * 'Too many SMS sent',
92
- * { retryAfter: 3600, currentCount: 4 }
93
- * );
94
- * ```
95
- */
96
- constructor(code: AuthErrorCode, message: string, details?: Record<string, unknown>) {
97
- super(message);
98
-
99
- this.code = code;
100
- this.details = details;
101
- this.timestamp = new Date().toISOString();
102
- this.name = 'NAuthException';
103
-
104
- // Ensure proper prototype chain for instanceof checks
105
- Object.setPrototypeOf(this, NAuthException.prototype);
106
-
107
- // Capture stack trace (excluding constructor call)
108
- if (Error.captureStackTrace) {
109
- Error.captureStackTrace(this, this.constructor);
110
- }
111
- }
112
-
113
- /**
114
- * Get the error code
115
- *
116
- * @returns Error code
117
- */
118
- getCode(): AuthErrorCode {
119
- return this.code;
120
- }
121
-
122
- /**
123
- * Get error details/metadata
124
- *
125
- * @returns Error details or undefined
126
- */
127
- getDetails(): Record<string, unknown> | undefined {
128
- return this.details;
129
- }
130
-
131
- /**
132
- * Check if error is a specific code
133
- *
134
- * @param code - Error code to check
135
- * @returns True if error matches code
136
- *
137
- * @example
138
- * ```typescript
139
- * try {
140
- * await sendSMS();
141
- * } catch (error) {
142
- * if (error instanceof NAuthException && error.isCode(AuthErrorCode.RATE_LIMIT_SMS)) {
143
- * // Handle rate limit specifically
144
- * }
145
- * }
146
- * ```
147
- */
148
- isCode(code: AuthErrorCode): boolean {
149
- return this.code === code;
150
- }
151
-
152
- /**
153
- * Serialize error to plain object
154
- *
155
- * Useful for logging, HTTP responses, or any serialization needs.
156
- *
157
- * @returns Plain object representation
158
- *
159
- * @example
160
- * ```typescript
161
- * catch (error) {
162
- * if (error instanceof NAuthException) {
163
- * console.log(error.toJSON());
164
- * // { code: 'RATE_LIMIT_SMS', message: '...', details: {...}, timestamp: '...' }
165
- * }
166
- * }
167
- * ```
168
- */
169
- toJSON(): { code: string; message: string; details?: Record<string, unknown>; timestamp: string } {
170
- return {
171
- code: this.code,
172
- message: this.message,
173
- details: this.details,
174
- timestamp: this.timestamp,
175
- };
176
- }
177
- }
178
-
179
- /**
180
- * Helper function to map error codes to suggested HTTP status codes
181
- *
182
- * **Optional** - Consumer can use this or define their own mapping.
183
- * Provided as a convenience for HTTP-based applications.
184
- *
185
- * @param code - Error code
186
- * @returns Suggested HTTP status code
187
- *
188
- * @example
189
- * ```typescript
190
- * // In NestJS exception filter
191
- * catch (exception: NAuthException, host: ArgumentsHost) {
192
- * const statusCode = getHttpStatusForErrorCode(exception.code);
193
- * const response = host.switchToHttp().getResponse();
194
- * response.status(statusCode).json(exception.toJSON());
195
- * }
196
- * ```
197
- */
198
- export function getHttpStatusForErrorCode(code: AuthErrorCode): number {
199
- // Rate limits
200
- if (code.startsWith('RATE_LIMIT_')) return 429;
201
-
202
- // Authentication errors
203
- if (code.startsWith('AUTH_')) {
204
- if (code === AuthErrorCode.ACCOUNT_INACTIVE || code === AuthErrorCode.ACCOUNT_LOCKED) return 403;
205
- return 401;
206
- }
207
-
208
- // Signup conflicts
209
- if (
210
- code === AuthErrorCode.EMAIL_EXISTS ||
211
- code === AuthErrorCode.USERNAME_EXISTS ||
212
- code === AuthErrorCode.PHONE_EXISTS
213
- )
214
- return 409;
215
- if (code === AuthErrorCode.SIGNUP_DISABLED) return 403;
216
-
217
- // Validation errors
218
- if (code.startsWith('VALIDATION_') || code.startsWith('INVALID_')) return 400;
219
-
220
- // Not found
221
- if (code === AuthErrorCode.NOT_FOUND) return 404;
222
-
223
- // Forbidden
224
- if (code === AuthErrorCode.FORBIDDEN) return 403;
225
-
226
- // Server errors
227
- if (code === AuthErrorCode.INTERNAL_ERROR || code === AuthErrorCode.SERVICE_UNAVAILABLE) return 500;
228
-
229
- // Default to 400
230
- return 400;
231
- }
@@ -1,260 +0,0 @@
1
- /**
2
- * Authentication Handler
3
- *
4
- * Validates JWT tokens and attaches user to request.
5
- *
6
- * **Platform-Agnostic:**
7
- * This handler operates purely on NAuthRequest interface.
8
- * Context is managed by the adapter, not this handler.
9
- */
10
-
11
- import { Repository } from 'typeorm';
12
- import {
13
- NAuthConfig,
14
- NAuthException,
15
- AuthErrorCode,
16
- resolveDeliveryForRequest,
17
- BaseUser,
18
- getAccessTokenCookieName,
19
- NAuthLogger,
20
- ContextStorage,
21
- IClientInfo,
22
- } from '../index';
23
- import { JwtService, SessionService } from '../internal';
24
- import { NAuthRequest, NAuthResponse } from '../platform/interfaces';
25
-
26
- /**
27
- * AuthHandler
28
- *
29
- * Validates JWT tokens and populates user context.
30
- * Performs optional authentication by default (doesn't reject unauthenticated requests).
31
- */
32
- export class AuthHandler {
33
- constructor(
34
- private jwtService: JwtService,
35
- private sessionService: SessionService,
36
- private userRepository: Repository<BaseUser>,
37
- private config: NAuthConfig,
38
- private logger?: NAuthLogger,
39
- ) {}
40
-
41
- /**
42
- * Handle request - validate token and attach user
43
- *
44
- * Note: Context is managed by adapter. This handler assumes context is available.
45
- */
46
- public async handle(req: NAuthRequest, _res: NAuthResponse, next: () => Promise<void> | void): Promise<void> {
47
- try {
48
- // Skip if route is marked as public
49
- if (req.attributes.nauthPublic) {
50
- await next();
51
- return;
52
- }
53
-
54
- const token = this.extractToken(req);
55
-
56
- if (!token) {
57
- // No token - continue without authentication (optional auth)
58
- await next();
59
- return;
60
- }
61
-
62
- const validation = await this.jwtService.validateAccessToken(token);
63
-
64
- if (!validation.valid) {
65
- this.logger?.debug?.('Invalid token:', validation.error);
66
- await next();
67
- return;
68
- }
69
-
70
- // Validate session
71
- const sessionId = validation.payload!.sessionId;
72
- const userId = validation.payload!.sub; // Extract userId from token sub claim
73
- const session = await this.sessionService.findByIdLight(sessionId);
74
-
75
- if (!session) {
76
- this.logger?.debug?.('Session not found:', sessionId);
77
- await next();
78
- return;
79
- }
80
-
81
- const initialVersion = session.version;
82
-
83
- if (session.isRevoked) {
84
- this.logger?.warn?.('Session has been revoked:', sessionId);
85
- await next();
86
- return;
87
- }
88
-
89
- if (session.expiresAt < new Date()) {
90
- this.logger?.debug?.('Session has expired:', sessionId);
91
- await next();
92
- return;
93
- }
94
-
95
- // Load user
96
- const user = await this.userRepository.findOne({
97
- select: this.getUserSelectFields(),
98
- where: { sub: validation.payload!.sub },
99
- });
100
-
101
- if (!user) {
102
- this.logger?.warn?.('User not found:', validation.payload!.sub);
103
- await next();
104
- return;
105
- }
106
-
107
- if (!user.isActive) {
108
- this.logger?.warn?.('Account is not active:', user.sub);
109
- await next();
110
- return;
111
- }
112
-
113
- // Optimistic locking check - ensure session wasn't modified during request
114
- const revalidated = await this.sessionService.findByIdLight(sessionId);
115
- if (!revalidated || revalidated.version !== initialVersion || revalidated.isRevoked) {
116
- this.logger?.error?.('Session was modified during request - possible security breach');
117
- await next();
118
- return;
119
- }
120
-
121
- // Attach to request attributes
122
- req.attributes.user = user;
123
- req.attributes.token = validation.payload;
124
-
125
- // Store in ContextStorage for service access
126
- ContextStorage.set('CURRENT_USER', user);
127
- ContextStorage.set('JWT_PAYLOAD', validation.payload);
128
- ContextStorage.set('CURRENT_SESSION', sessionId);
129
-
130
- this.logger?.debug?.(`User ${user.sub} authenticated successfully`);
131
-
132
- // Update CLIENT_INFO with sessionId and userId
133
- this.updateClientInfoSessionId(sessionId);
134
- this.updateClientInfoUserId(userId);
135
-
136
- await next();
137
- } catch (error) {
138
- this.logger?.error?.(
139
- 'Error in auth handler:',
140
- error instanceof Error ? error.message : String(error),
141
- error instanceof Error ? error.stack : undefined,
142
- );
143
- await next();
144
- }
145
- }
146
-
147
- /**
148
- * Extract token from request based on delivery mode
149
- */
150
- private extractToken(req: NAuthRequest): string | null {
151
- const method = this.config.tokenDelivery?.method || 'json';
152
-
153
- // Get token from header
154
- const authHeader = req.getHeader('authorization');
155
- const headerToken = authHeader?.startsWith('Bearer ') ? authHeader.substring(7) : null;
156
-
157
- // Get token from cookie
158
- const accessTokenCookieName = getAccessTokenCookieName(this.config);
159
- const cookieToken = req.cookies[accessTokenCookieName];
160
-
161
- // Check for route-level override
162
- const routeMode = req.attributes.nauthTokenDelivery;
163
-
164
- let effective: 'cookies' | 'json' = 'json';
165
-
166
- if (routeMode) {
167
- effective = routeMode;
168
- } else if (method === 'hybrid') {
169
- // Determine mode based on request characteristics
170
- effective = resolveDeliveryForRequest(req.raw, this.config.tokenDelivery?.hybridPolicy);
171
- } else {
172
- effective = method === 'cookies' ? 'cookies' : 'json';
173
- }
174
-
175
- if (effective === 'cookies') {
176
- // Cookie mode: Reject if Bearer header present
177
- if (headerToken && !cookieToken) {
178
- throw new NAuthException(
179
- AuthErrorCode.BEARER_NOT_ALLOWED,
180
- 'Bearer tokens are not allowed in cookie-only path.',
181
- );
182
- }
183
- return cookieToken || null;
184
- }
185
-
186
- // JSON mode: Reject if cookie present
187
- if (cookieToken && !headerToken) {
188
- throw new NAuthException(AuthErrorCode.COOKIES_NOT_ALLOWED, 'Cookie tokens are not allowed in JSON-only path.');
189
- }
190
- return headerToken || null;
191
- }
192
-
193
- /**
194
- * Update CLIENT_INFO with session ID from token
195
- */
196
- private updateClientInfoSessionId(sessionId: string | number): void {
197
- const clientInfo = ContextStorage.get<IClientInfo>('CLIENT_INFO');
198
- if (clientInfo) {
199
- const sessionIdNumber = typeof sessionId === 'number' ? sessionId : parseInt(String(sessionId), 10);
200
-
201
- if (!isNaN(sessionIdNumber) && sessionIdNumber > 0) {
202
- clientInfo.sessionId = sessionIdNumber;
203
- ContextStorage.set('CLIENT_INFO', clientInfo);
204
- }
205
- }
206
- }
207
-
208
- /**
209
- * Update CLIENT_INFO with user ID from token
210
- */
211
- private updateClientInfoUserId(userId: string | number): void {
212
- const clientInfo = ContextStorage.get<IClientInfo>('CLIENT_INFO');
213
- if (clientInfo) {
214
- const userIdNumber = typeof userId === 'number' ? userId : parseInt(String(userId), 10);
215
-
216
- if (!isNaN(userIdNumber) && userIdNumber > 0) {
217
- clientInfo.userId = userIdNumber;
218
- ContextStorage.set('CLIENT_INFO', clientInfo);
219
- }
220
- }
221
- }
222
-
223
- /**
224
- * Get fields to select when loading user
225
- */
226
- private getUserSelectFields(): (keyof BaseUser)[] {
227
- return [
228
- 'id',
229
- 'sub',
230
- 'username',
231
- 'firstName',
232
- 'lastName',
233
- 'email',
234
- 'phone',
235
- 'isEmailVerified',
236
- 'isPhoneVerified',
237
- 'isActive',
238
- 'mustChangePassword',
239
- 'isLocked',
240
- 'lockReason',
241
- 'lockedAt',
242
- 'lockedUntil',
243
- 'failedLoginAttempts',
244
- 'lastFailedLoginAt',
245
- 'lastLoginAt',
246
- 'lastLoginIp',
247
- 'hasSocialAuth',
248
- 'socialProviders',
249
- 'mfaEnabled',
250
- 'mfaMethods',
251
- 'preferredMfaMethod',
252
- 'mfaExempt',
253
- 'mfaExemptReason',
254
- 'mfaExemptGrantedAt',
255
- 'metadata',
256
- 'createdAt',
257
- 'updatedAt',
258
- ] as (keyof BaseUser)[];
259
- }
260
- }
@@ -1,101 +0,0 @@
1
- /**
2
- * Client Info Handler
3
- *
4
- * Extracts client information (IP, user agent, device info) from NAuthRequest
5
- * and stores in AsyncLocalStorage context.
6
- *
7
- * **Platform-Agnostic:**
8
- * This handler operates purely on NAuthRequest interface.
9
- * Context initialization is handled by the adapter, not this handler.
10
- */
11
-
12
- import { ContextStorage, ClientInfoService, IClientInfo, NAuthLogger, getDeviceTokenCookieName } from '../index';
13
- import { GeoLocationService } from '../internal';
14
- import { NAuthRequest, NAuthResponse } from '../platform/interfaces';
15
-
16
- /**
17
- * ClientInfoHandler
18
- *
19
- * First handler in the chain. Extracts client information and stores it
20
- * in the context for downstream handlers and services.
21
- */
22
- export class ClientInfoHandler {
23
- constructor(
24
- private clientInfoService: ClientInfoService,
25
- private geoLocationService?: GeoLocationService,
26
- private logger?: NAuthLogger,
27
- ) {}
28
-
29
- /**
30
- * Handle request - extract and store client info
31
- *
32
- * Context initialization is handled by the adapter.
33
- * This handler assumes context is already available.
34
- */
35
- public async handle(req: NAuthRequest, res: NAuthResponse, next: () => Promise<void> | void): Promise<void> {
36
- try {
37
- await this.extractAndStore(req, res);
38
- } catch (error) {
39
- this.logger?.error?.('Error extracting client info:', error);
40
- }
41
-
42
- await next();
43
- }
44
-
45
- /**
46
- * Extract client information and store in context
47
- */
48
- private async extractAndStore(req: NAuthRequest, res: NAuthResponse): Promise<void> {
49
- // Extract user agent
50
- const userAgent = req.getHeader('user-agent') || 'unknown';
51
-
52
- // Parse user agent for device/browser info
53
- const parsedUA = this.clientInfoService.parseUserAgent(userAgent);
54
-
55
- // Extract device token from cookie or header
56
- // Use default cookie name (nauth_device_token) if config not available
57
- const deviceTokenCookieName = getDeviceTokenCookieName();
58
- const deviceToken = req.cookies[deviceTokenCookieName] || req.getHeader('x-device-token');
59
-
60
- // Build client info object
61
- const clientInfo: IClientInfo = {
62
- ipAddress: req.ip,
63
- userAgent,
64
- deviceToken,
65
- deviceName: (req.body.deviceName as string) || parsedUA.deviceName || undefined,
66
- deviceType: ((req.body.deviceType as string) || parsedUA.deviceType || undefined) as IClientInfo['deviceType'],
67
- platform: parsedUA.platform || undefined,
68
- browser: parsedUA.browser || undefined,
69
- sessionId: undefined, // Set later by AuthHandler
70
- userId: undefined, // Set later by AuthHandler
71
- ipCountry: undefined,
72
- ipCity: undefined,
73
- ipLatitude: undefined,
74
- ipLongitude: undefined,
75
- };
76
-
77
- // Populate geolocation if service available
78
- if (this.geoLocationService && clientInfo.ipAddress && clientInfo.ipAddress !== '0.0.0.0') {
79
- try {
80
- const geo = await this.geoLocationService.getIpGeolocation(clientInfo.ipAddress);
81
- clientInfo.ipCountry = geo.country;
82
- clientInfo.ipCity = geo.city;
83
- clientInfo.ipLatitude = geo.latitude;
84
- clientInfo.ipLongitude = geo.longitude;
85
- } catch (error) {
86
- // Log error instead of silently failing
87
- this.logger?.error?.(
88
- `Geolocation lookup failed for IP ${clientInfo.ipAddress}:`,
89
- error instanceof Error ? error.message : 'Unknown error',
90
- );
91
- }
92
- }
93
-
94
- // Store in context
95
- ContextStorage.set('CLIENT_INFO', clientInfo);
96
- ContextStorage.set('HTTP_RESPONSE', res.raw);
97
-
98
- // Also attach to request attributes for handler access
99
- req.attributes.clientInfo = clientInfo;
100
- }
101
- }