@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,581 +0,0 @@
1
- import * as crypto from 'crypto';
2
- import { Repository } from 'typeorm';
3
- import { BaseUser } from '../entities';
4
- import { IUser } from '../interfaces/entities.interface';
5
- import { AuthService } from './auth.service';
6
- import { SocialAuthService } from './social-auth.service';
7
- import { TrustedDeviceService } from './trusted-device.service';
8
- import { JwtService } from './jwt.service';
9
- import { SessionService } from './session.service';
10
- import { AuthChallengeHelperService } from './auth-challenge-helper.service';
11
- import { ClientInfoService } from './client-info.service';
12
- import { PhoneVerificationService } from './phone-verification.service';
13
- import { InternalAuthAuditService as AuthAuditService } from './auth-audit.service';
14
- import { AuthAuditEventType } from '../enums/auth-audit-event-type.enum';
15
- import { NAuthConfig } from '../interfaces/config.interface';
16
- import { NAuthLogger } from '../utils/nauth-logger';
17
- import { AuthResponseDTO } from '../dto';
18
- import { OAuthUserProfile } from '../interfaces/oauth.interface';
19
- import { ISocialAuthProviderService } from '../interfaces/social-auth-provider.interface';
20
- import { NAuthException } from '../exceptions/nauth.exception';
21
- import { AuthErrorCode } from '../enums/error-codes.enum';
22
-
23
- /**
24
- * Base Social Auth Provider Service
25
- *
26
- * Abstract base class that provides common functionality for all social auth providers.
27
- * Provider-specific services (Google, Apple, Facebook, GitHub, etc.) should extend this class
28
- * and implement provider-specific OAuth client logic.
29
- *
30
- * This base class handles:
31
- * - User creation/lookup
32
- * - Social account linking
33
- * - JWT token generation
34
- * - Session management
35
- * - Challenge system integration
36
- *
37
- * **Key Design:**
38
- * - No hardcoded provider names - works with any provider
39
- * - Provider config accessed dynamically via `providerName`
40
- * - Future developers can add new providers without modifying this class
41
- *
42
- * @example
43
- * ```typescript
44
- * @Injectable()
45
- * export class GitHubSocialAuthService extends BaseSocialAuthProviderService {
46
- * readonly providerName = 'github';
47
- *
48
- * constructor(
49
- * // ... dependencies
50
- * private readonly githubOAuthClient: GitHubOAuthClient,
51
- * ) {
52
- * super(/* ... base dependencies *\/);
53
- * }
54
- *
55
- * protected async getOAuthProfile(code: string, state: string): Promise<OAuthUserProfile> {
56
- * // Provider-specific implementation
57
- * }
58
- * }
59
- * ```
60
- */
61
- export abstract class BaseSocialAuthProviderService implements ISocialAuthProviderService {
62
- abstract readonly providerName: string;
63
-
64
- constructor(
65
- protected readonly config: NAuthConfig,
66
- protected readonly logger: NAuthLogger,
67
- protected readonly authService: AuthService,
68
- protected readonly socialAuthService: SocialAuthService,
69
- protected readonly jwtService: JwtService,
70
- protected readonly sessionService: SessionService,
71
- protected readonly challengeHelper: AuthChallengeHelperService,
72
- protected readonly clientInfoService: ClientInfoService,
73
- // State store for CSRF protection - shared across all providers
74
- protected readonly stateStore: Map<string, { timestamp: number; provider: string }>,
75
- // User repository for creating social users
76
- protected readonly userRepository: Repository<BaseUser>,
77
- // Phone verification service (optional - only available when SMS provider is configured)
78
- protected readonly phoneVerificationService?: PhoneVerificationService,
79
- protected readonly auditService?: AuthAuditService, // Optional - audit trail service (enabled via config.auditLogs.enabled)
80
- protected readonly trustedDeviceService?: TrustedDeviceService, // Optional - only available when rememberDevices is not 'never'
81
- ) {}
82
-
83
- /**
84
- * Get provider configuration dynamically
85
- *
86
- * Accesses config.social[providerName] without hardcoding provider names.
87
- * This allows any provider to work without modifying core code.
88
- *
89
- * @returns Provider configuration from NAuthConfig
90
- * @protected
91
- */
92
- protected getProviderConfig(): any {
93
- const socialConfig = this.config.social;
94
- if (!socialConfig) return null;
95
-
96
- // Access config dynamically using providerName (no hardcoding)
97
- return (socialConfig as Record<string, unknown>)[this.providerName] || null;
98
- }
99
-
100
- /**
101
- * Generate OAuth authorization URL for this provider
102
- *
103
- * Must be implemented by provider-specific services to generate the OAuth URL.
104
- *
105
- * @param state - Optional state parameter for CSRF protection
106
- * @returns Authorization URL to redirect user to
107
- * @throws {BadRequestException} When provider is not properly configured
108
- */
109
- abstract getAuthUrl(state?: string): Promise<string>;
110
-
111
- /**
112
- * Get user profile from OAuth callback
113
- *
114
- * Must be implemented by provider-specific services to exchange code for tokens
115
- * and fetch user profile.
116
- *
117
- * @param code - Authorization code from OAuth callback
118
- * @param state - State parameter from OAuth callback
119
- * @returns OAuth user profile
120
- * @protected
121
- */
122
- protected abstract getOAuthProfile(code: string, state: string): Promise<OAuthUserProfile>;
123
-
124
- /**
125
- * Verify social authentication token from native mobile apps
126
- *
127
- * Must be implemented by provider-specific services to verify ID tokens.
128
- *
129
- * @param idToken - ID token from native SDK
130
- * @param accessToken - Optional access token from native SDK
131
- * @param profileData - Optional profile data from native SDK
132
- * @returns OAuth user profile
133
- * @protected
134
- */
135
- protected abstract verifyNativeToken(
136
- idToken: string,
137
- accessToken?: string,
138
- profileData?: any,
139
- ): Promise<OAuthUserProfile>;
140
-
141
- /**
142
- * Handle OAuth callback and authenticate user
143
- *
144
- * Uses the provider-specific getOAuthProfile method and then handles
145
- * user creation, session management, and token generation.
146
- */
147
- async handleCallback(code: string, state: string): Promise<AuthResponseDTO> {
148
- const providerConfig = this.getProviderConfig();
149
- if (!providerConfig) {
150
- throw new NAuthException(
151
- AuthErrorCode.SOCIAL_CONFIG_MISSING,
152
- `Provider configuration not found: ${this.providerName}`,
153
- );
154
- }
155
-
156
- // Validate state (basic CSRF protection)
157
- this.validateState(state);
158
-
159
- try {
160
- // Get user profile from provider
161
- const profile = await this.getOAuthProfile(code, state);
162
- this.logger?.log?.(`[SocialAuth] ${this.providerName} callback verified (secure): ${profile.email}`);
163
-
164
- // Find or create user
165
- const user = await this.findOrCreateUser(profile, providerConfig);
166
-
167
- // Create or update social account
168
- await this.createOrUpdateSocialAccount(user, profile);
169
-
170
- // Generate JWT tokens and session
171
- return await this.createAuthResponse(user, 'web');
172
- } catch (error) {
173
- if (error instanceof NAuthException) {
174
- throw error;
175
- }
176
- if (error instanceof Error) {
177
- throw new NAuthException(AuthErrorCode.SOCIAL_TOKEN_INVALID, `Social authentication failed: ${error.message}`);
178
- }
179
- throw new NAuthException(AuthErrorCode.SOCIAL_TOKEN_INVALID, 'Social authentication failed: Unknown error');
180
- }
181
- }
182
-
183
- /**
184
- * Verify social authentication token from native mobile apps
185
- */
186
- async verifyToken(idToken: string, accessToken?: string, profileData?: any): Promise<AuthResponseDTO> {
187
- const providerConfig = this.getProviderConfig();
188
- if (!providerConfig || !providerConfig.enabled) {
189
- throw new NAuthException(
190
- AuthErrorCode.SOCIAL_CONFIG_MISSING,
191
- `Provider '${this.providerName}' is not configured or not enabled`,
192
- );
193
- }
194
-
195
- try {
196
- // Verify token and get profile
197
- const profile = await this.verifyNativeToken(idToken, accessToken, profileData);
198
-
199
- // Find or create user
200
- const user = await this.findOrCreateUser(profile, providerConfig);
201
-
202
- // Create or update social account
203
- await this.createOrUpdateSocialAccount(user, profile);
204
-
205
- // Generate JWT tokens and session
206
- return await this.createAuthResponse(user, 'mobile');
207
- } catch (error) {
208
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
209
- this.logger?.error?.(`Native token verification failed for ${this.providerName}: ${errorMessage}`);
210
- throw new NAuthException(AuthErrorCode.SOCIAL_TOKEN_INVALID, `Token verification failed: ${errorMessage}`);
211
- }
212
- }
213
-
214
- /**
215
- * Link social account to existing user
216
- */
217
- async linkAccount(userId: string, code: string, state: string): Promise<{ message: string }> {
218
- // Get full user entity (need internal id for foreign keys)
219
- const user = (await this.userRepository.findOne({ where: { sub: userId } })) as IUser | null;
220
- if (!user) {
221
- throw new NAuthException(AuthErrorCode.NOT_FOUND, 'User not found');
222
- }
223
-
224
- const providerConfig = this.getProviderConfig();
225
- if (!providerConfig) {
226
- throw new NAuthException(
227
- AuthErrorCode.SOCIAL_CONFIG_MISSING,
228
- `Provider configuration not found: ${this.providerName}`,
229
- );
230
- }
231
-
232
- // Validate state
233
- this.validateState(state);
234
-
235
- try {
236
- // Get user profile from provider
237
- const profile = await this.getOAuthProfile(code, state);
238
-
239
- // Check if account is already linked
240
- const existingAccount = await this.socialAuthService.findSocialAccountByProvider(this.providerName, profile.id);
241
-
242
- if (existingAccount) {
243
- throw new NAuthException(
244
- AuthErrorCode.SOCIAL_ACCOUNT_LINKED,
245
- 'This social account is already linked to another user',
246
- );
247
- }
248
-
249
- // Create social account using service
250
- await this.socialAuthService.createOrUpdateSocialAccount(
251
- user.id as number,
252
- this.providerName,
253
- profile.id,
254
- profile.email,
255
- profile.raw,
256
- );
257
-
258
- // ============================================================================
259
- // Audit: Record social account link
260
- // ============================================================================
261
- try {
262
- await this.auditService?.recordEvent({
263
- userId: user.id,
264
- eventType: AuthAuditEventType.SOCIAL_ACCOUNT_LINKED,
265
- eventStatus: 'INFO',
266
- authMethod: this.providerName,
267
- // Client info automatically included from context
268
- metadata: {
269
- provider: this.providerName,
270
- providerEmail: profile.email || null,
271
- },
272
- });
273
- } catch (auditError) {
274
- // Non-blocking: Log but continue
275
- const errorMessage = auditError instanceof Error ? auditError.message : 'Unknown error';
276
- this.logger?.error?.(`Failed to record SOCIAL_ACCOUNT_LINKED audit event: ${errorMessage}`, {
277
- error: auditError,
278
- userId: user.id,
279
- provider: this.providerName,
280
- });
281
- }
282
-
283
- // ============================================================================
284
- // Audit: Record social account link
285
- // ============================================================================
286
- try {
287
- await this.auditService?.recordEvent({
288
- userId: user.id,
289
- eventType: AuthAuditEventType.SOCIAL_ACCOUNT_LINKED,
290
- eventStatus: 'SUCCESS',
291
- authMethod: this.providerName.toLowerCase(),
292
- // Client info automatically included from context
293
- metadata: {
294
- provider: this.providerName.toLowerCase(),
295
- providerEmail: profile.email || null,
296
- },
297
- });
298
- } catch (auditError) {
299
- // Non-blocking: Log but continue
300
- const errorMessage = auditError instanceof Error ? auditError.message : 'Unknown error';
301
- this.logger?.error?.(`Failed to record SOCIAL_ACCOUNT_LINKED audit event: ${errorMessage}`, {
302
- error: auditError,
303
- userId: user.id,
304
- provider: this.providerName,
305
- });
306
- }
307
-
308
- return { message: `${this.providerName} account linked successfully` };
309
- } catch (error) {
310
- if (error instanceof NAuthException) {
311
- throw error;
312
- }
313
- if (error instanceof Error) {
314
- throw new NAuthException(AuthErrorCode.SOCIAL_TOKEN_INVALID, `Social account linking failed: ${error.message}`);
315
- }
316
- throw new NAuthException(AuthErrorCode.SOCIAL_TOKEN_INVALID, 'Social account linking failed: Unknown error');
317
- }
318
- }
319
-
320
- /**
321
- * Get OAuth user profile from callback
322
- *
323
- * Alias for getOAuthProfile for interface compliance.
324
- */
325
- async getUserProfileFromCallback(code: string, state: string): Promise<OAuthUserProfile> {
326
- return this.getOAuthProfile(code, state);
327
- }
328
-
329
- // ============================================================================
330
- // Protected Helper Methods
331
- // ============================================================================
332
-
333
- /**
334
- * Validate state parameter for CSRF protection
335
- */
336
- protected validateState(state: string): void {
337
- const stateData = this.stateStore.get(state);
338
- if (!stateData) {
339
- throw new NAuthException(AuthErrorCode.VALIDATION_FAILED, 'Invalid state parameter', { field: 'state' });
340
- }
341
-
342
- if (stateData.provider !== this.providerName) {
343
- throw new NAuthException(AuthErrorCode.VALIDATION_FAILED, 'State provider mismatch', { field: 'state' });
344
- }
345
-
346
- // Check if state is not too old (5 minutes)
347
- if (Date.now() - stateData.timestamp > 5 * 60 * 1000) {
348
- this.stateStore.delete(state);
349
- throw new NAuthException(AuthErrorCode.CHALLENGE_EXPIRED, 'State parameter expired');
350
- }
351
-
352
- // Clean up used state
353
- this.stateStore.delete(state);
354
- }
355
-
356
- /**
357
- * Generate random state for CSRF protection
358
- */
359
- protected generateState(): string {
360
- const state = crypto.randomBytes(32).toString('hex');
361
- this.stateStore.set(state, {
362
- timestamp: Date.now(),
363
- provider: this.providerName,
364
- });
365
- return state;
366
- }
367
-
368
- /**
369
- * Find existing user or create new one
370
- */
371
- protected async findOrCreateUser(profile: OAuthUserProfile, providerConfig: any): Promise<IUser> {
372
- // First, try to find user by social account
373
- const socialAccount = await this.socialAuthService.findSocialAccountByProvider(this.providerName, profile.id);
374
-
375
- if (socialAccount) {
376
- return (socialAccount as unknown as { user?: IUser }).user as IUser;
377
- }
378
-
379
- // If auto-link is enabled, try to find by email
380
- if (providerConfig.autoLink && profile.email) {
381
- // Get full user entity (need internal id for foreign keys)
382
- const existingUser = (await this.userRepository.findOne({
383
- where: { email: profile.email, isEmailVerified: true },
384
- })) as IUser | null;
385
-
386
- if (existingUser) {
387
- return existingUser;
388
- }
389
- }
390
-
391
- // Create new user if allowSignup is enabled
392
- if (providerConfig.allowSignup) {
393
- this.logger?.log?.(
394
- `[SocialAuth] Creating user: email=${profile.email}, isEmailVerified=${profile.verified || false}`,
395
- );
396
-
397
- const savedUser = await this.createSocialUser(
398
- profile.email || '',
399
- profile.firstName,
400
- profile.lastName,
401
- profile.verified || false,
402
- this.providerName,
403
- );
404
-
405
- this.logger?.log?.(
406
- `[SocialAuth] User created: email=${savedUser.email}, isEmailVerified=${savedUser.isEmailVerified}`,
407
- );
408
-
409
- return savedUser;
410
- }
411
-
412
- throw new NAuthException(AuthErrorCode.SIGNUP_DISABLED, 'User not found and signup is disabled');
413
- }
414
-
415
- /**
416
- * Create a social-only user (no password)
417
- *
418
- * @param email - User email
419
- * @param firstName - Optional first name
420
- * @param lastName - Optional last name
421
- * @param isEmailVerified - Whether email is verified (default: true)
422
- * @param socialProvider - Initial social provider name
423
- * @returns Created user
424
- * @protected
425
- */
426
- protected async createSocialUser(
427
- email: string,
428
- firstName?: string | null,
429
- lastName?: string | null,
430
- isEmailVerified: boolean = true,
431
- socialProvider?: string,
432
- ): Promise<IUser> {
433
- const user = this.userRepository.create({
434
- email,
435
- firstName: firstName || null,
436
- lastName: lastName || null,
437
- isEmailVerified,
438
- hasSocialAuth: true,
439
- socialProviders: socialProvider ? [socialProvider] : null,
440
- isActive: true,
441
- });
442
-
443
- const savedUser = (await this.userRepository.save(user)) as unknown as IUser;
444
- this.logger?.log?.(`Social user created: ${email} (sub: ${savedUser.sub})`);
445
- return savedUser;
446
- }
447
-
448
- /**
449
- * Create or update social account
450
- */
451
- protected async createOrUpdateSocialAccount(user: IUser, profile: OAuthUserProfile): Promise<void> {
452
- await this.socialAuthService.createOrUpdateSocialAccount(
453
- user.id as number,
454
- this.providerName,
455
- profile.id,
456
- profile.email,
457
- profile.raw,
458
- );
459
- }
460
-
461
- /**
462
- * Create authentication response with tokens and user info
463
- */
464
- protected async createAuthResponse(user: IUser, _deviceType: 'web' | 'mobile'): Promise<AuthResponseDTO> {
465
- // Get actual client info from context (IP, userAgent, etc.)
466
- const clientInfo = this.clientInfoService.get();
467
-
468
- // ============================================================================
469
- // Audit: Record login attempt for social authentication
470
- // ============================================================================
471
- try {
472
- await this.auditService?.recordEvent({
473
- userId: user.id,
474
- eventType: AuthAuditEventType.LOGIN_ATTEMPT,
475
- eventStatus: 'INFO',
476
- authMethod: this.providerName.toLowerCase(),
477
- description: `${this.providerName} OAuth token validated`,
478
- });
479
- } catch (auditError) {
480
- // Non-blocking: Log but continue
481
- const errorMessage = auditError instanceof Error ? auditError.message : 'Unknown error';
482
- this.logger?.error?.(`Failed to record LOGIN_ATTEMPT audit event for social login: ${errorMessage}`, {
483
- error: auditError,
484
- userId: user.id,
485
- provider: this.providerName,
486
- });
487
- }
488
-
489
- // ============================================================================
490
- // Check for Required Challenges BEFORE creating session
491
- // ============================================================================
492
- const response = await this.challengeHelper.determineAuthResponse({
493
- user,
494
- config: this.config,
495
- deviceToken: clientInfo.deviceToken,
496
- isSocialLogin: true,
497
- skipMFAVerification: false,
498
- authProvider: this.providerName.toLowerCase(), // e.g., 'google', 'facebook', 'apple'
499
- });
500
-
501
- if (response.challengeName) {
502
- this.logger?.log?.(`Challenge required for social auth user ${user.sub}: ${response.challengeName}`);
503
- // Record SOCIAL_LOGIN event even when challenge is required
504
- try {
505
- await this.auditService?.recordEvent({
506
- userId: user.id,
507
- eventType: AuthAuditEventType.SOCIAL_LOGIN,
508
- eventStatus: 'INFO',
509
- authMethod: this.providerName.toLowerCase(),
510
- metadata: {
511
- provider: this.providerName.toLowerCase(),
512
- challengeRequired: response.challengeName,
513
- },
514
- });
515
- } catch (auditError) {
516
- const errorMessage = auditError instanceof Error ? auditError.message : 'Unknown error';
517
- this.logger?.error?.(`Failed to record SOCIAL_LOGIN audit event (challenge): ${errorMessage}`, {
518
- error: auditError,
519
- userId: user.id,
520
- provider: this.providerName,
521
- });
522
- }
523
- return response;
524
- }
525
-
526
- // ============================================================================
527
- // No challenges - determineAuthResponse already created session and tokens
528
- // Just record SOCIAL_LOGIN audit event and return the response
529
- // ============================================================================
530
- // ============================================================================
531
- // No challenges - determineAuthResponse already created session and tokens
532
- // Just record SOCIAL_LOGIN audit event and return the response
533
- // ============================================================================
534
-
535
- // Check trusted device status (for audit metadata)
536
- let isTrustedDevice = false;
537
- if (
538
- this.config.mfa?.rememberDevices &&
539
- this.config.mfa?.rememberDevices !== 'never' &&
540
- this.trustedDeviceService &&
541
- clientInfo.deviceToken
542
- ) {
543
- try {
544
- isTrustedDevice = await this.trustedDeviceService.isDeviceTrusted(clientInfo.deviceToken, user.id);
545
- } catch (error) {
546
- // Non-blocking: Log but continue
547
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
548
- this.logger?.warn?.(`Failed to check trusted device for social login: ${errorMessage}`, {
549
- error,
550
- userId: user.id,
551
- provider: this.providerName,
552
- });
553
- }
554
- }
555
-
556
- // Record SOCIAL_LOGIN audit event
557
- try {
558
- await this.auditService?.recordEvent({
559
- userId: user.id,
560
- eventType: AuthAuditEventType.SOCIAL_LOGIN,
561
- eventStatus: 'SUCCESS',
562
- authMethod: this.providerName.toLowerCase(),
563
- metadata: {
564
- provider: this.providerName.toLowerCase(),
565
- trustedDevice: isTrustedDevice,
566
- },
567
- });
568
- } catch (auditError) {
569
- // Non-blocking: Log but continue
570
- const errorMessage = auditError instanceof Error ? auditError.message : 'Unknown error';
571
- this.logger?.error?.(`Failed to record SOCIAL_LOGIN audit event: ${errorMessage}`, {
572
- error: auditError,
573
- userId: user.id,
574
- provider: this.providerName,
575
- });
576
- }
577
-
578
- // Return the response with session and tokens already created by determineAuthResponse
579
- return response;
580
- }
581
- }