@payez/next-mvp 3.9.0 → 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 (149) 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 -961
  39. package/dist/client/better-auth-client.js +54 -7
  40. package/dist/client/fetch-with-auth.js +2 -2
  41. package/dist/components/SessionSync.js +121 -119
  42. package/dist/components/account/MobileNavDrawer.js +64 -64
  43. package/dist/components/account/UserAvatarMenu.js +91 -88
  44. package/dist/components/admin/VibeAdminLayout.js +71 -69
  45. package/dist/hooks/useAuth.js +9 -7
  46. package/dist/hooks/useAuthSettings.js +93 -93
  47. package/dist/hooks/useAvailableProviders.d.ts +43 -45
  48. package/dist/hooks/useAvailableProviders.js +112 -108
  49. package/dist/hooks/useSessionExpiration.d.ts +2 -3
  50. package/dist/hooks/useSessionExpiration.js +2 -2
  51. package/dist/hooks/useViabilitySession.js +3 -2
  52. package/dist/index.js +4 -6
  53. package/dist/lib/app-slug.d.ts +95 -95
  54. package/dist/lib/app-slug.js +172 -172
  55. package/dist/lib/standardized-client-api.js +10 -5
  56. package/dist/lib/startup-init.js +21 -25
  57. package/dist/lib/test-aware-get-token.js +86 -81
  58. package/dist/lib/token-lifecycle.d.ts +78 -52
  59. package/dist/lib/token-lifecycle.js +360 -398
  60. package/dist/pages/admin-login/page.js +73 -83
  61. package/dist/pages/client-admin/ClientSiteAdminPage.js +179 -177
  62. package/dist/pages/login/page.js +202 -211
  63. package/dist/pages/showcase/ShowcasePage.js +142 -140
  64. package/dist/pages/test-env/EmergencyLogoutPage.js +99 -98
  65. package/dist/pages/test-env/JwtInspectPage.js +116 -114
  66. package/dist/pages/test-env/RefreshTokenPage.js +4 -2
  67. package/dist/pages/test-env/TestEnvPage.js +51 -49
  68. package/dist/pages/verify-code/page.js +412 -408
  69. package/dist/routes/auth/logout.d.ts +31 -31
  70. package/dist/routes/auth/logout.js +98 -113
  71. package/dist/routes/auth/nextauth.d.ts +14 -11
  72. package/dist/routes/auth/nextauth.js +25 -57
  73. package/dist/routes/auth/session.js +157 -179
  74. package/dist/routes/auth/viability.js +190 -201
  75. package/dist/server/auth.d.ts +50 -0
  76. package/dist/server/auth.js +62 -0
  77. package/dist/stores/authStore.js +19 -23
  78. package/dist/utils/logout.js +5 -5
  79. package/package.json +1 -3
  80. package/src/api/auth-handler.ts +550 -549
  81. package/src/api-handlers/account/change-password.ts +5 -8
  82. package/src/api-handlers/admin/analytics.ts +4 -6
  83. package/src/api-handlers/admin/audit.ts +5 -7
  84. package/src/api-handlers/admin/index.ts +1 -2
  85. package/src/api-handlers/admin/redis-sessions.ts +6 -8
  86. package/src/api-handlers/admin/sessions.ts +5 -7
  87. package/src/api-handlers/admin/site-logs.ts +8 -10
  88. package/src/api-handlers/admin/stats.ts +4 -6
  89. package/src/api-handlers/admin/users.ts +5 -7
  90. package/src/api-handlers/admin/vibe-data.ts +10 -12
  91. package/src/api-handlers/auth/refresh.ts +5 -7
  92. package/src/api-handlers/auth/signout.ts +5 -6
  93. package/src/api-handlers/auth/status.ts +4 -7
  94. package/src/api-handlers/auth/update-session.ts +123 -125
  95. package/src/api-handlers/auth/verify-code.ts +9 -13
  96. package/src/api-handlers/session/viability.ts +10 -47
  97. package/src/api-handlers/test/force-expire.ts +4 -11
  98. package/src/auth/auth-decision.ts +1 -1
  99. package/src/auth/better-auth.ts +138 -141
  100. package/src/auth/route-config.ts +219 -219
  101. package/src/auth/utils/token-utils.ts +0 -1
  102. package/src/client/AuthContext.tsx +6 -2
  103. package/src/client/better-auth-client.ts +54 -7
  104. package/src/client/fetch-with-auth.ts +47 -47
  105. package/src/components/SessionSync.tsx +6 -5
  106. package/src/components/account/MobileNavDrawer.tsx +3 -3
  107. package/src/components/account/UserAvatarMenu.tsx +6 -3
  108. package/src/components/admin/VibeAdminLayout.tsx +4 -2
  109. package/src/config/logger.ts +1 -1
  110. package/src/hooks/useAuth.ts +117 -115
  111. package/src/hooks/useAuthSettings.ts +2 -2
  112. package/src/hooks/useAvailableProviders.ts +9 -5
  113. package/src/hooks/useSessionExpiration.ts +101 -102
  114. package/src/hooks/useViabilitySession.ts +336 -335
  115. package/src/index.ts +60 -63
  116. package/src/lib/api-handler.ts +0 -1
  117. package/src/lib/app-slug.ts +6 -6
  118. package/src/lib/standardized-client-api.ts +901 -895
  119. package/src/lib/startup-init.ts +243 -247
  120. package/src/lib/test-aware-get-token.ts +22 -12
  121. package/src/lib/token-lifecycle.ts +12 -53
  122. package/src/pages/admin-login/page.tsx +9 -17
  123. package/src/pages/client-admin/ClientSiteAdminPage.tsx +4 -2
  124. package/src/pages/login/page.tsx +21 -28
  125. package/src/pages/showcase/ShowcasePage.tsx +4 -2
  126. package/src/pages/test-env/EmergencyLogoutPage.tsx +7 -6
  127. package/src/pages/test-env/JwtInspectPage.tsx +5 -3
  128. package/src/pages/test-env/RefreshTokenPage.tsx +157 -155
  129. package/src/pages/test-env/TestEnvPage.tsx +4 -2
  130. package/src/pages/verify-code/page.tsx +10 -6
  131. package/src/routes/auth/logout.ts +7 -25
  132. package/src/routes/auth/nextauth.ts +45 -71
  133. package/src/routes/auth/session.ts +25 -50
  134. package/src/routes/auth/viability.ts +7 -19
  135. package/src/server/auth.ts +60 -0
  136. package/src/stores/authStore.ts +1899 -1904
  137. package/src/utils/logout.ts +30 -30
  138. package/src/auth/auth-options.ts +0 -237
  139. package/src/auth/callbacks/index.ts +0 -7
  140. package/src/auth/callbacks/jwt.ts +0 -382
  141. package/src/auth/callbacks/session.ts +0 -243
  142. package/src/auth/callbacks/signin.ts +0 -56
  143. package/src/auth/events/index.ts +0 -5
  144. package/src/auth/events/signout.ts +0 -33
  145. package/src/auth/providers/credentials.ts +0 -256
  146. package/src/auth/providers/index.ts +0 -6
  147. package/src/auth/providers/oauth.ts +0 -114
  148. package/src/lib/nextauth-secret.ts +0 -121
  149. package/src/types/next-auth.d.ts +0 -15
