@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,202 +0,0 @@
1
- import { AuthFlowContext, Rule } from './auth-flow-state-machine.types';
2
-
3
- /**
4
- * Rule Builder
5
- *
6
- * Utility class for composing complex rules using combinators.
7
- * Supports logical operations: all, any, not.
8
- *
9
- * @example
10
- * ```typescript
11
- * const complexRule = RuleBuilder.all([
12
- * Rules.mustChangePassword,
13
- * RuleBuilder.not(Rules.isMFAExempt)
14
- * ]);
15
- * ```
16
- */
17
- export class RuleBuilder {
18
- /**
19
- * Combine multiple rules with AND logic
20
- * All rules must evaluate to true
21
- *
22
- * @param rules - Array of rules to combine
23
- * @returns Combined rule that returns true only if all rules are true
24
- *
25
- * @example
26
- * ```typescript
27
- * const rule = RuleBuilder.all([
28
- * Rules.emailVerificationPending,
29
- * Rules.isNotSocialLogin
30
- * ]);
31
- * ```
32
- */
33
- static all(rules: Rule[]): Rule {
34
- return (context: AuthFlowContext): boolean => {
35
- return rules.every((rule) => rule(context));
36
- };
37
- }
38
-
39
- /**
40
- * Combine multiple rules with OR logic
41
- * At least one rule must evaluate to true
42
- *
43
- * @param rules - Array of rules to combine
44
- * @returns Combined rule that returns true if any rule is true
45
- *
46
- * @example
47
- * ```typescript
48
- * const rule = RuleBuilder.any([
49
- * Rules.isDeviceTrusted,
50
- * Rules.isMFAExempt
51
- * ]);
52
- * ```
53
- */
54
- static any(rules: Rule[]): Rule {
55
- return (context: AuthFlowContext): boolean => {
56
- return rules.some((rule) => rule(context));
57
- };
58
- }
59
-
60
- /**
61
- * Negate a rule
62
- * Returns true when the rule returns false
63
- *
64
- * @param rule - Rule to negate
65
- * @returns Negated rule
66
- *
67
- * @example
68
- * ```typescript
69
- * const rule = RuleBuilder.not(Rules.isMFAExempt);
70
- * ```
71
- */
72
- static not(rule: Rule): Rule {
73
- return (context: AuthFlowContext): boolean => {
74
- return !rule(context);
75
- };
76
- }
77
- }
78
-
79
- /**
80
- * Authentication Flow Rules
81
- *
82
- * Declarative rules for evaluating authentication flow states.
83
- * Each rule is a pure function that evaluates to true or false based on context.
84
- *
85
- * Rules are used in state definitions to determine which state applies.
86
- */
87
- export const Rules = {
88
- /**
89
- * User must change password
90
- * Priority: 1 (highest)
91
- *
92
- * @param context - Authentication flow context
93
- * @returns True if user must change password
94
- */
95
- mustChangePassword: (context: AuthFlowContext): boolean => {
96
- return context.user.mustChangePassword === true;
97
- },
98
-
99
- /**
100
- * Email verification is pending
101
- * Priority: 2
102
- *
103
- * @param context - Authentication flow context
104
- * @returns True if email verification is required and not completed
105
- */
106
- emailVerificationPending: (context: AuthFlowContext): boolean => {
107
- return context.computed.isEmailVerificationRequired;
108
- },
109
-
110
- /**
111
- * Phone collection is needed
112
- * Priority: 3
113
- *
114
- * @param context - Authentication flow context
115
- * @returns True if phone collection is needed (user has no phone)
116
- */
117
- phoneCollectionNeeded: (context: AuthFlowContext): boolean => {
118
- return context.computed.isPhoneCollectionNeeded;
119
- },
120
-
121
- /**
122
- * Phone verification is pending
123
- * Priority: 4
124
- *
125
- * @param context - Authentication flow context
126
- * @returns True if phone verification is required and not completed
127
- */
128
- phoneVerificationPending: (context: AuthFlowContext): boolean => {
129
- return context.computed.isPhoneVerificationRequired;
130
- },
131
-
132
- /**
133
- * MFA setup is required
134
- * Priority: 5
135
- *
136
- * @param context - Authentication flow context
137
- * @returns True if MFA setup is required
138
- */
139
- mfaSetupRequired: (context: AuthFlowContext): boolean => {
140
- return context.computed.isMFASetupRequired;
141
- },
142
-
143
- /**
144
- * MFA verification is required
145
- * Priority: 6
146
- *
147
- * @param context - Authentication flow context
148
- * @returns True if MFA verification is required
149
- */
150
- mfaVerificationRequired: (context: AuthFlowContext): boolean => {
151
- return context.computed.isMFAVerificationRequired;
152
- },
153
-
154
- /**
155
- * Grace period is active (ADAPTIVE mode with MFA not enabled)
156
- * Priority: 7
157
- *
158
- * This rule applies when:
159
- * - Enforcement is ADAPTIVE
160
- * - Grace period is active
161
- * - MFA is not enabled
162
- * - User is not blocked
163
- *
164
- * @param context - Authentication flow context
165
- * @returns True if grace period is active and MFA not enabled
166
- */
167
- gracePeriodActiveAdaptive: (context: AuthFlowContext): boolean => {
168
- const enforcement = context.config.mfa?.enforcement || 'OPTIONAL';
169
- return (
170
- enforcement === 'ADAPTIVE' &&
171
- context.computed.isGracePeriodActive &&
172
- !context.user.mfaEnabled &&
173
- !context.computed.isBlocked
174
- );
175
- },
176
-
177
- /**
178
- * User is blocked from signing in
179
- * Priority: 8
180
- *
181
- * @param context - Authentication flow context
182
- * @returns True if user is blocked
183
- */
184
- isBlocked: (context: AuthFlowContext): boolean => {
185
- return context.computed.isBlocked;
186
- },
187
-
188
- /**
189
- * User is authenticated (no challenges pending)
190
- * Priority: 9 (lowest - default state)
191
- *
192
- * This rule applies when no other state rules match.
193
- * It's the default state when all challenges are complete.
194
- *
195
- * @param _context - Authentication flow context (unused - always returns true)
196
- * @returns True if user is authenticated (always true as fallback)
197
- */
198
- authenticated: (_context: AuthFlowContext): boolean => {
199
- // Always true - this is the default state when no other rules match
200
- return true;
201
- },
202
- };
@@ -1,190 +0,0 @@
1
- import { AuthFlowState, StateDefinition, AuthFlowContext, ResponseMetadata } from './auth-flow-state-machine.types';
2
- import { AuthChallenge } from '../dto/auth-challenge.dto';
3
- import { Rules } from './auth-flow-rules';
4
- import { MFAMethod } from '../enums/mfa-method.enum';
5
-
6
- /**
7
- * Authentication Flow State Definitions
8
- *
9
- * Defines all possible states in the authentication flow with their:
10
- * - Priority (evaluation order, 1-9)
11
- * - Condition rules (when state applies)
12
- * - Challenge mappings (which AuthChallenge this state maps to)
13
- * - Metadata builders (optional additional response data)
14
- * - OnEnter hooks (optional actions when state is entered)
15
- *
16
- * States are evaluated in priority order. The first state whose condition
17
- * evaluates to true is selected.
18
- *
19
- * @example
20
- * ```typescript
21
- * const state = STATE_DEFINITIONS.find(def => def.condition(context));
22
- * ```
23
- */
24
- export const STATE_DEFINITIONS: StateDefinition[] = [
25
- /**
26
- * Priority 1: Force Password Change
27
- * Highest priority - must be completed before any other challenges
28
- */
29
- {
30
- state: AuthFlowState.PENDING_PASSWORD_CHANGE,
31
- priority: 1,
32
- condition: Rules.mustChangePassword,
33
- challenge: AuthChallenge.FORCE_CHANGE_PASSWORD,
34
- },
35
-
36
- /**
37
- * Priority 2: Email Verification
38
- * Required before phone verification (sequential flow)
39
- */
40
- {
41
- state: AuthFlowState.PENDING_EMAIL_VERIFICATION,
42
- priority: 2,
43
- condition: Rules.emailVerificationPending,
44
- challenge: AuthChallenge.VERIFY_EMAIL,
45
- },
46
-
47
- /**
48
- * Priority 3: Phone Collection
49
- * User must provide phone number before verification
50
- */
51
- {
52
- state: AuthFlowState.PENDING_PHONE_COLLECTION,
53
- priority: 3,
54
- condition: Rules.phoneCollectionNeeded,
55
- challenge: AuthChallenge.VERIFY_PHONE,
56
- },
57
-
58
- /**
59
- * Priority 4: Phone Verification
60
- * Required after email verification (sequential flow)
61
- */
62
- {
63
- state: AuthFlowState.PENDING_PHONE_VERIFICATION,
64
- priority: 4,
65
- condition: Rules.phoneVerificationPending,
66
- challenge: AuthChallenge.VERIFY_PHONE,
67
- },
68
-
69
- /**
70
- * Priority 5: MFA Setup Required
71
- * Required when enforcement is REQUIRED/ADAPTIVE and grace period expired
72
- */
73
- {
74
- state: AuthFlowState.PENDING_MFA_SETUP,
75
- priority: 5,
76
- condition: Rules.mfaSetupRequired,
77
- challenge: AuthChallenge.MFA_SETUP_REQUIRED,
78
- /**
79
- * OnEnter hook: Auto-complete SMS MFA setup if phone is already verified
80
- *
81
- * Special case: If user's phone is already verified and they choose SMS MFA,
82
- * we can skip the SMS verification step during setup (improves UX).
83
- * The phone was already verified, so we trust it for MFA setup.
84
- *
85
- * This sets skipMFAVerification flag which will be checked during MFA setup completion.
86
- */
87
- onEnter: async (context: AuthFlowContext): Promise<void> => {
88
- // Check if phone is verified and preferred method is SMS
89
- if (context.user.isPhoneVerified && context.user.phone && context.user.preferredMfaMethod === MFAMethod.SMS) {
90
- // Auto-complete: Set skipMFAVerification flag
91
- // This will be used during MFA setup completion to skip SMS verification
92
- context.skipMFAVerification = true;
93
- }
94
- },
95
- },
96
-
97
- /**
98
- * Priority 6: MFA Verification Required
99
- * Required when MFA is enabled and verification is needed
100
- */
101
- {
102
- state: AuthFlowState.PENDING_MFA_VERIFICATION,
103
- priority: 6,
104
- condition: Rules.mfaVerificationRequired,
105
- challenge: AuthChallenge.MFA_REQUIRED,
106
- },
107
-
108
- /**
109
- * Priority 7: Grace Period Active
110
- * ADAPTIVE mode with grace period active and MFA not enabled
111
- * This is a special state that allows login but includes metadata about grace period
112
- */
113
- {
114
- state: AuthFlowState.GRACE_PERIOD_ACTIVE,
115
- priority: 7,
116
- condition: Rules.gracePeriodActiveAdaptive,
117
- /**
118
- * Build metadata for grace period state
119
- * Includes grace period end timestamp and risk information
120
- */
121
- buildMetadata: (context: AuthFlowContext): ResponseMetadata | undefined => {
122
- return {
123
- gracePeriodEndsAt: context.computed.gracePeriodEndsAt,
124
- riskScore: context.computed.riskScore,
125
- riskLevel: context.computed.riskLevel,
126
- };
127
- },
128
- },
129
-
130
- /**
131
- * Priority 8: Blocked
132
- * User is blocked from signing in due to high risk
133
- */
134
- {
135
- state: AuthFlowState.BLOCKED,
136
- priority: 8,
137
- condition: Rules.isBlocked,
138
- /**
139
- * Build metadata for blocked state
140
- * Includes block expiration and reason
141
- */
142
- buildMetadata: (context: AuthFlowContext): ResponseMetadata | undefined => {
143
- return {
144
- blockedUntil: context.computed.blockedUntil,
145
- reason: context.computed.blockReason,
146
- };
147
- },
148
- },
149
-
150
- /**
151
- * Priority 9: Authenticated
152
- * Default state when all challenges are complete
153
- * This rule always evaluates to true, so it's the fallback state
154
- */
155
- {
156
- state: AuthFlowState.AUTHENTICATED,
157
- priority: 9,
158
- condition: Rules.authenticated,
159
- },
160
- ];
161
-
162
- /**
163
- * Get state definition by state
164
- *
165
- * @param state - State to get definition for
166
- * @returns State definition or undefined if not found
167
- *
168
- * @example
169
- * ```typescript
170
- * const def = getStateDefinition(AuthFlowState.PENDING_EMAIL_VERIFICATION);
171
- * ```
172
- */
173
- export function getStateDefinition(state: AuthFlowState): StateDefinition | undefined {
174
- return STATE_DEFINITIONS.find((def) => def.state === state);
175
- }
176
-
177
- /**
178
- * Get state definitions sorted by priority
179
- *
180
- * @returns State definitions sorted by priority (1-9)
181
- *
182
- * @example
183
- * ```typescript
184
- * const sorted = getStateDefinitionsByPriority();
185
- * // Evaluates states in order: PENDING_PASSWORD_CHANGE, PENDING_EMAIL_VERIFICATION, ...
186
- * ```
187
- */
188
- export function getStateDefinitionsByPriority(): StateDefinition[] {
189
- return [...STATE_DEFINITIONS].sort((a, b) => a.priority - b.priority);
190
- }
@@ -1,207 +0,0 @@
1
- import { AuthFlowState, AuthFlowContext, StateDefinition, ResponseMetadata } from './auth-flow-state-machine.types';
2
- import { AuthFlowContextBuilder } from './auth-flow-context-builder.service';
3
- import { NAuthLogger } from '../utils/nauth-logger';
4
- import { getStateDefinitionsByPriority, getStateDefinition } from './auth-flow-state-definitions';
5
-
6
- /**
7
- * Authentication Flow State Machine Service
8
- *
9
- * Core engine for evaluating authentication flow states using declarative rules.
10
- * Replaces imperative if/else logic with a rule-based state machine.
11
- *
12
- * **How it works:**
13
- * 1. Build context with pre-computed values
14
- * 2. Evaluate states in priority order (1-9)
15
- * 3. Select first state whose condition rule evaluates to true
16
- * 4. Execute onEnter hook if defined
17
- * 5. Return state with metadata
18
- *
19
- * **Benefits:**
20
- * - Declarative and maintainable
21
- * - Easy to test (pure functions)
22
- * - Extensible (add new states/rules easily)
23
- * - Clear priority ordering
24
- *
25
- * @example
26
- * ```typescript
27
- * const state = await stateMachine.evaluateState(context);
28
- * const definition = stateMachine.getStateDefinition(state);
29
- * ```
30
- */
31
- export class AuthFlowStateMachineService {
32
- constructor(
33
- private readonly contextBuilder: AuthFlowContextBuilder,
34
- private readonly logger?: NAuthLogger,
35
- ) {}
36
-
37
- /**
38
- * Evaluate authentication flow state
39
- *
40
- * Evaluates states in priority order and returns the first matching state.
41
- * Executes onEnter hook if defined for the selected state.
42
- *
43
- * @param context - Authentication flow context
44
- * @returns Evaluated state
45
- *
46
- * @example
47
- * ```typescript
48
- * const context = await contextBuilder.build({ user, config, authMethod: 'password' });
49
- * const state = await stateMachine.evaluateState(context);
50
- * // Returns: AuthFlowState.PENDING_EMAIL_VERIFICATION
51
- * ```
52
- */
53
- async evaluateState(context: AuthFlowContext): Promise<AuthFlowState> {
54
- // Get state definitions sorted by priority
55
- const stateDefinitions = getStateDefinitionsByPriority();
56
-
57
- this.logger?.debug?.(
58
- `[StateMachine] Evaluating states for user ${context.user.sub} (priority 1-9, first match wins)`,
59
- );
60
-
61
- // Evaluate states in priority order
62
- for (const definition of stateDefinitions) {
63
- // Evaluate condition rule
64
- const ruleResult = definition.condition(context);
65
- this.logger?.debug?.(
66
- `[StateMachine] Priority ${definition.priority}: ${definition.state} → ${ruleResult ? 'MATCH' : 'skip'}`,
67
- );
68
-
69
- if (ruleResult) {
70
- // State matches - execute onEnter hook if defined
71
- if (definition.onEnter) {
72
- this.logger?.debug?.(`[StateMachine] Executing onEnter hook for ${definition.state}`);
73
- try {
74
- await definition.onEnter(context);
75
- } catch (error) {
76
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
77
- this.logger?.warn?.(`onEnter hook failed for state ${definition.state}: ${errorMessage}`, {
78
- error,
79
- state: definition.state,
80
- userId: context.user.id,
81
- });
82
- // Continue with state selection even if hook fails
83
- }
84
- }
85
-
86
- this.logger?.debug?.(`[StateMachine] ✓ Selected state: ${definition.state} for user ${context.user.sub}`);
87
- return definition.state;
88
- }
89
- }
90
-
91
- // Fallback: Should never reach here (AUTHENTICATED always matches)
92
- // But return AUTHENTICATED as safe default
93
- this.logger?.warn?.(`No state matched for user ${context.user.sub} - falling back to AUTHENTICATED`, {
94
- userId: context.user.id,
95
- });
96
- return AuthFlowState.AUTHENTICATED;
97
- }
98
-
99
- /**
100
- * Get state definition by state
101
- *
102
- * @param state - State to get definition for
103
- * @returns State definition or undefined if not found
104
- *
105
- * @example
106
- * ```typescript
107
- * const def = stateMachine.getStateDefinition(AuthFlowState.PENDING_EMAIL_VERIFICATION);
108
- * ```
109
- */
110
- getStateDefinition(state: AuthFlowState): StateDefinition | undefined {
111
- return getStateDefinition(state);
112
- }
113
-
114
- /**
115
- * Build metadata for state response
116
- *
117
- * Calls buildMetadata function if defined for the state.
118
- *
119
- * @param state - State to build metadata for
120
- * @param context - Authentication flow context
121
- * @returns Metadata object or undefined
122
- *
123
- * @example
124
- * ```typescript
125
- * const metadata = await stateMachine.buildMetadata(state, context);
126
- * // Returns: { gracePeriodEndsAt: Date, riskScore: 45, riskLevel: 'medium' }
127
- * ```
128
- */
129
- buildMetadata(state: AuthFlowState, context: AuthFlowContext): ResponseMetadata | undefined {
130
- const definition = this.getStateDefinition(state);
131
- if (!definition || !definition.buildMetadata) {
132
- return undefined;
133
- }
134
-
135
- try {
136
- return definition.buildMetadata(context);
137
- } catch (error) {
138
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
139
- this.logger?.warn?.(`buildMetadata failed for state ${state}: ${errorMessage}`, {
140
- error,
141
- state,
142
- userId: context.user.id,
143
- });
144
- return undefined;
145
- }
146
- }
147
-
148
- /**
149
- * Transition after challenge completion
150
- *
151
- * Re-evaluates state after a challenge is completed.
152
- * This is used in the challenge completion flow to determine the next state.
153
- *
154
- * @param params - Transition parameters
155
- * @param params.completedChallenge - Challenge that was just completed
156
- * @param params.context - Current authentication flow context
157
- * @param params.updateFn - Function to update user data (e.g., mark email as verified)
158
- * @returns New state after transition
159
- *
160
- * @example
161
- * ```typescript
162
- * const newState = await stateMachine.transitionAfterChallenge({
163
- * completedChallenge: AuthChallenge.VERIFY_EMAIL,
164
- * context,
165
- * updateFn: async (user) => {
166
- * user.isEmailVerified = true;
167
- * await userRepository.save(user);
168
- * }
169
- * });
170
- * ```
171
- */
172
- async transitionAfterChallenge(params: {
173
- completedChallenge: string;
174
- context: AuthFlowContext;
175
- updateFn?: (user: AuthFlowContext['user']) => Promise<void>;
176
- }): Promise<AuthFlowState> {
177
- const { completedChallenge, context, updateFn } = params;
178
-
179
- // Update user data if update function provided
180
- if (updateFn) {
181
- try {
182
- await updateFn(context.user);
183
- } catch (error) {
184
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
185
- this.logger?.error?.(`Failed to update user after challenge completion: ${errorMessage}`, {
186
- error,
187
- challenge: completedChallenge,
188
- userId: context.user.id,
189
- });
190
- // Continue with re-evaluation even if update fails
191
- }
192
- }
193
-
194
- // Re-build context with updated user data
195
- const newContext = await this.contextBuilder.build({
196
- user: context.user,
197
- config: context.config,
198
- authMethod: context.authMethod,
199
- authProvider: context.authProvider,
200
- deviceToken: context.deviceToken,
201
- skipMFAVerification: context.skipMFAVerification,
202
- });
203
-
204
- // Re-evaluate state
205
- return this.evaluateState(newContext);
206
- }
207
- }