@payez/next-mvp 3.9.1 → 4.0.0

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 (147) hide show
  1. package/dist/api/auth-handler.d.ts +1 -2
  2. package/dist/api/auth-handler.js +9 -9
  3. package/dist/api-handlers/account/change-password.js +110 -112
  4. package/dist/api-handlers/admin/analytics.d.ts +19 -20
  5. package/dist/api-handlers/admin/analytics.js +378 -379
  6. package/dist/api-handlers/admin/audit.d.ts +19 -20
  7. package/dist/api-handlers/admin/audit.js +213 -214
  8. package/dist/api-handlers/admin/index.d.ts +21 -22
  9. package/dist/api-handlers/admin/index.js +42 -43
  10. package/dist/api-handlers/admin/redis-sessions.d.ts +35 -36
  11. package/dist/api-handlers/admin/redis-sessions.js +203 -204
  12. package/dist/api-handlers/admin/sessions.d.ts +20 -21
  13. package/dist/api-handlers/admin/sessions.js +283 -284
  14. package/dist/api-handlers/admin/site-logs.d.ts +45 -46
  15. package/dist/api-handlers/admin/site-logs.js +317 -318
  16. package/dist/api-handlers/admin/stats.d.ts +20 -21
  17. package/dist/api-handlers/admin/stats.js +239 -240
  18. package/dist/api-handlers/admin/users.d.ts +19 -20
  19. package/dist/api-handlers/admin/users.js +221 -222
  20. package/dist/api-handlers/admin/vibe-data.d.ts +79 -80
  21. package/dist/api-handlers/admin/vibe-data.js +267 -268
  22. package/dist/api-handlers/auth/refresh.js +633 -635
  23. package/dist/api-handlers/auth/signout.js +186 -187
  24. package/dist/api-handlers/auth/status.js +4 -7
  25. package/dist/api-handlers/auth/update-session.d.ts +1 -1
  26. package/dist/api-handlers/auth/update-session.js +12 -14
  27. package/dist/api-handlers/auth/verify-code.d.ts +43 -43
  28. package/dist/api-handlers/auth/verify-code.js +90 -94
  29. package/dist/api-handlers/session/viability.js +114 -146
  30. package/dist/api-handlers/test/force-expire.js +59 -65
  31. package/dist/auth/auth-decision.js +182 -182
  32. package/dist/auth/better-auth.d.ts +3 -6
  33. package/dist/auth/better-auth.js +3 -6
  34. package/dist/auth/route-config.js +2 -2
  35. package/dist/auth/utils/token-utils.d.ts +83 -84
  36. package/dist/auth/utils/token-utils.js +218 -219
  37. package/dist/client/AuthContext.js +115 -112
  38. package/dist/client/better-auth-client.d.ts +1020 -1020
  39. package/dist/client/fetch-with-auth.js +2 -2
  40. package/dist/components/SessionSync.js +121 -119
  41. package/dist/components/account/MobileNavDrawer.js +64 -64
  42. package/dist/components/account/UserAvatarMenu.js +91 -88
  43. package/dist/components/admin/VibeAdminLayout.js +71 -69
  44. package/dist/hooks/useAuth.js +9 -7
  45. package/dist/hooks/useAuthSettings.js +93 -93
  46. package/dist/hooks/useAvailableProviders.d.ts +43 -45
  47. package/dist/hooks/useAvailableProviders.js +112 -108
  48. package/dist/hooks/useSessionExpiration.d.ts +2 -3
  49. package/dist/hooks/useSessionExpiration.js +2 -2
  50. package/dist/hooks/useViabilitySession.js +3 -2
  51. package/dist/index.js +4 -6
  52. package/dist/lib/app-slug.d.ts +95 -95
  53. package/dist/lib/app-slug.js +172 -172
  54. package/dist/lib/standardized-client-api.js +10 -5
  55. package/dist/lib/startup-init.js +21 -25
  56. package/dist/lib/test-aware-get-token.js +86 -81
  57. package/dist/lib/token-lifecycle.d.ts +78 -52
  58. package/dist/lib/token-lifecycle.js +360 -398
  59. package/dist/pages/admin-login/page.js +73 -83
  60. package/dist/pages/client-admin/ClientSiteAdminPage.js +179 -177
  61. package/dist/pages/login/page.js +202 -211
  62. package/dist/pages/showcase/ShowcasePage.js +142 -140
  63. package/dist/pages/test-env/EmergencyLogoutPage.js +99 -98
  64. package/dist/pages/test-env/JwtInspectPage.js +116 -114
  65. package/dist/pages/test-env/RefreshTokenPage.js +4 -2
  66. package/dist/pages/test-env/TestEnvPage.js +51 -49
  67. package/dist/pages/verify-code/page.js +412 -408
  68. package/dist/routes/auth/logout.d.ts +31 -31
  69. package/dist/routes/auth/logout.js +98 -113
  70. package/dist/routes/auth/nextauth.d.ts +14 -11
  71. package/dist/routes/auth/nextauth.js +25 -57
  72. package/dist/routes/auth/session.js +157 -179
  73. package/dist/routes/auth/viability.js +190 -201
  74. package/dist/server/auth.d.ts +50 -0
  75. package/dist/server/auth.js +62 -0
  76. package/dist/stores/authStore.js +19 -23
  77. package/dist/utils/logout.js +5 -5
  78. package/package.json +1 -3
  79. package/src/api/auth-handler.ts +550 -549
  80. package/src/api-handlers/account/change-password.ts +5 -8
  81. package/src/api-handlers/admin/analytics.ts +4 -6
  82. package/src/api-handlers/admin/audit.ts +5 -7
  83. package/src/api-handlers/admin/index.ts +1 -2
  84. package/src/api-handlers/admin/redis-sessions.ts +6 -8
  85. package/src/api-handlers/admin/sessions.ts +5 -7
  86. package/src/api-handlers/admin/site-logs.ts +8 -10
  87. package/src/api-handlers/admin/stats.ts +4 -6
  88. package/src/api-handlers/admin/users.ts +5 -7
  89. package/src/api-handlers/admin/vibe-data.ts +10 -12
  90. package/src/api-handlers/auth/refresh.ts +5 -7
  91. package/src/api-handlers/auth/signout.ts +5 -6
  92. package/src/api-handlers/auth/status.ts +4 -7
  93. package/src/api-handlers/auth/update-session.ts +123 -125
  94. package/src/api-handlers/auth/verify-code.ts +9 -13
  95. package/src/api-handlers/session/viability.ts +10 -47
  96. package/src/api-handlers/test/force-expire.ts +4 -11
  97. package/src/auth/auth-decision.ts +1 -1
  98. package/src/auth/better-auth.ts +138 -141
  99. package/src/auth/route-config.ts +219 -219
  100. package/src/auth/utils/token-utils.ts +0 -1
  101. package/src/client/AuthContext.tsx +6 -2
  102. package/src/client/fetch-with-auth.ts +47 -47
  103. package/src/components/SessionSync.tsx +6 -5
  104. package/src/components/account/MobileNavDrawer.tsx +3 -3
  105. package/src/components/account/UserAvatarMenu.tsx +6 -3
  106. package/src/components/admin/VibeAdminLayout.tsx +4 -2
  107. package/src/config/logger.ts +1 -1
  108. package/src/hooks/useAuth.ts +117 -115
  109. package/src/hooks/useAuthSettings.ts +2 -2
  110. package/src/hooks/useAvailableProviders.ts +9 -5
  111. package/src/hooks/useSessionExpiration.ts +101 -102
  112. package/src/hooks/useViabilitySession.ts +336 -335
  113. package/src/index.ts +60 -63
  114. package/src/lib/api-handler.ts +0 -1
  115. package/src/lib/app-slug.ts +6 -6
  116. package/src/lib/standardized-client-api.ts +901 -895
  117. package/src/lib/startup-init.ts +243 -247
  118. package/src/lib/test-aware-get-token.ts +22 -12
  119. package/src/lib/token-lifecycle.ts +12 -53
  120. package/src/pages/admin-login/page.tsx +9 -17
  121. package/src/pages/client-admin/ClientSiteAdminPage.tsx +4 -2
  122. package/src/pages/login/page.tsx +21 -28
  123. package/src/pages/showcase/ShowcasePage.tsx +4 -2
  124. package/src/pages/test-env/EmergencyLogoutPage.tsx +7 -6
  125. package/src/pages/test-env/JwtInspectPage.tsx +5 -3
  126. package/src/pages/test-env/RefreshTokenPage.tsx +157 -155
  127. package/src/pages/test-env/TestEnvPage.tsx +4 -2
  128. package/src/pages/verify-code/page.tsx +10 -6
  129. package/src/routes/auth/logout.ts +7 -25
  130. package/src/routes/auth/nextauth.ts +45 -71
  131. package/src/routes/auth/session.ts +25 -50
  132. package/src/routes/auth/viability.ts +7 -19
  133. package/src/server/auth.ts +60 -0
  134. package/src/stores/authStore.ts +1899 -1904
  135. package/src/utils/logout.ts +30 -30
  136. package/src/auth/auth-options.ts +0 -237
  137. package/src/auth/callbacks/index.ts +0 -7
  138. package/src/auth/callbacks/jwt.ts +0 -382
  139. package/src/auth/callbacks/session.ts +0 -243
  140. package/src/auth/callbacks/signin.ts +0 -56
  141. package/src/auth/events/index.ts +0 -5
  142. package/src/auth/events/signout.ts +0 -33
  143. package/src/auth/providers/credentials.ts +0 -256
  144. package/src/auth/providers/index.ts +0 -6
  145. package/src/auth/providers/oauth.ts +0 -114
  146. package/src/lib/nextauth-secret.ts +0 -121
  147. package/src/types/next-auth.d.ts +0 -15