@@ -1,201 +1,190 @@
1
- "use strict";
2
- /**
3
- * Ready-to-Use Session Viability Route
4
- *
5
- * Checks if the current session is viable (valid and not expired).
6
- * Used by client-side code to determine if a refresh is needed.
7
- *
8
- * @example
9
- * ```typescript
10
- * // app/api/session/viability/route.ts
11
- * export { GET } from '@payez/next-mvp/routes/auth/viability';
12
- * ```
13
- *
14
- * @version 2.0.0
15
- * @since auth-ready-v2
16
- */
17
- Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.GET = GET;
19
- const server_1 = require("next/server");
20
- const jwt_1 = require("next-auth/jwt");
21
- const session_store_1 = require("../../lib/session-store");
22
- const app_slug_1 = require("../../lib/app-slug");
23
- const idp_client_config_1 = require("../../lib/idp-client-config");
24
- /**
25
- * Get NextAuth secret from IDP config (cached).
26
- * NEVER use process.env.NEXTAUTH_SECRET at module level - it may not be set yet.
27
- */
28
- async function getNextAuthSecret() {
29
- const config = await (0, idp_client_config_1.getIDPClientConfig)();
30
- return config.nextAuthSecret || '';
31
- }
32
- /**
33
- * Get tenant-wide 2FA requirement from cached client config (from broker handshake)
34
- */
35
- async function getTenantRequiresTwoFactor() {
36
- try {
37
- const config = await (0, idp_client_config_1.getIDPClientConfig)();
38
- return config.authSettings?.require2FA ?? true; // Default to true for security
39
- }
40
- catch {
41
- console.warn('[VIABILITY] Could not get client config, defaulting tenantRequiresTwoFactor to true');
42
- return true;
43
- }
44
- }
45
- /**
46
- * GET /api/session/viability - Check if session is viable
47
- *
48
- * Returns:
49
- * - viable: boolean - Whether the session can be used
50
- * - needsRefresh: boolean - Whether a refresh is recommended
51
- * - expiresIn: number - Seconds until token expires
52
- */
53
- async function GET(req) {
54
- try {
55
- const cookieName = (0, app_slug_1.getJwtCookieName)();
56
- const secret = await getNextAuthSecret();
57
- const token = await (0, jwt_1.getToken)({ req, secret, cookieName });
58
- if (!token) {
59
- return server_1.NextResponse.json({
60
- viable: false,
61
- needsRefresh: false,
62
- authenticated: false,
63
- reason: 'No session found'
64
- });
65
- }
66
- // Support both field names: sessionToken (auth.ts JWT) and redisSessionId (legacy)
67
- const sessionToken = token.sessionToken || token.redisSessionId;
68
- const session = sessionToken ? await (0, session_store_1.getSession)(sessionToken) : null;
69
- // CRITICAL: Detect stale cookie state (JWT exists but Redis session missing)
70
- if (sessionToken && !session) {
71
- console.warn('[VIABILITY] Stale cookie detected - session not in Redis');
72
- return server_1.NextResponse.json({
73
- viable: false,
74
- needsRefresh: false,
75
- authenticated: false,
76
- sessionToken, // Return sessionToken so middleware can detect and clear stale cookie
77
- reason: 'Stale session - cookie exists but session not found in Redis'
78
- });
79
- }
80
- // Check access token expiry
81
- const now = Math.floor(Date.now() / 1000);
82
- const accessTokenExpires = token.accessTokenExpires || token.exp;
83
- if (!accessTokenExpires) {
84
- // No expiry info, assume viable but recommend refresh
85
- const tenantRequiresTwoFactor = await getTenantRequiresTwoFactor();
86
- // CRITICAL: Check if MFA has expired (2FA TTL enforcement)
87
- const mfaExpiresAt = session?.mfaExpiresAt || 0;
88
- const mfaExpired = mfaExpiresAt > 0 && mfaExpiresAt < Date.now();
89
- // Check mfaVerified (normalized name) with fallback to twoFactorComplete for compatibility
90
- const mfaVerifiedInSession = session?.mfaVerified ?? session?.twoFactorComplete ?? false;
91
- // User has completed 2FA requirements if: they verified AND it hasn't expired
92
- const userHasCompletedTenantTwoFactorRequirements = mfaVerifiedInSession && !mfaExpired;
93
- // userStillNeedsTwoFactor = inverse of completed (matches session callback logic)
94
- const userStillNeedsTwoFactor = !userHasCompletedTenantTwoFactorRequirements;
95
- return server_1.NextResponse.json({
96
- viable: true,
97
- needsRefresh: true,
98
- authenticated: true,
99
- sessionToken,
100
- // Clear names for middleware decision-making
101
- tenantRequiresTwoFactor,
102
- userHasCompletedTenantTwoFactorRequirements,
103
- userStillNeedsTwoFactor,
104
- // Legacy field names for backwards compatibility
105
- requires2FA: tenantRequiresTwoFactor,
106
- twoFactorComplete: userHasCompletedTenantTwoFactorRequirements,
107
- accessTokenExpired: false,
108
- reason: 'No expiry information'
109
- });
110
- }
111
- // Convert to seconds if needed
112
- const expiryTime = accessTokenExpires > 1000000000000
113
- ? Math.floor(accessTokenExpires / 1000)
114
- : accessTokenExpires;
115
- const expiresIn = expiryTime - now;
116
- const isExpired = expiresIn <= 0;
117
- const needsRefresh = expiresIn <= 300; // 5 minutes buffer
118
- // Check if we have refresh capability (check normalized field name first)
119
- const hasRefreshToken = !!(session?.idpRefreshToken || session?.refreshToken || token.refreshToken);
120
- // CLEAR NAMING: Tenant-wide 2FA requirement from client config
121
- const tenantRequiresTwoFactor = await getTenantRequiresTwoFactor();
122
- // CRITICAL: Check if MFA has expired (2FA TTL enforcement)
123
- // The session may have mfaVerified=true from days ago, but if mfaExpiresAt
124
- // has passed, we must treat 2FA as incomplete to force re-verification.
125
- const mfaExpiresAt = session?.mfaExpiresAt || 0;
126
- const mfaExpired = mfaExpiresAt > 0 && mfaExpiresAt < Date.now();
127
- // Check mfaVerified (normalized name) with fallback to twoFactorComplete for compatibility
128
- const mfaVerifiedInSession = session?.mfaVerified ?? session?.twoFactorComplete ?? false;
129
- // DEBUG: Log what we're reading from the session
130
- console.log('[VIABILITY] Session 2FA state:', {
131
- sessionToken: sessionToken?.substring(0, 8) + '...',
132
- 'session.mfaVerified': session?.mfaVerified,
133
- 'session.twoFactorComplete': session?.twoFactorComplete,
134
- mfaVerifiedInSession,
135
- mfaExpiresAt,
136
- mfaExpired,
137
- hasRefreshToken,
138
- 'session.idpRefreshToken': !!session?.idpRefreshToken,
139
- 'session.refreshToken': !!session?.refreshToken,
140
- });
141
- // CLEAR NAMING: User has completed 2FA requirements if: they verified AND it hasn't expired
142
- const userHasCompletedTenantTwoFactorRequirements = mfaVerifiedInSession && !mfaExpired;
143
- // userStillNeedsTwoFactor = inverse of completed (matches session callback logic)
144
- const userStillNeedsTwoFactor = !userHasCompletedTenantTwoFactorRequirements;
145
- if (mfaExpired && mfaVerifiedInSession) {
146
- console.warn('[VIABILITY] MFA expired - forcing 2FA re-verification');
147
- }
148
- if (isExpired) {
149
- return server_1.NextResponse.json({
150
- viable: false,
151
- needsRefresh: hasRefreshToken,
152
- expiresIn: 0,
153
- hasRefreshToken,
154
- authenticated: true,
155
- sessionToken,
156
- // Clear names
157
- tenantRequiresTwoFactor,
158
- userHasCompletedTenantTwoFactorRequirements,
159
- userStillNeedsTwoFactor,
160
- // Legacy names for backwards compatibility
161
- requires2FA: tenantRequiresTwoFactor,
162
- twoFactorComplete: userHasCompletedTenantTwoFactorRequirements,
163
- accessTokenExpired: true,
164
- reason: 'Token expired',
165
- // RBAC fields
166
- roles: session?.roles || [],
167
- clientId: session?.idpClientId || process.env.IDP_CLIENT_ID || '',
168
- });
169
- }
170
- return server_1.NextResponse.json({
171
- viable: true,
172
- needsRefresh,
173
- expiresIn,
174
- hasRefreshToken,
175
- authenticated: true,
176
- sessionToken,
177
- // Clear names
178
- tenantRequiresTwoFactor,
179
- userHasCompletedTenantTwoFactorRequirements,
180
- userStillNeedsTwoFactor,
181
- // Legacy names for backwards compatibility
182
- requires2FA: tenantRequiresTwoFactor,
183
- twoFactorComplete: userHasCompletedTenantTwoFactorRequirements,
184
- accessTokenExpired: false,
185
- expiresAt: new Date(expiryTime * 1000).toISOString(),
186
- // RBAC fields
187
- roles: session?.roles || [],
188
- clientId: session?.idpClientId || process.env.IDP_CLIENT_ID || '',
189
- });
190
- }
191
- catch (error) {
192
- console.error('[VIABILITY_ROUTE] Error checking session viability:', error);
193
- return server_1.NextResponse.json({
194
- viable: false,
195
- needsRefresh: false,
196
- authenticated: false,
197
- error: 'Failed to check session',
198
- details: error instanceof Error ? error.message : 'Unknown error'
199
- }, { status: 500 });
200
- }
201
- }
1
+ "use strict";
2
+ /**
3
+ * Ready-to-Use Session Viability Route
4
+ *
5
+ * Checks if the current session is viable (valid and not expired).
6
+ * Used by client-side code to determine if a refresh is needed.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * // app/api/session/viability/route.ts
11
+ * export { GET } from '@payez/next-mvp/routes/auth/viability';
12
+ * ```
13
+ *
14
+ * @version 2.0.0
15
+ * @since auth-ready-v2
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.GET = GET;
19
+ const server_1 = require("next/server");
20
+ const auth_1 = require("../../server/auth");
21
+ const session_store_1 = require("../../lib/session-store");
22
+ const idp_client_config_1 = require("../../lib/idp-client-config");
23
+ /**
24
+ * Get tenant-wide 2FA requirement from cached client config (from broker handshake)
25
+ */
26
+ async function getTenantRequiresTwoFactor() {
27
+ try {
28
+ const config = await (0, idp_client_config_1.getIDPClientConfig)();
29
+ return config.authSettings?.require2FA ?? true; // Default to true for security
30
+ }
31
+ catch {
32
+ console.warn('[VIABILITY] Could not get client config, defaulting tenantRequiresTwoFactor to true');
33
+ return true;
34
+ }
35
+ }
36
+ /**
37
+ * GET /api/session/viability - Check if session is viable
38
+ *
39
+ * Returns:
40
+ * - viable: boolean - Whether the session can be used
41
+ * - needsRefresh: boolean - Whether a refresh is recommended
42
+ * - expiresIn: number - Seconds until token expires
43
+ */
44
+ async function GET(req) {
45
+ try {
46
+ const baSession = await (0, auth_1.getSession)(req);
47
+ if (!baSession) {
48
+ return server_1.NextResponse.json({
49
+ viable: false,
50
+ needsRefresh: false,
51
+ authenticated: false,
52
+ reason: 'No session found'
53
+ });
54
+ }
55
+ const token = baSession;
56
+ const sessionToken = baSession.session?.token;
57
+ const session = sessionToken ? await (0, session_store_1.getSession)(sessionToken) : null;
58
+ // CRITICAL: Detect stale cookie state (JWT exists but Redis session missing)
59
+ if (sessionToken && !session) {
60
+ console.warn('[VIABILITY] Stale cookie detected - session not in Redis');
61
+ return server_1.NextResponse.json({
62
+ viable: false,
63
+ needsRefresh: false,
64
+ authenticated: false,
65
+ sessionToken, // Return sessionToken so middleware can detect and clear stale cookie
66
+ reason: 'Stale session - cookie exists but session not found in Redis'
67
+ });
68
+ }
69
+ // Check access token expiry
70
+ const now = Math.floor(Date.now() / 1000);
71
+ const accessTokenExpires = token.accessTokenExpires || token.exp;
72
+ if (!accessTokenExpires) {
73
+ // No expiry info, assume viable but recommend refresh
74
+ const tenantRequiresTwoFactor = await getTenantRequiresTwoFactor();
75
+ // CRITICAL: Check if MFA has expired (2FA TTL enforcement)
76
+ const mfaExpiresAt = session?.mfaExpiresAt || 0;
77
+ const mfaExpired = mfaExpiresAt > 0 && mfaExpiresAt < Date.now();
78
+ // Check mfaVerified (normalized name) with fallback to twoFactorComplete for compatibility
79
+ const mfaVerifiedInSession = session?.mfaVerified ?? session?.twoFactorComplete ?? false;
80
+ // User has completed 2FA requirements if: they verified AND it hasn't expired
81
+ const userHasCompletedTenantTwoFactorRequirements = mfaVerifiedInSession && !mfaExpired;
82
+ // userStillNeedsTwoFactor = inverse of completed (matches session callback logic)
83
+ const userStillNeedsTwoFactor = !userHasCompletedTenantTwoFactorRequirements;
84
+ return server_1.NextResponse.json({
85
+ viable: true,
86
+ needsRefresh: true,
87
+ authenticated: true,
88
+ sessionToken,
89
+ // Clear names for middleware decision-making
90
+ tenantRequiresTwoFactor,
91
+ userHasCompletedTenantTwoFactorRequirements,
92
+ userStillNeedsTwoFactor,
93
+ // Legacy field names for backwards compatibility
94
+ requires2FA: tenantRequiresTwoFactor,
95
+ twoFactorComplete: userHasCompletedTenantTwoFactorRequirements,
96
+ accessTokenExpired: false,
97
+ reason: 'No expiry information'
98
+ });
99
+ }
100
+ // Convert to seconds if needed
101
+ const expiryTime = accessTokenExpires > 1000000000000
102
+ ? Math.floor(accessTokenExpires / 1000)
103
+ : accessTokenExpires;
104
+ const expiresIn = expiryTime - now;
105
+ const isExpired = expiresIn <= 0;
106
+ const needsRefresh = expiresIn <= 300; // 5 minutes buffer
107
+ // Check if we have refresh capability (check normalized field name first)
108
+ const hasRefreshToken = !!(session?.idpRefreshToken || session?.refreshToken || token.refreshToken);
109
+ // CLEAR NAMING: Tenant-wide 2FA requirement from client config
110
+ const tenantRequiresTwoFactor = await getTenantRequiresTwoFactor();
111
+ // CRITICAL: Check if MFA has expired (2FA TTL enforcement)
112
+ // The session may have mfaVerified=true from days ago, but if mfaExpiresAt
113
+ // has passed, we must treat 2FA as incomplete to force re-verification.
114
+ const mfaExpiresAt = session?.mfaExpiresAt || 0;
115
+ const mfaExpired = mfaExpiresAt > 0 && mfaExpiresAt < Date.now();
116
+ // Check mfaVerified (normalized name) with fallback to twoFactorComplete for compatibility
117
+ const mfaVerifiedInSession = session?.mfaVerified ?? session?.twoFactorComplete ?? false;
118
+ // DEBUG: Log what we're reading from the session
119
+ console.log('[VIABILITY] Session 2FA state:', {
120
+ sessionToken: sessionToken?.substring(0, 8) + '...',
121
+ 'session.mfaVerified': session?.mfaVerified,
122
+ 'session.twoFactorComplete': session?.twoFactorComplete,
123
+ mfaVerifiedInSession,
124
+ mfaExpiresAt,
125
+ mfaExpired,
126
+ hasRefreshToken,
127
+ 'session.idpRefreshToken': !!session?.idpRefreshToken,
128
+ 'session.refreshToken': !!session?.refreshToken,
129
+ });
130
+ // CLEAR NAMING: User has completed 2FA requirements if: they verified AND it hasn't expired
131
+ const userHasCompletedTenantTwoFactorRequirements = mfaVerifiedInSession && !mfaExpired;
132
+ // userStillNeedsTwoFactor = inverse of completed (matches session callback logic)
133
+ const userStillNeedsTwoFactor = !userHasCompletedTenantTwoFactorRequirements;
134
+ if (mfaExpired && mfaVerifiedInSession) {
135
+ console.warn('[VIABILITY] MFA expired - forcing 2FA re-verification');
136
+ }
137
+ if (isExpired) {
138
+ return server_1.NextResponse.json({
139
+ viable: false,
140
+ needsRefresh: hasRefreshToken,
141
+ expiresIn: 0,
142
+ hasRefreshToken,
143
+ authenticated: true,
144
+ sessionToken,
145
+ // Clear names
146
+ tenantRequiresTwoFactor,
147
+ userHasCompletedTenantTwoFactorRequirements,
148
+ userStillNeedsTwoFactor,
149
+ // Legacy names for backwards compatibility
150
+ requires2FA: tenantRequiresTwoFactor,
151
+ twoFactorComplete: userHasCompletedTenantTwoFactorRequirements,
152
+ accessTokenExpired: true,
153
+ reason: 'Token expired',
154
+ // RBAC fields
155
+ roles: session?.roles || [],
156
+ clientId: session?.idpClientId || process.env.IDP_CLIENT_ID || '',
157
+ });
158
+ }
159
+ return server_1.NextResponse.json({
160
+ viable: true,
161
+ needsRefresh,
162
+ expiresIn,
163
+ hasRefreshToken,
164
+ authenticated: true,
165
+ sessionToken,
166
+ // Clear names
167
+ tenantRequiresTwoFactor,
168
+ userHasCompletedTenantTwoFactorRequirements,
169
+ userStillNeedsTwoFactor,
170
+ // Legacy names for backwards compatibility
171
+ requires2FA: tenantRequiresTwoFactor,
172
+ twoFactorComplete: userHasCompletedTenantTwoFactorRequirements,
173
+ accessTokenExpired: false,
174
+ expiresAt: new Date(expiryTime * 1000).toISOString(),
175
+ // RBAC fields
176
+ roles: session?.roles || [],
177
+ clientId: session?.idpClientId || process.env.IDP_CLIENT_ID || '',
178
+ });
179
+ }
180
+ catch (error) {
181
+ console.error('[VIABILITY_ROUTE] Error checking session viability:', error);
182
+ return server_1.NextResponse.json({
183
+ viable: false,
184
+ needsRefresh: false,
185
+ authenticated: false,
186
+ error: 'Failed to check session',
187
+ details: error instanceof Error ? error.message : 'Unknown error'
188
+ }, { status: 500 });
189
+ }
190
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Server-side auth utilities for Better Auth (v4.0)
3
+ *
4
+ * Replaces:
5
+ * - getToken() from next-auth/jwt
6
+ * - getServerSession() from next-auth
7
+ *
8
+ * All server-side auth flows go through the Better Auth instance.
9
+ */
10
+ import 'server-only';
11
+ /**
12
+ * Get the initialized Better Auth instance (singleton).
13
+ */
14
+ export declare function getAuthInstance(): Promise<import("better-auth/types").Auth<{
15
+ secret: string;
16
+ socialProviders: Record<string, import("../auth/better-auth").BetterAuthSocialProvider>;
17
+ trustedOrigins: string[];
18
+ session: {
19
+ cookieCache: {
20
+ enabled: true;
21
+ maxAge: number;
22
+ refreshCache: true;
23
+ };
24
+ };
25
+ plugins: [{
26
+ id: "next-cookies";
27
+ hooks: {
28
+ before: {
29
+ matcher(ctx: import("@better-auth/core").HookEndpointContext): boolean;
30
+ handler: (inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<void>;
31
+ }[];
32
+ after: {
33
+ matcher(ctx: import("@better-auth/core").HookEndpointContext): true;
34
+ handler: (inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<void>;
35
+ }[];
36
+ };
37
+ }];
38
+ }>>;
39
+ /**
40
+ * Get the current session from a request.
41
+ * Replaces getToken() and getServerSession().
42
+ *
43
+ * Returns the session object or null if not authenticated.
44
+ */
45
+ export declare function getSession(request?: Request): Promise<any>;
46
+ /**
47
+ * Get the current session, throwing if not authenticated.
48
+ * Use in API handlers that require auth.
49
+ */
50
+ export declare function requireSession(request: Request): Promise<any>;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ /**
3
+ * Server-side auth utilities for Better Auth (v4.0)
4
+ *
5
+ * Replaces:
6
+ * - getToken() from next-auth/jwt
7
+ * - getServerSession() from next-auth
8
+ *
9
+ * All server-side auth flows go through the Better Auth instance.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.getAuthInstance = getAuthInstance;
13
+ exports.getSession = getSession;
14
+ exports.requireSession = requireSession;
15
+ require("server-only");
16
+ const better_auth_1 = require("../auth/better-auth");
17
+ const idp_client_config_1 = require("../lib/idp-client-config");
18
+ let authInstance = null;
19
+ let authInitPromise = null;
20
+ /**
21
+ * Get the initialized Better Auth instance (singleton).
22
+ */
23
+ async function getAuthInstance() {
24
+ if (authInstance)
25
+ return authInstance;
26
+ if (!authInitPromise) {
27
+ authInitPromise = (0, idp_client_config_1.getIDPClientConfig)().then(config => {
28
+ authInstance = (0, better_auth_1.createBetterAuthInstance)(config);
29
+ return authInstance;
30
+ });
31
+ }
32
+ return authInitPromise;
33
+ }
34
+ /**
35
+ * Get the current session from a request.
36
+ * Replaces getToken() and getServerSession().
37
+ *
38
+ * Returns the session object or null if not authenticated.
39
+ */
40
+ async function getSession(request) {
41
+ const auth = await getAuthInstance();
42
+ if (!request)
43
+ return null;
44
+ try {
45
+ const session = await auth.api.getSession({ headers: request.headers });
46
+ return session;
47
+ }
48
+ catch {
49
+ return null;
50
+ }
51
+ }
52
+ /**
53
+ * Get the current session, throwing if not authenticated.
54
+ * Use in API handlers that require auth.
55
+ */
56
+ async function requireSession(request) {
57
+ const session = await getSession(request);
58
+ if (!session) {
59
+ throw new Error('Unauthorized');
60
+ }
61
+ return session;
62
+ }
@@ -49,7 +49,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
49
49
  exports.initializeAuthStore = exports.useAuthStore = void 0;
50
50
  const zustand_1 = require("zustand");
51
51
  const middleware_1 = require("zustand/middleware");
52
- const react_1 = require("next-auth/react");
52
+ const better_auth_client_1 = require("../client/better-auth-client");
53
53
  const session_1 = require("../lib/session");
54
54
  const logger_1 = require("../config/logger");
55
55
  const signalr_1 = require("@microsoft/signalr");
@@ -254,18 +254,19 @@ exports.useAuthStore = (0, zustand_1.create)()((0, middleware_1.devtools)((set,
254
254
  signIn: async (credentials) => {
255
255
  set({ isLoading: true, error: null });
256
256
  try {
257
- // Use NextAuth signIn - this will trigger our auth callbacks
258
- const { signIn } = await Promise.resolve().then(() => __importStar(require('next-auth/react')));
259
- const result = await signIn('credentials', {
260
- ...credentials,
261
- redirect: false,
257
+ // Use Better Auth signIn
258
+ const result = await better_auth_client_1.authClient.signIn.email({
259
+ email: credentials.email,
260
+ password: credentials.password,
262
261
  });
263
- if (result?.ok) {
262
+ if (result?.data) {
264
263
  logger_1.authLogger.info('[AuthStore] Sign in successful');
265
264
  return true;
266
265
  }
267
266
  else {
268
- const errorMessage = result?.error || 'Sign in failed';
267
+ const errorMessage = result?.error
268
+ ? (typeof result.error === 'object' ? result.error.message : String(result.error))
269
+ : 'Sign in failed';
269
270
  set({ error: errorMessage, isLoading: false });
270
271
  return false;
271
272
  }
@@ -291,8 +292,8 @@ exports.useAuthStore = (0, zustand_1.create)()((0, middleware_1.devtools)((set,
291
292
  rolesLastFetch: null,
292
293
  });
293
294
  try {
294
- // Use NextAuth signOut
295
- await (0, react_1.signOut)({ redirect: false });
295
+ // Use Better Auth signOut
296
+ await better_auth_client_1.authClient.signOut();
296
297
  logger_1.authLogger.info('[AuthStore] Sign out completed');
297
298
  }
298
299
  catch (error) {
@@ -361,9 +362,8 @@ exports.useAuthStore = (0, zustand_1.create)()((0, middleware_1.devtools)((set,
361
362
  }
362
363
  });
363
364
  }
364
- // Step 6: Force NextAuth signOut (this should clear the session cookie)
365
- const { signOut } = await Promise.resolve().then(() => __importStar(require('next-auth/react')));
366
- await signOut({ redirect: false });
365
+ // Step 6: Force Better Auth signOut (this should clear the session cookie)
366
+ await better_auth_client_1.authClient.signOut();
367
367
  logger_1.authLogger.info('[AuthStore] Force logout completed, redirecting to login');
368
368
  // Step 7: Longer delay to ensure everything is processed
369
369
  await new Promise(resolve => setTimeout(resolve, 1000));
@@ -421,9 +421,8 @@ exports.useAuthStore = (0, zustand_1.create)()((0, middleware_1.devtools)((set,
421
421
  if (token)
422
422
  return token;
423
423
  try {
424
- const { getSession } = await Promise.resolve().then(() => __importStar(require('next-auth/react')));
425
424
  for (let attempt = 1; attempt <= 3 && !token; attempt++) {
426
- const s = await getSession();
425
+ const { data: s } = await better_auth_client_1.authClient.getSession();
427
426
  token = s?.sessionToken;
428
427
  if (!token) {
429
428
  await new Promise(r => setTimeout(r, 150));
@@ -431,7 +430,7 @@ exports.useAuthStore = (0, zustand_1.create)()((0, middleware_1.devtools)((set,
431
430
  }
432
431
  }
433
432
  catch {
434
- logger_1.authLogger.warn('[AuthStore] Failed to resolve session token from NextAuth during refresh');
433
+ logger_1.authLogger.warn('[AuthStore] Failed to resolve session token from Better Auth during refresh');
435
434
  }
436
435
  return token;
437
436
  };
@@ -504,15 +503,12 @@ exports.useAuthStore = (0, zustand_1.create)()((0, middleware_1.devtools)((set,
504
503
  logger_1.authLogger.info('[AuthStore] Starting session rehydration after token refresh');
505
504
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
506
505
  try {
507
- // Force NextAuth to reload the session from the session store
508
- // This triggers the JWT callback which reads fresh data from Redis
509
- const { getSession } = await Promise.resolve().then(() => __importStar(require('next-auth/react')));
506
+ // Force Better Auth to reload the session
510
507
  logger_1.authLogger.debug(`[AuthStore] Rehydration attempt ${attempt}/${maxRetries}`);
511
- // Force session refresh - this calls NextAuth's session endpoint
512
- // which triggers JWT callback to read fresh tokens from Redis
513
- const freshSession = await getSession();
508
+ // Force session refresh via Better Auth
509
+ const { data: freshSession } = await better_auth_client_1.authClient.getSession();
514
510
  if (!freshSession) {
515
- throw new Error('No session returned from NextAuth after refresh');
511
+ throw new Error('No session returned from Better Auth after refresh');
516
512
  }
517
513
  if (!freshSession.accessToken) {
518
514
  throw new Error('Fresh session missing access token');
@@ -9,7 +9,7 @@
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
11
  exports.logout = logout;
12
- const react_1 = require("next-auth/react");
12
+ const better_auth_client_1 = require("../client/better-auth-client");
13
13
  /**
14
14
  * Sign out the current user and redirect to login
15
15
  *
@@ -17,10 +17,10 @@ const react_1 = require("next-auth/react");
17
17
  */
18
18
  async function logout(redirectUrl = '/account-auth/login') {
19
19
  try {
20
- await (0, react_1.signOut)({
21
- callbackUrl: redirectUrl,
22
- redirect: true,
23
- });
20
+ await better_auth_client_1.authClient.signOut();
21
+ if (typeof window !== 'undefined') {
22
+ window.location.href = redirectUrl;
23
+ }
24
24
  }
25
25
  catch (error) {
26
26
  console.error('Logout error:', error);