@payez/next-mvp 4.1.10 → 4.1.20
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.
- package/dist/api-handlers/account/profile.d.ts +38 -0
- package/dist/api-handlers/account/profile.js +73 -45
- package/dist/api-handlers/account/recovery/send-code.d.ts +33 -0
- package/dist/api-handlers/account/recovery/send-code.js +52 -18
- package/dist/api-handlers/account/recovery/verify-code.d.ts +33 -0
- package/dist/api-handlers/account/recovery/verify-code.js +52 -18
- package/dist/api-handlers/account/verify-email.js +15 -1
- package/dist/api-handlers/account/verify-sms.js +15 -1
- package/dist/api-handlers/auth/passkey-signin.d.ts +29 -0
- package/dist/api-handlers/auth/passkey-signin.js +97 -0
- package/dist/api-handlers/mfa/backup-code-verify.d.ts +73 -0
- package/dist/api-handlers/mfa/backup-code-verify.js +153 -0
- package/dist/api-handlers/mfa/backup-codes.d.ts +7 -0
- package/dist/api-handlers/mfa/backup-codes.js +40 -0
- package/dist/api-handlers/mfa/methods.d.ts +3 -0
- package/dist/api-handlers/mfa/methods.js +41 -0
- package/dist/api-handlers/mfa/totp-confirm.d.ts +8 -0
- package/dist/api-handlers/mfa/totp-confirm.js +41 -0
- package/dist/api-handlers/mfa/totp-enroll.d.ts +8 -0
- package/dist/api-handlers/mfa/totp-enroll.js +41 -0
- package/dist/api-handlers/mfa/totp-verify.d.ts +47 -0
- package/dist/api-handlers/mfa/totp-verify.js +134 -0
- package/dist/api-handlers/passkeys/list.d.ts +7 -0
- package/dist/api-handlers/passkeys/list.js +29 -0
- package/dist/api-handlers/passkeys/manage.d.ts +3 -0
- package/dist/api-handlers/passkeys/manage.js +54 -0
- package/dist/api-handlers/passkeys/register-options.d.ts +8 -0
- package/dist/api-handlers/passkeys/register-options.js +41 -0
- package/dist/api-handlers/passkeys/register-verify.d.ts +7 -0
- package/dist/api-handlers/passkeys/register-verify.js +40 -0
- package/dist/api-handlers/session/viability.js +19 -0
- package/dist/auth/better-auth.d.ts +68 -5
- package/dist/auth/better-auth.js +271 -45
- package/dist/auth/utils/idp-client.d.ts +13 -22
- package/dist/auth/utils/idp-client.js +12 -82
- package/dist/client/better-auth-client.d.ts +14 -2
- package/dist/lib/ensure-fresh-access-token.js +13 -2
- package/dist/lib/session-store.js +28 -21
- package/dist/lib/startup-init.js +19 -19
- package/dist/lib/token-lifecycle.d.ts +2 -0
- package/dist/lib/token-lifecycle.js +61 -7
- package/dist/models/SessionModel.d.ts +17 -0
- package/dist/pages/login/page.d.ts +10 -1
- package/dist/pages/login/page.js +69 -80
- package/dist/pages/verify-code/page.d.ts +10 -1
- package/dist/pages/verify-code/page.js +72 -4
- package/dist/routes/auth/passkey-signin.d.ts +13 -0
- package/dist/routes/auth/passkey-signin.js +18 -0
- package/dist/routes/auth/viability.d.ts +2 -0
- package/dist/routes/auth/viability.js +88 -0
- package/dist/routes/mfa/backup-code-verify.d.ts +1 -0
- package/dist/routes/mfa/backup-code-verify.js +5 -0
- package/dist/routes/mfa/backup-codes.d.ts +10 -0
- package/dist/routes/mfa/backup-codes.js +14 -0
- package/dist/routes/mfa/methods.d.ts +21 -0
- package/dist/routes/mfa/methods.js +10 -0
- package/dist/routes/mfa/totp-confirm.d.ts +10 -0
- package/dist/routes/mfa/totp-confirm.js +14 -0
- package/dist/routes/mfa/totp-enroll.d.ts +10 -0
- package/dist/routes/mfa/totp-enroll.js +14 -0
- package/dist/routes/mfa/totp-verify.d.ts +14 -0
- package/dist/routes/mfa/totp-verify.js +18 -0
- package/dist/routes/passkeys/list.d.ts +10 -0
- package/dist/routes/passkeys/list.js +14 -0
- package/dist/routes/passkeys/manage.d.ts +18 -0
- package/dist/routes/passkeys/manage.js +13 -0
- package/dist/routes/passkeys/register-options.d.ts +10 -0
- package/dist/routes/passkeys/register-options.js +14 -0
- package/dist/routes/passkeys/register-verify.d.ts +10 -0
- package/dist/routes/passkeys/register-verify.js +14 -0
- package/dist/server/auth.d.ts +2 -2
- package/dist/server/decode-session.js +37 -3
- package/package.json +958 -893
- package/scripts/check-internal-url-usage.sh +73 -73
- package/src/api/index.ts +17 -17
- package/src/api-handlers/account/masked-info.ts +45 -45
- package/src/api-handlers/account/profile.ts +99 -80
- package/src/api-handlers/account/recovery/initiate.ts +23 -23
- package/src/api-handlers/account/recovery/send-code.ts +73 -25
- package/src/api-handlers/account/recovery/verify-code.ts +73 -25
- package/src/api-handlers/account/reset-password.ts +23 -23
- package/src/api-handlers/account/send-code.ts +76 -76
- package/src/api-handlers/account/validate-password.ts +118 -118
- package/src/api-handlers/account/verify-email.ts +15 -1
- package/src/api-handlers/account/verify-sms.ts +15 -1
- package/src/api-handlers/admin/stats.ts +249 -249
- package/src/api-handlers/anon/preferences.ts +123 -123
- package/src/api-handlers/auth/jwks.ts +20 -20
- package/src/api-handlers/auth/login.ts +240 -240
- package/src/api-handlers/auth/passkey-signin.ts +98 -0
- package/src/api-handlers/auth/validate.ts +43 -43
- package/src/api-handlers/mfa/backup-code-verify.ts +175 -0
- package/src/api-handlers/mfa/backup-codes.ts +41 -0
- package/src/api-handlers/mfa/methods.ts +46 -0
- package/src/api-handlers/mfa/totp-confirm.ts +42 -0
- package/src/api-handlers/mfa/totp-enroll.ts +42 -0
- package/src/api-handlers/mfa/totp-verify.ts +156 -0
- package/src/api-handlers/passkeys/list.ts +31 -0
- package/src/api-handlers/passkeys/manage.ts +54 -0
- package/src/api-handlers/passkeys/register-options.ts +42 -0
- package/src/api-handlers/passkeys/register-verify.ts +41 -0
- package/src/api-handlers/session/viability.ts +161 -142
- package/src/auth/better-auth.ts +708 -455
- package/src/auth/unauthenticated-routes.ts +2 -2
- package/src/auth/utils/idp-client.ts +13 -95
- package/src/client/better-auth-client.ts +70 -70
- package/src/client/fetchWithSession.ts +21 -21
- package/src/client/index.ts +13 -13
- package/src/client/useAnonSession.ts +131 -131
- package/src/components/admin/VibeAdminContext.tsx +87 -87
- package/src/config/logging-config.ts +82 -82
- package/src/config/unauthenticated-routes.ts +19 -19
- package/src/edge/internal-api-url.ts +65 -65
- package/src/edge/middleware.ts +42 -42
- package/src/hooks/useProfile.ts +75 -75
- package/src/lib/anon-session.ts +213 -213
- package/src/lib/auth-secret.ts +137 -137
- package/src/lib/demo-mode.ts +17 -17
- package/src/lib/ensure-fresh-access-token.ts +13 -2
- package/src/lib/idp-client-config.ts +559 -559
- package/src/lib/idp-fetch.ts +126 -126
- package/src/lib/internal-api.ts +171 -171
- package/src/lib/jwt-decode-client.ts +45 -45
- package/src/lib/rate-limit-service.ts +9 -9
- package/src/lib/redis.ts +26 -26
- package/src/lib/refresh-token-validator.ts +64 -64
- package/src/lib/secret-validation.ts +8 -8
- package/src/lib/session-store.ts +702 -695
- package/src/lib/session.ts +34 -34
- package/src/lib/startup-init.ts +251 -251
- package/src/lib/token-expiry.ts +40 -40
- package/src/lib/token-lifecycle.ts +533 -470
- package/src/lib/types/api-responses.ts +336 -336
- package/src/middleware/auth-decision.ts +43 -43
- package/src/models/DecodedAccessToken.ts +16 -16
- package/src/models/SessionModel.ts +282 -264
- package/src/pages/admin-roles/index.ts +9 -9
- package/src/pages/login/page.tsx +123 -228
- package/src/pages/security/EnhancedSecurityPage.tsx +9 -9
- package/src/pages/verify-code/page.tsx +71 -5
- package/src/routes/account/index.ts +31 -31
- package/src/routes/account/masked-info.ts +36 -36
- package/src/routes/account/send-code.ts +39 -39
- package/src/routes/account/verify-email.ts +40 -40
- package/src/routes/account/verify-sms.ts +40 -40
- package/src/routes/auth/index.ts +22 -22
- package/src/routes/auth/passkey-signin.ts +13 -0
- package/src/routes/auth/refresh.ts +48 -48
- package/src/routes/auth/session.ts +166 -166
- package/src/routes/auth/viability.ts +89 -0
- package/src/routes/index.ts +17 -17
- package/src/routes/mfa/backup-code-verify.ts +1 -0
- package/src/routes/mfa/backup-codes.ts +10 -0
- package/src/routes/mfa/methods.ts +22 -0
- package/src/routes/mfa/totp-confirm.ts +10 -0
- package/src/routes/mfa/totp-enroll.ts +10 -0
- package/src/routes/mfa/totp-verify.ts +14 -0
- package/src/routes/passkeys/list.ts +10 -0
- package/src/routes/passkeys/manage.ts +23 -0
- package/src/routes/passkeys/register-options.ts +10 -0
- package/src/routes/passkeys/register-verify.ts +10 -0
- package/src/routes/session/index.ts +7 -7
- package/src/routes/session/refresh-viability.ts +17 -17
- package/src/server/auth.ts +373 -373
- package/src/server/decode-session.ts +235 -202
- package/src/services/signalrActivityService.ts +258 -258
- package/src/theme/ThemeProvider.tsx +39 -39
- package/src/theme/default.ts +33 -33
- package/src/theme/index.ts +31 -31
- package/src/theme/types.ts +69 -69
- package/src/theme/useTheme.ts +57 -57
- package/src/theme/utils.ts +40 -40
- package/src/types/api.ts +12 -12
- package/src/types/auth.d.ts +23 -23
- package/src/types/auth.ts +22 -22
- package/src/types/logging.ts +11 -11
- package/src/types/recovery.ts +54 -54
- package/src/utils/api.ts +353 -353
- package/src/utils/circuitBreaker.ts +40 -40
- package/src/utils/error-message.ts +108 -108
- package/src/utils/layout/reservedSpace.ts +124 -124
|
@@ -1,143 +1,162 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Session Viability Check API Handler for `@payez/next-mvp`
|
|
3
|
-
*
|
|
4
|
-
* This API route is called by the middleware to securely check if a session is valid.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
8
|
-
import { getSession as getRedisSession, getBetterAuthSession } from '../../lib/session-store';
|
|
9
|
-
import { getSession } from '../../server/auth';
|
|
10
|
-
import { isInitializationFailed, ensureInitialized } from '../../lib/startup-init';
|
|
11
|
-
import { getIDPClientConfig } from '../../lib/idp-client-config';
|
|
12
|
-
|
|
13
|
-
export async function GET(req: NextRequest) {
|
|
14
|
-
try {
|
|
15
|
-
// Ensure initialization is complete (auth signing secret resolved from IDP)
|
|
16
|
-
if (!process.env.BETTER_AUTH_SECRET && !process.env.NEXTAUTH_SECRET) {
|
|
17
|
-
try {
|
|
18
|
-
await ensureInitialized();
|
|
19
|
-
} catch (error) {
|
|
20
|
-
// Initialization failed - return 503
|
|
21
|
-
console.error('[API Viability] Initialization failed - returning 503');
|
|
22
|
-
return NextResponse.json(
|
|
23
|
-
{
|
|
24
|
-
error: 'Service Unavailable',
|
|
25
|
-
message: 'Authentication service is not properly configured',
|
|
26
|
-
code: 'AUTH_NOT_INITIALIZED'
|
|
27
|
-
},
|
|
28
|
-
{ status: 503 }
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Double-check after initialization attempt
|
|
34
|
-
if (isInitializationFailed()) {
|
|
35
|
-
console.error('[API Viability] Initialization failed - returning 503');
|
|
36
|
-
return NextResponse.json(
|
|
37
|
-
{
|
|
38
|
-
error: 'Service Unavailable',
|
|
39
|
-
message: 'Authentication service is not properly configured',
|
|
40
|
-
code: 'AUTH_NOT_INITIALIZED'
|
|
41
|
-
},
|
|
42
|
-
{ status: 503 }
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Get session from Better Auth
|
|
47
|
-
const betterAuthSession = await getSession(req);
|
|
48
|
-
|
|
49
|
-
// Debug logging
|
|
50
|
-
if (!betterAuthSession) {
|
|
51
|
-
console.warn('[VIABILITY] getSession returned null');
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const sessionToken = betterAuthSession?.session?.token as string | undefined;
|
|
55
|
-
if (betterAuthSession && sessionToken) {
|
|
56
|
-
// Try legacy session store first, then Better Auth format
|
|
57
|
-
let sessionData = await getRedisSession(sessionToken);
|
|
58
|
-
if (!sessionData) {
|
|
59
|
-
// Better Auth stores sessions with ba:{appSlug}:{token} prefix
|
|
60
|
-
sessionData = await getBetterAuthSession(sessionToken);
|
|
61
|
-
if (sessionData) {
|
|
62
|
-
console.log('[VIABILITY] Found session in Better Auth store');
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
if (sessionData) {
|
|
66
|
-
// The session exists in Redis
|
|
67
|
-
|
|
68
|
-
// Check if access token is expired (for middleware decision-making)
|
|
69
|
-
const accessTokenExpires = sessionData.idpAccessTokenExpires || 0;
|
|
70
|
-
const accessTokenExpired = accessTokenExpires < Date.now();
|
|
71
|
-
|
|
72
|
-
// Get requires2FA from cached client config (client-wide setting)
|
|
73
|
-
let requires2FA = true; // Default to true for security
|
|
74
|
-
try {
|
|
75
|
-
const cachedConfig = await getIDPClientConfig();
|
|
76
|
-
requires2FA = cachedConfig.authSettings?.require2FA ?? true;
|
|
77
|
-
} catch (e) {
|
|
78
|
-
console.warn('[API Viability] Could not get client config, defaulting requires2FA to true');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// CRITICAL: OAuth sessions without refresh token always need 2FA completion
|
|
82
|
-
// to obtain a refresh token from the IDP. The OAuth callback sets mfaVerified=false
|
|
83
|
-
// when no refresh_token is present. This must override the client config.
|
|
84
|
-
if (sessionData.mfaVerified === false && !sessionData.idpRefreshToken) {
|
|
85
|
-
requires2FA = true;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// CRITICAL: Check if MFA has expired (2FA TTL enforcement)
|
|
89
|
-
// The session may have mfaVerified=true from days ago, but if mfaExpiresAt
|
|
90
|
-
// has passed, we must treat 2FA as incomplete to force re-verification.
|
|
91
|
-
const mfaExpiresAt = sessionData.mfaExpiresAt || 0;
|
|
92
|
-
const mfaExpired = mfaExpiresAt > 0 && mfaExpiresAt < Date.now();
|
|
93
|
-
// Check both field names for compatibility (mfaVerified is the normalized name)
|
|
94
|
-
const sessionMfaComplete = sessionData.mfaVerified ?? (sessionData as any).twoFactorComplete ?? false;
|
|
95
|
-
const effectiveTwoFactorComplete = sessionMfaComplete && !mfaExpired;
|
|
96
|
-
|
|
97
|
-
console.log('[VIABILITY] Session 2FA check:', {
|
|
98
|
-
sessionToken: sessionToken.substring(0, 8) + '...',
|
|
99
|
-
mfaVerified: sessionData.mfaVerified,
|
|
100
|
-
twoFactorComplete: (sessionData as any).twoFactorComplete,
|
|
101
|
-
sessionMfaComplete,
|
|
102
|
-
mfaExpired,
|
|
103
|
-
effectiveTwoFactorComplete,
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
if (mfaExpired && sessionMfaComplete) {
|
|
107
|
-
console.warn('[API Viability] MFA expired - forcing 2FA re-verification', {
|
|
108
|
-
mfaExpiresAt: new Date(mfaExpiresAt).toISOString(),
|
|
109
|
-
now: new Date().toISOString(),
|
|
110
|
-
hoursExpiredAgo: ((Date.now() - mfaExpiresAt) / (1000 * 60 * 60)).toFixed(1)
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const response = {
|
|
115
|
-
authenticated: true,
|
|
116
|
-
sessionToken, // Include token for middleware tracking
|
|
117
|
-
// 2FA fields - critical for middleware redirect logic
|
|
118
|
-
requires2FA, // From cached client config (client-wide setting)
|
|
119
|
-
twoFactorComplete: effectiveTwoFactorComplete, // From session, BUT respects MFA TTL
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Session Viability Check API Handler for `@payez/next-mvp`
|
|
3
|
+
*
|
|
4
|
+
* This API route is called by the middleware to securely check if a session is valid.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
8
|
+
import { getSession as getRedisSession, getBetterAuthSession } from '../../lib/session-store';
|
|
9
|
+
import { getSession } from '../../server/auth';
|
|
10
|
+
import { isInitializationFailed, ensureInitialized } from '../../lib/startup-init';
|
|
11
|
+
import { getIDPClientConfig } from '../../lib/idp-client-config';
|
|
12
|
+
|
|
13
|
+
export async function GET(req: NextRequest) {
|
|
14
|
+
try {
|
|
15
|
+
// Ensure initialization is complete (auth signing secret resolved from IDP)
|
|
16
|
+
if (!process.env.BETTER_AUTH_SECRET && !process.env.NEXTAUTH_SECRET) {
|
|
17
|
+
try {
|
|
18
|
+
await ensureInitialized();
|
|
19
|
+
} catch (error) {
|
|
20
|
+
// Initialization failed - return 503
|
|
21
|
+
console.error('[API Viability] Initialization failed - returning 503');
|
|
22
|
+
return NextResponse.json(
|
|
23
|
+
{
|
|
24
|
+
error: 'Service Unavailable',
|
|
25
|
+
message: 'Authentication service is not properly configured',
|
|
26
|
+
code: 'AUTH_NOT_INITIALIZED'
|
|
27
|
+
},
|
|
28
|
+
{ status: 503 }
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Double-check after initialization attempt
|
|
34
|
+
if (isInitializationFailed()) {
|
|
35
|
+
console.error('[API Viability] Initialization failed - returning 503');
|
|
36
|
+
return NextResponse.json(
|
|
37
|
+
{
|
|
38
|
+
error: 'Service Unavailable',
|
|
39
|
+
message: 'Authentication service is not properly configured',
|
|
40
|
+
code: 'AUTH_NOT_INITIALIZED'
|
|
41
|
+
},
|
|
42
|
+
{ status: 503 }
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Get session from Better Auth
|
|
47
|
+
const betterAuthSession = await getSession(req);
|
|
48
|
+
|
|
49
|
+
// Debug logging
|
|
50
|
+
if (!betterAuthSession) {
|
|
51
|
+
console.warn('[VIABILITY] getSession returned null');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const sessionToken = betterAuthSession?.session?.token as string | undefined;
|
|
55
|
+
if (betterAuthSession && sessionToken) {
|
|
56
|
+
// Try legacy session store first, then Better Auth format
|
|
57
|
+
let sessionData = await getRedisSession(sessionToken);
|
|
58
|
+
if (!sessionData) {
|
|
59
|
+
// Better Auth stores sessions with ba:{appSlug}:{token} prefix
|
|
60
|
+
sessionData = await getBetterAuthSession(sessionToken);
|
|
61
|
+
if (sessionData) {
|
|
62
|
+
console.log('[VIABILITY] Found session in Better Auth store');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (sessionData) {
|
|
66
|
+
// The session exists in Redis
|
|
67
|
+
|
|
68
|
+
// Check if access token is expired (for middleware decision-making)
|
|
69
|
+
const accessTokenExpires = sessionData.idpAccessTokenExpires || 0;
|
|
70
|
+
const accessTokenExpired = accessTokenExpires < Date.now();
|
|
71
|
+
|
|
72
|
+
// Get requires2FA from cached client config (client-wide setting)
|
|
73
|
+
let requires2FA = true; // Default to true for security
|
|
74
|
+
try {
|
|
75
|
+
const cachedConfig = await getIDPClientConfig();
|
|
76
|
+
requires2FA = cachedConfig.authSettings?.require2FA ?? true;
|
|
77
|
+
} catch (e) {
|
|
78
|
+
console.warn('[API Viability] Could not get client config, defaulting requires2FA to true');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// CRITICAL: OAuth sessions without refresh token always need 2FA completion
|
|
82
|
+
// to obtain a refresh token from the IDP. The OAuth callback sets mfaVerified=false
|
|
83
|
+
// when no refresh_token is present. This must override the client config.
|
|
84
|
+
if (sessionData.mfaVerified === false && !sessionData.idpRefreshToken) {
|
|
85
|
+
requires2FA = true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// CRITICAL: Check if MFA has expired (2FA TTL enforcement)
|
|
89
|
+
// The session may have mfaVerified=true from days ago, but if mfaExpiresAt
|
|
90
|
+
// has passed, we must treat 2FA as incomplete to force re-verification.
|
|
91
|
+
const mfaExpiresAt = sessionData.mfaExpiresAt || 0;
|
|
92
|
+
const mfaExpired = mfaExpiresAt > 0 && mfaExpiresAt < Date.now();
|
|
93
|
+
// Check both field names for compatibility (mfaVerified is the normalized name)
|
|
94
|
+
const sessionMfaComplete = sessionData.mfaVerified ?? (sessionData as any).twoFactorComplete ?? false;
|
|
95
|
+
const effectiveTwoFactorComplete = sessionMfaComplete && !mfaExpired;
|
|
96
|
+
|
|
97
|
+
console.log('[VIABILITY] Session 2FA check:', {
|
|
98
|
+
sessionToken: sessionToken.substring(0, 8) + '...',
|
|
99
|
+
mfaVerified: sessionData.mfaVerified,
|
|
100
|
+
twoFactorComplete: (sessionData as any).twoFactorComplete,
|
|
101
|
+
sessionMfaComplete,
|
|
102
|
+
mfaExpired,
|
|
103
|
+
effectiveTwoFactorComplete,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (mfaExpired && sessionMfaComplete) {
|
|
107
|
+
console.warn('[API Viability] MFA expired - forcing 2FA re-verification', {
|
|
108
|
+
mfaExpiresAt: new Date(mfaExpiresAt).toISOString(),
|
|
109
|
+
now: new Date().toISOString(),
|
|
110
|
+
hoursExpiredAgo: ((Date.now() - mfaExpiresAt) / (1000 * 60 * 60)).toFixed(1)
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const response = {
|
|
115
|
+
authenticated: true,
|
|
116
|
+
sessionToken, // Include token for middleware tracking
|
|
117
|
+
// 2FA fields - critical for middleware redirect logic
|
|
118
|
+
requires2FA, // From cached client config (client-wide setting)
|
|
119
|
+
twoFactorComplete: effectiveTwoFactorComplete, // From session, BUT respects MFA TTL
|
|
120
|
+
// Cards 141688/141741 — the IDP's routing decision, relayed verbatim.
|
|
121
|
+
//
|
|
122
|
+
// THIS IS THE SECOND IMPLEMENTATION OF VIABILITY. The other is
|
|
123
|
+
// routes/auth/viability.ts, which is what the admin graft mounts.
|
|
124
|
+
// Both are publicly exported entry points, so both can be a consumer's
|
|
125
|
+
// only source of this answer. Adding the relay to just one would leave
|
|
126
|
+
// an app that mounts THIS one still blind to enroll_mfa — the exact
|
|
127
|
+
// seam these cards are about, reopened one export over.
|
|
128
|
+
//
|
|
129
|
+
// 'enroll_mfa' = no USABLE method, the user cannot complete a
|
|
130
|
+
// challenge. 'mfa_verify' = usable method exists. null = nothing due.
|
|
131
|
+
// NOT a completion signal: twoFactorComplete still owns that.
|
|
132
|
+
//
|
|
133
|
+
// No `?? null`: undefined (never captured — every session minted
|
|
134
|
+
// before this change) must stay distinguishable from null (the IDP
|
|
135
|
+
// said nothing is outstanding). Defaulting to null would tell the
|
|
136
|
+
// gate that every pre-existing session is fully authenticated,
|
|
137
|
+
// including zero-method users who must be routed to enrol.
|
|
138
|
+
nextStep: sessionData.nextStep,
|
|
139
|
+
// Token status for refresh decisions
|
|
140
|
+
accessTokenExpired,
|
|
141
|
+
hasRefreshToken: !!sessionData.idpRefreshToken
|
|
142
|
+
};
|
|
143
|
+
return NextResponse.json(response);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// CRITICAL: Cookie exists but Redis session is missing (stale cookie state)
|
|
147
|
+
// Return sessionToken so middleware can detect this and clear the stale cookie
|
|
148
|
+
console.warn('[VIABILITY] Stale cookie detected - session not in Redis');
|
|
149
|
+
return NextResponse.json({
|
|
150
|
+
authenticated: false,
|
|
151
|
+
sessionToken // Include token to enable stale cookie detection
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// If there's no token at all, it's not authenticated
|
|
156
|
+
return NextResponse.json({ authenticated: false });
|
|
157
|
+
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.error('[API Viability] Error checking session viability:', error);
|
|
160
|
+
return NextResponse.json({ authenticated: false }, { status: 500 });
|
|
161
|
+
}
|
|
143
162
|
}
|