@@ -1,243 +0,0 @@
1
- /**
2
- * Session Callback
3
- *
4
- * Builds the NextAuth session from Redis session data.
5
- * The JWT only contains redisSessionId - all user data comes from Redis.
6
- *
7
- * FLOW:
8
- * 1. Extract redisSessionId from JWT token
9
- * 2. Fetch session data from Redis
10
- * 3. Build NextAuth session with user info
11
- *
12
- * @version 1.0.0
13
- * @since auth-refactor-2026-01
14
- */
15
-
16
- import type { Session } from 'next-auth';
17
- import type { JWT } from 'next-auth/jwt';
18
- import { getSession } from '../../lib/session-store';
19
- // NOTE: Using SessionData from models until Phase 3 normalizes names
20
- import type { SessionData } from '../../models/SessionModel';
21
-
22
- // ============================================================================
23
- // TYPES
24
- // ============================================================================
25
-
26
- interface SessionCallbackParams {
27
- session: Session;
28
- token: JWT & {
29
- /** Redis session ID - the key to look up session data */
30
- redisSessionId?: string;
31
- error?: string;
32
- };
33
- }
34
-
35
- interface AppSessionUser {
36
- id: string;
37
- email: string;
38
- name?: string;
39
- roles: string[];
40
- // MFA status
41
- twoFactorSessionVerified: boolean;
42
- requiresTwoFactor: boolean;
43
- // MFA claims from IDP token
44
- authenticationMethods?: string[];
45
- authenticationLevel?: string;
46
- mfaCompletedAt?: number;
47
- mfaExpiresAt?: number;
48
- mfaValidityHours?: number;
49
- // OAuth provider info
50
- oauthProvider?: string;
51
- // Multi-tenant IDP info
52
- idpClientId?: string;
53
- merchantId?: string;
54
- // JWT signing key (from header, NOT client_id from payload)
55
- bearerKeyId?: string;
56
- }
57
-
58
- interface AppSession extends Omit<Session, 'user'> {
59
- user: AppSessionUser;
60
- sessionToken?: string;
61
- accessToken?: string;
62
- refreshToken?: string;
63
- accessTokenExpires?: number;
64
- error?: string;
65
- }
66
-
67
- // ============================================================================
68
- // SESSION CALLBACK
69
- // ============================================================================
70
-
71
- /**
72
- * Session callback - builds NextAuth session from Redis.
73
- *
74
- * This callback is called whenever getSession() or useSession() is used.
75
- * It fetches the full session from Redis and exposes it to the client.
76
- *
77
- * @param params - Session callback parameters from NextAuth
78
- * @returns AppSession with user data from Redis
79
- */
80
- export async function sessionCallback({
81
- session,
82
- token,
83
- }: SessionCallbackParams): Promise<AppSession> {
84
- // Support both field names: sessionToken (auth.ts JWT) and redisSessionId (legacy)
85
- const redisSessionId = (token as any)?.sessionToken || token?.redisSessionId;
86
-
87
- console.log('[SESSION_CALLBACK] Entry:', {
88
- hasToken: !!token,
89
- redisSessionId: redisSessionId || 'MISSING',
90
- tokenError: token?.error || 'none',
91
- tokenKeys: token ? Object.keys(token) : [],
92
- });
93
-
94
- // -------------------------------------------------------------------------
95
- // Handle Token Errors
96
- // -------------------------------------------------------------------------
97
-
98
- if (token.error) {
99
- console.log('[SESSION_CALLBACK] Token has error:', token.error);
100
- // Special case: MFA expired - return partial session for step-up flow
101
- if (token.error === 'MfaExpired' && redisSessionId) {
102
- const sessionData = await safeGetSession(redisSessionId as string);
103
- if (sessionData) {
104
- return {
105
- ...session,
106
- user: {
107
- id: sessionData.userId,
108
- email: sessionData.email,
109
- name: sessionData.name,
110
- roles: sessionData.roles || [],
111
- twoFactorSessionVerified: false,
112
- requiresTwoFactor: true,
113
- authenticationMethods: sessionData.authenticationMethods,
114
- authenticationLevel: sessionData.authenticationLevel,
115
- mfaExpiresAt: sessionData.mfaExpiresAt,
116
- },
117
- sessionToken: redisSessionId as string,
118
- accessToken: sessionData.idpAccessToken,
119
- refreshToken: sessionData.idpRefreshToken,
120
- error: 'MfaExpired',
121
- };
122
- }
123
- }
124
-
125
- // For other errors, try to recover session data if possible
126
- if (redisSessionId) {
127
- const sessionData = await safeGetSession(redisSessionId as string);
128
- if (sessionData) {
129
- return buildSessionFromRedis(session, redisSessionId as string, sessionData);
130
- }
131
- }
132
-
133
- // No recovery possible - return error session
134
- return buildErrorSession(session, token.error);
135
- }
136
-
137
- // -------------------------------------------------------------------------
138
- // Validate Session Token
139
- // -------------------------------------------------------------------------
140
-
141
- if (!redisSessionId) {
142
- console.log('[SESSION_CALLBACK] No redisSessionId - returning error session');
143
- return buildErrorSession(session, 'NoSessionToken');
144
- }
145
-
146
- // -------------------------------------------------------------------------
147
- // Fetch Session from Redis
148
- // -------------------------------------------------------------------------
149
-
150
- const sessionData = await safeGetSession(redisSessionId as string);
151
-
152
- if (!sessionData) {
153
- console.log('[SESSION_CALLBACK] Redis session not found for:', redisSessionId);
154
- return buildErrorSession(session, 'SessionNotFound');
155
- }
156
-
157
- console.log('[SESSION_CALLBACK] Redis session found:', {
158
- userId: sessionData.userId,
159
- email: sessionData.email,
160
- roles: sessionData.roles,
161
- hasAccessToken: !!sessionData.idpAccessToken,
162
- });
163
-
164
- const result = buildSessionFromRedis(session, redisSessionId as string, sessionData);
165
- console.log('[SESSION_CALLBACK] Returning:', {
166
- userId: result.user.id,
167
- roles: result.user.roles,
168
- hasAccessToken: !!result.accessToken,
169
- });
170
- return result;
171
- }
172
-
173
- // ============================================================================
174
- // HELPER FUNCTIONS
175
- // ============================================================================
176
-
177
- /**
178
- * Safely fetch session from Redis, returning null on error.
179
- */
180
- async function safeGetSession(
181
- sessionId: string
182
- ): Promise<SessionData | null> {
183
- try {
184
- return await getSession(sessionId);
185
- } catch {
186
- return null;
187
- }
188
- }
189
-
190
- /**
191
- * Build complete session from Redis data.
192
- * Uses normalized field names from SessionData.
193
- */
194
- function buildSessionFromRedis(
195
- session: Session,
196
- sessionId: string,
197
- data: SessionData
198
- ): AppSession {
199
- return {
200
- ...session,
201
- user: {
202
- id: data.userId,
203
- email: data.email,
204
- name: data.name,
205
- roles: data.roles || [],
206
- // MFA state (normalized field name)
207
- twoFactorSessionVerified: data.mfaVerified,
208
- requiresTwoFactor: !data.mfaVerified,
209
- authenticationMethods: data.authenticationMethods,
210
- authenticationLevel: data.authenticationLevel,
211
- mfaCompletedAt: data.mfaCompletedAt,
212
- mfaExpiresAt: data.mfaExpiresAt,
213
- mfaValidityHours: data.mfaValidityHours,
214
- oauthProvider: data.oauthProvider,
215
- idpClientId: data.idpClientId,
216
- merchantId: data.merchantId,
217
- // Bearer key ID from JWT header (may be undefined for old sessions)
218
- bearerKeyId: data.bearerKeyId,
219
- },
220
- sessionToken: sessionId,
221
- // IDP tokens (normalized field names)
222
- accessToken: data.idpAccessToken,
223
- refreshToken: data.idpRefreshToken,
224
- accessTokenExpires: data.idpAccessTokenExpires,
225
- };
226
- }
227
-
228
- /**
229
- * Build error session with empty user.
230
- */
231
- function buildErrorSession(session: Session, error: string): AppSession {
232
- return {
233
- ...session,
234
- user: {
235
- id: '',
236
- email: '',
237
- roles: [],
238
- twoFactorSessionVerified: false,
239
- requiresTwoFactor: false,
240
- },
241
- error,
242
- };
243
- }
@@ -1,56 +0,0 @@
1
- /**
2
- * SignIn Callback
3
- *
4
- * Handles post-authentication actions like 2FA redirect.
5
- * Called after credentials or OAuth authentication succeeds.
6
- *
7
- * @version 1.0.0
8
- * @since auth-refactor-2026-01
9
- */
10
-
11
- import type { User, Account } from 'next-auth';
12
-
13
- // ============================================================================
14
- // SIGNIN CALLBACK
15
- // ============================================================================
16
-
17
- /**
18
- * SignIn callback - handle 2FA redirect for OAuth users.
19
- *
20
- * When require2FA is true for the client, OAuth users need to be
21
- * redirected to the verify-code page immediately after OAuth login.
22
- *
23
- * @param params - SignIn callback parameters from NextAuth
24
- * @returns true to allow sign-in, or a URL string to redirect
25
- */
26
- export async function signInCallback({
27
- user,
28
- account,
29
- }: {
30
- user: User | any;
31
- account: Account | null;
32
- }): Promise<boolean | string> {
33
- // Only handle OAuth providers (credentials flow handles 2FA separately)
34
- if (!account?.provider || account.provider === 'credentials') {
35
- return true;
36
- }
37
-
38
- // Check if OAuth user needs 2FA redirect
39
- const token = user as any;
40
- if (token?.requiresTwoFactorRedirect) {
41
- // Preserve the original callback URL through 2FA flow
42
- const originalCallbackUrl = (account as any)?.callbackUrl || '/';
43
-
44
- // Don't redirect back to auth pages after 2FA
45
- const safeCallbackUrl = originalCallbackUrl.startsWith('/account-auth/')
46
- ? '/'
47
- : originalCallbackUrl;
48
-
49
- const encodedCallback = encodeURIComponent(safeCallbackUrl);
50
-
51
- // Return redirect URL - NextAuth will redirect here instead of completing sign-in
52
- return `/account-auth/verify-code?callbackUrl=${encodedCallback}`;
53
- }
54
-
55
- return true;
56
- }
@@ -1,5 +0,0 @@
1
- /**
2
- * Auth Events - Public Exports
3
- */
4
-
5
- export { handleSignOut } from './signout';
@@ -1,33 +0,0 @@
1
- /**
2
- * SignOut Event Handler
3
- *
4
- * Cleans up Redis session when user signs out.
5
- *
6
- * @version 1.0.0
7
- * @since auth-refactor-2026-01
8
- */
9
-
10
- import type { JWT } from 'next-auth/jwt';
11
- import { deleteSession } from '../../lib/session-store';
12
-
13
- // ============================================================================
14
- // SIGNOUT EVENT
15
- // ============================================================================
16
-
17
- /**
18
- * Handle user sign out by deleting Redis session.
19
- *
20
- * @param token - The JWT token containing the session ID
21
- */
22
- export async function handleSignOut({ token }: { token: JWT | null }): Promise<void> {
23
- // Support both field names: sessionToken (auth.ts JWT) and redisSessionId (legacy)
24
- const redisSessionId = (token as any)?.sessionToken || (token as any)?.redisSessionId;
25
-
26
- if (redisSessionId) {
27
- try {
28
- await deleteSession(redisSessionId);
29
- } catch (error) {
30
- console.error('[SIGNOUT_EVENT] Failed to delete session:', error);
31
- }
32
- }
33
- }
@@ -1,256 +0,0 @@
1
- /**
2
- * Credentials Provider
3
- *
4
- * Handles email/password authentication via PayEz IDP.
5
- * Creates Redis session and returns minimal user object to NextAuth.
6
- *
7
- * FLOW:
8
- * 1. User submits email/password
9
- * 2. We call IDP /api/ExternalAuth/login
10
- * 3. IDP returns tokens if credentials valid
11
- * 4. We create Redis session with tokens
12
- * 5. Return user object with redisSessionId to NextAuth
13
- *
14
- * @version 1.0.0
15
- * @since auth-refactor-2026-01
16
- */
17
-
18
- import CredentialsProvider from 'next-auth/providers/credentials';
19
- import { createSession } from '../../lib/session-store';
20
- import { idpLogin } from '../utils/idp-client';
21
- import {
22
- decodeIdpAccessToken,
23
- extractEmailFromToken,
24
- extractRolesFromToken,
25
- extractAmrFromToken,
26
- expClaimToMs,
27
- extractKidFromToken,
28
- } from '../utils/token-utils';
29
- import type { AuthorizeResult, LoginCredentials } from '../types/auth-types';
30
- import { toRedisSessionId } from '../types/auth-types';
31
- // NOTE: Using any for sessionData until Phase 3 normalizes types
32
-
33
- // ============================================================================
34
- // CREDENTIALS PROVIDER
35
- // ============================================================================
36
-
37
- /**
38
- * Create the CredentialsProvider for NextAuth.
39
- *
40
- * This provider handles email/password login. The authorize function
41
- * is called when a user submits the login form.
42
- */
43
- export function createCredentialsProvider() {
44
- return CredentialsProvider({
45
- id: 'credentials',
46
- name: 'Credentials',
47
- credentials: {
48
- email: { label: 'Email', type: 'email' },
49
- password: { label: 'Password', type: 'password' },
50
- },
51
- authorize: authorizeCredentials,
52
- });
53
- }
54
-
55
- /**
56
- * Authorize user with email/password.
57
- *
58
- * This is the core authentication function. It:
59
- * 1. Validates credentials with IDP
60
- * 2. Decodes the returned tokens
61
- * 3. Creates a Redis session
62
- * 4. Returns user info for NextAuth JWT
63
- *
64
- * @param credentials - Email and password from login form
65
- * @param req - The incoming request (for IP/UA forwarding)
66
- * @returns User object for NextAuth, or null/throws on failure
67
- */
68
- async function authorizeCredentials(
69
- credentials: Record<'email' | 'password', string> | undefined,
70
- req: any
71
- ): Promise<AuthorizeResult | null> {
72
- // -------------------------------------------------------------------------
73
- // Validate Input
74
- // -------------------------------------------------------------------------
75
-
76
- if (!credentials?.email || !credentials?.password) {
77
- throw new Error('Email and password required');
78
- }
79
-
80
- const loginCredentials: LoginCredentials = {
81
- email: credentials.email,
82
- password: credentials.password,
83
- };
84
-
85
- // Extract client info for audit logging
86
- const clientHeaders = extractClientHeaders(req);
87
-
88
- // -------------------------------------------------------------------------
89
- // Call IDP
90
- // -------------------------------------------------------------------------
91
-
92
- const loginResult = await idpLogin(loginCredentials, clientHeaders);
93
-
94
- if (!loginResult.success || !loginResult.result) {
95
- // Build structured error for frontend
96
- const errorResponse = buildAuthError(loginResult.error);
97
- throw new Error(JSON.stringify(errorResponse));
98
- }
99
-
100
- const { access_token, refresh_token, user: idpUser } = loginResult.result;
101
-
102
- // -------------------------------------------------------------------------
103
- // Decode Token
104
- // -------------------------------------------------------------------------
105
-
106
- const decoded = decodeIdpAccessToken(access_token);
107
- if (!decoded) {
108
- throw new Error('Failed to decode token');
109
- }
110
-
111
- // Extract kid from JWT header (CRITICAL: this is different from client_id in payload)
112
- const bearerKeyId = extractKidFromToken(access_token);
113
- if (bearerKeyId) {
114
- console.log('[CREDENTIALS] Extracted bearerKeyId (kid) from JWT header:', bearerKeyId);
115
- } else {
116
- console.warn('[CREDENTIALS] No kid found in JWT header - token may be unsigned or malformed');
117
- }
118
-
119
- // Extract claims from token
120
- const email = extractEmailFromToken(decoded);
121
- const roles = extractRolesFromToken(decoded);
122
- const amrClaims = extractAmrFromToken(decoded);
123
- const acrLevel = decoded.acr || '1';
124
- const userId = decoded.sub;
125
-
126
- // Check if 2FA is complete based on ACR level
127
- // ACR=1: Provisional token (requires 2FA)
128
- // ACR=2: Full authentication (2FA complete)
129
- const mfaVerified = acrLevel === '2';
130
-
131
- // Decode refresh token expiry if available
132
- let refreshTokenExpires: number | undefined;
133
- try {
134
- const refreshDecoded = decodeIdpAccessToken(refresh_token);
135
- if (refreshDecoded?.exp) {
136
- refreshTokenExpires = expClaimToMs(refreshDecoded.exp);
137
- }
138
- } catch {
139
- // Ignore - will use default expiry
140
- }
141
-
142
- // -------------------------------------------------------------------------
143
- // Create Redis Session
144
- // -------------------------------------------------------------------------
145
-
146
- // Using normalized field names (session-store handles backward compatibility)
147
- const sessionData: any = {
148
- userId,
149
- email,
150
- roles,
151
- // IDP tokens (normalized names)
152
- idpAccessToken: access_token,
153
- idpRefreshToken: refresh_token,
154
- idpAccessTokenExpires: expClaimToMs(decoded.exp),
155
- idpRefreshTokenExpires: refreshTokenExpires,
156
- decodedAccessToken: decoded,
157
- // Bearer key ID from JWT header (NOT client_id from payload)
158
- bearerKeyId,
159
- // MFA state (normalized names)
160
- mfaVerified,
161
- authenticationMethods: amrClaims,
162
- authenticationLevel: acrLevel,
163
- // MFA timing info from token
164
- mfaCompletedAt: decoded.mfa_time ? expClaimToMs(decoded.mfa_time) : undefined,
165
- mfaExpiresAt: decoded.mfa_expires ? expClaimToMs(decoded.mfa_expires) : undefined,
166
- mfaValidityHours: decoded.mfa_validity_hours,
167
- };
168
-
169
- // Determine MFA method from IDP user info
170
- let mfaMethod: 'email' | 'sms' | 'totp' | undefined;
171
- if (idpUser?.isEmailConfirmed) {
172
- mfaMethod = 'email';
173
- } else if (idpUser?.isSmsConfirmed) {
174
- mfaMethod = 'sms';
175
- }
176
-
177
- if (mfaMethod) {
178
- sessionData.mfaMethod = mfaMethod;
179
- }
180
-
181
- // Create the Redis session
182
- const redisSessionId = await createSession(sessionData);
183
-
184
- // -------------------------------------------------------------------------
185
- // Return User Object for NextAuth
186
- // -------------------------------------------------------------------------
187
-
188
- // NextAuth requires 'id' field - we use userId from IDP
189
- // The redisSessionId is passed through to the JWT callback
190
- return {
191
- id: userId,
192
- email,
193
- roles,
194
- redisSessionId: toRedisSessionId(redisSessionId),
195
- mfaRequired: !mfaVerified,
196
- mfaMethod,
197
- };
198
- }
199
-
200
- // ============================================================================
201
- // HELPER FUNCTIONS
202
- // ============================================================================
203
-
204
- /**
205
- * Extract client headers from request for audit logging.
206
- */
207
- function extractClientHeaders(req: any): { ip?: string; userAgent?: string } {
208
- const headers: { ip?: string; userAgent?: string } = {};
209
-
210
- // Extract client IP
211
- const forwardedFor = req?.headers?.['x-forwarded-for'];
212
- const realIp = req?.headers?.['x-real-ip'];
213
-
214
- if (forwardedFor) {
215
- const ip = Array.isArray(forwardedFor) ? forwardedFor[0] : forwardedFor.split(',')[0].trim();
216
- headers.ip = ip;
217
- } else if (realIp) {
218
- headers.ip = Array.isArray(realIp) ? realIp[0] : realIp;
219
- }
220
-
221
- // Extract User-Agent
222
- const userAgent = req?.headers?.['user-agent'];
223
- if (userAgent) {
224
- headers.userAgent = Array.isArray(userAgent) ? userAgent[0] : userAgent;
225
- }
226
-
227
- return headers;
228
- }
229
-
230
- /**
231
- * Build structured error response for frontend.
232
- *
233
- * The frontend expects a specific error structure to display
234
- * appropriate messages and handle things like lockout.
235
- */
236
- function buildAuthError(error?: { code: string; message: string; details?: any }): object {
237
- if (!error) {
238
- return {
239
- success: false,
240
- error: {
241
- code: 'AUTH_ERROR',
242
- message: 'Authentication failed',
243
- details: {},
244
- },
245
- };
246
- }
247
-
248
- return {
249
- success: false,
250
- error: {
251
- code: error.code || 'AUTH_ERROR',
252
- message: error.message || 'Authentication failed',
253
- details: error.details || {},
254
- },
255
- };
256
- }
@@ -1,6 +0,0 @@
1
- /**
2
- * Auth Providers - Public Exports
3
- */
4
-
5
- export * from './credentials';
6
- export * from './oauth';