@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,94 +1,90 @@
1
- "use strict";
2
- /**
3
- * Authentication Verify Code / Complete 2FA API Handler
4
- *
5
- * Handles 2FA verification and token updates after successful verification.
6
- * This handler is designed to be imported and used by Next.js applications
7
- * using the @payez/next-mvp package.
8
- *
9
- * @version 2.0
10
- * @requires Authentication (authenticated endpoint)
11
- */
12
- Object.defineProperty(exports, "__esModule", { value: true });
13
- exports.POST = void 0;
14
- exports.createVerifyCodeHandler = createVerifyCodeHandler;
15
- const server_1 = require("next/server");
16
- const jwt_1 = require("next-auth/jwt");
17
- const session_store_1 = require("../../lib/session-store");
18
- const app_slug_1 = require("../../lib/app-slug");
19
- /**
20
- * Creates a verify-code/complete-2FA handler for Next.js API routes
21
- *
22
- * @param config Configuration for NextAuth
23
- * @returns Next.js POST handler function
24
- *
25
- * @example
26
- * ```typescript
27
- * // In your app's /app/api/auth/verify-code/route.ts
28
- * import { createVerifyCodeHandler } from '@payez/next-mvp/api-handlers/auth/verify-code';
29
- *
30
- * export const POST = createVerifyCodeHandler({
31
- * nextAuthSecret: process.env.NEXTAUTH_SECRET!
32
- * });
33
- * ```
34
- */
35
- function createVerifyCodeHandler(config) {
36
- const { nextAuthSecret } = config;
37
- return async function POST(req) {
38
- try {
39
- let body;
40
- try {
41
- body = await req.json();
42
- }
43
- catch (parseError) {
44
- return server_1.NextResponse.json({ success: false, message: 'Invalid JSON format' }, { status: 400 });
45
- }
46
- const { accessToken, refreshToken, accessTokenExpires, refreshTokenExpires } = body;
47
- // Get current session token from JWT
48
- let token = await (0, jwt_1.getToken)({ req, secret: nextAuthSecret, cookieName: (0, app_slug_1.getJwtCookieName)() });
49
- // The sessionToken is stored in the JWT token object
50
- // Support both field names: sessionToken (auth.ts JWT) and redisSessionId (legacy)
51
- const sessionToken = (token?.sessionToken || token?.redisSessionId);
52
- if (!sessionToken) {
53
- console.error('[VERIFY-CODE] No session token found in JWT', {
54
- hasToken: !!token,
55
- tokenKeys: token ? Object.keys(token) : []
56
- });
57
- return server_1.NextResponse.json({ success: false, message: 'No session found' }, { status: 401 });
58
- }
59
- console.info('[VERIFY-CODE] Updating session with new tokens after 2FA', {
60
- sessionToken: sessionToken.substring(0, 8) + '...',
61
- userId: token?.sub,
62
- hasAccessToken: !!accessToken,
63
- hasRefreshToken: !!refreshToken,
64
- accessTokenLength: accessToken?.length,
65
- refreshTokenLength: refreshToken?.length
66
- });
67
- // Update tokens in Redis
68
- await (0, session_store_1.updateTokens)(sessionToken, accessToken, refreshToken, accessTokenExpires, refreshTokenExpires);
69
- // Mark 2FA as complete
70
- await (0, session_store_1.mark2FAComplete)(sessionToken);
71
- console.info('[VERIFY-CODE] 2FA completion successful', {
72
- sessionToken: sessionToken.substring(0, 8) + '...',
73
- userId: token?.sub
74
- });
75
- return server_1.NextResponse.json({
76
- success: true,
77
- message: '2FA verification complete'
78
- });
79
- }
80
- catch (error) {
81
- console.error('[VERIFY-CODE] Failed to complete 2FA', {
82
- error: error instanceof Error ? error.message : String(error)
83
- });
84
- return server_1.NextResponse.json({ success: false, message: 'Failed to complete 2FA' }, { status: 500 });
85
- }
86
- };
87
- }
88
- /**
89
- * Default export for backward compatibility
90
- * Requires environment variable: NEXTAUTH_SECRET
91
- */
92
- exports.POST = createVerifyCodeHandler({
93
- nextAuthSecret: process.env.NEXTAUTH_SECRET || ''
94
- });
1
+ "use strict";
2
+ /**
3
+ * Authentication Verify Code / Complete 2FA API Handler
4
+ *
5
+ * Handles 2FA verification and token updates after successful verification.
6
+ * This handler is designed to be imported and used by Next.js applications
7
+ * using the @payez/next-mvp package.
8
+ *
9
+ * @version 2.0
10
+ * @requires Authentication (authenticated endpoint)
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.POST = void 0;
14
+ exports.createVerifyCodeHandler = createVerifyCodeHandler;
15
+ const server_1 = require("next/server");
16
+ const auth_1 = require("../../server/auth");
17
+ const session_store_1 = require("../../lib/session-store");
18
+ /**
19
+ * Creates a verify-code/complete-2FA handler for Next.js API routes
20
+ *
21
+ * @param config Configuration for NextAuth
22
+ * @returns Next.js POST handler function
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * // In your app's /app/api/auth/verify-code/route.ts
27
+ * import { createVerifyCodeHandler } from '@payez/next-mvp/api-handlers/auth/verify-code';
28
+ *
29
+ * export const POST = createVerifyCodeHandler({
30
+ * nextAuthSecret: process.env.NEXTAUTH_SECRET!
31
+ * });
32
+ * ```
33
+ */
34
+ function createVerifyCodeHandler(config) {
35
+ const { nextAuthSecret } = config;
36
+ return async function POST(req) {
37
+ try {
38
+ let body;
39
+ try {
40
+ body = await req.json();
41
+ }
42
+ catch (parseError) {
43
+ return server_1.NextResponse.json({ success: false, message: 'Invalid JSON format' }, { status: 400 });
44
+ }
45
+ const { accessToken, refreshToken, accessTokenExpires, refreshTokenExpires } = body;
46
+ // Get current session from Better Auth
47
+ const betterAuthSession = await (0, auth_1.getSession)(req);
48
+ const sessionToken = betterAuthSession?.session?.token;
49
+ if (!sessionToken) {
50
+ console.error('[VERIFY-CODE] No session token found', {
51
+ hasSession: !!betterAuthSession,
52
+ });
53
+ return server_1.NextResponse.json({ success: false, message: 'No session found' }, { status: 401 });
54
+ }
55
+ console.info('[VERIFY-CODE] Updating session with new tokens after 2FA', {
56
+ sessionToken: sessionToken.substring(0, 8) + '...',
57
+ userId: betterAuthSession?.user?.id,
58
+ hasAccessToken: !!accessToken,
59
+ hasRefreshToken: !!refreshToken,
60
+ accessTokenLength: accessToken?.length,
61
+ refreshTokenLength: refreshToken?.length
62
+ });
63
+ // Update tokens in Redis
64
+ await (0, session_store_1.updateTokens)(sessionToken, accessToken, refreshToken, accessTokenExpires, refreshTokenExpires);
65
+ // Mark 2FA as complete
66
+ await (0, session_store_1.mark2FAComplete)(sessionToken);
67
+ console.info('[VERIFY-CODE] 2FA completion successful', {
68
+ sessionToken: sessionToken.substring(0, 8) + '...',
69
+ userId: betterAuthSession?.user?.id
70
+ });
71
+ return server_1.NextResponse.json({
72
+ success: true,
73
+ message: '2FA verification complete'
74
+ });
75
+ }
76
+ catch (error) {
77
+ console.error('[VERIFY-CODE] Failed to complete 2FA', {
78
+ error: error instanceof Error ? error.message : String(error)
79
+ });
80
+ return server_1.NextResponse.json({ success: false, message: 'Failed to complete 2FA' }, { status: 500 });
81
+ }
82
+ };
83
+ }
84
+ /**
85
+ * Default export for backward compatibility
86
+ * Requires environment variable: NEXTAUTH_SECRET
87
+ */
88
+ exports.POST = createVerifyCodeHandler({
89
+ nextAuthSecret: process.env.NEXTAUTH_SECRET || ''
90
+ });
@@ -1,146 +1,114 @@
1
- "use strict";
2
- /**
3
- * Session Viability Check API Handler for `@payez/next-mvp`
4
- *
5
- * This API route is called by the middleware to securely check if a session is valid.
6
- */
7
- Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.GET = GET;
9
- const server_1 = require("next/server");
10
- const session_store_1 = require("../../lib/session-store");
11
- const jwt_1 = require("next-auth/jwt");
12
- const startup_init_1 = require("../../lib/startup-init");
13
- const app_slug_1 = require("../../lib/app-slug");
14
- const idp_client_config_1 = require("../../lib/idp-client-config");
15
- /**
16
- * Get NextAuth secret from IDP config (cached).
17
- * NEVER use process.env.NEXTAUTH_SECRET directly - it may not be set.
18
- */
19
- async function getNextAuthSecret() {
20
- const config = await (0, idp_client_config_1.getIDPClientConfig)();
21
- return config.nextAuthSecret || '';
22
- }
23
- async function GET(req) {
24
- try {
25
- // Ensure initialization is complete
26
- if (!process.env.NEXTAUTH_SECRET) {
27
- try {
28
- await (0, startup_init_1.ensureInitialized)();
29
- }
30
- catch (error) {
31
- // Initialization failed - return 503
32
- console.error('[API Viability] Initialization failed - returning 503');
33
- return server_1.NextResponse.json({
34
- error: 'Service Unavailable',
35
- message: 'Authentication service is not properly configured',
36
- code: 'AUTH_NOT_INITIALIZED'
37
- }, { status: 503 });
38
- }
39
- }
40
- // Double-check after initialization attempt
41
- if ((0, startup_init_1.isInitializationFailed)()) {
42
- console.error('[API Viability] Initialization failed - returning 503');
43
- return server_1.NextResponse.json({
44
- error: 'Service Unavailable',
45
- message: 'Authentication service is not properly configured',
46
- code: 'AUTH_NOT_INITIALIZED'
47
- }, { status: 503 });
48
- }
49
- // Get secret from IDP config (same source as session.ts and token-lifecycle.ts)
50
- const secret = await getNextAuthSecret();
51
- if (!secret) {
52
- console.error('[API Viability] NEXTAUTH_SECRET not available from IDP config');
53
- return server_1.NextResponse.json({
54
- error: 'Service Unavailable',
55
- message: 'Authentication service is not properly configured',
56
- code: 'AUTH_NOT_INITIALIZED'
57
- }, { status: 503 });
58
- }
59
- // getToken is the recommended way to get the JWT from a request
60
- const cookieName = (0, app_slug_1.getJwtCookieName)();
61
- const token = await (0, jwt_1.getToken)({ req, secret, cookieName });
62
- // Debug logging to diagnose AKS-specific issues
63
- if (!token) {
64
- const cookieHeader = req.headers.get('cookie') || '';
65
- const hasCookie = cookieHeader.includes(cookieName);
66
- const cookieMatch = cookieHeader.match(new RegExp(`${cookieName}=([^;]*)`));
67
- const cookieValue = cookieMatch ? cookieMatch[1] : null;
68
- console.warn('[VIABILITY] getToken returned null:', {
69
- cookieName,
70
- hasCookie,
71
- cookieValueLength: cookieValue?.length || 0,
72
- cookieValuePreview: cookieValue ? cookieValue.substring(0, 30) + '...' : 'EMPTY',
73
- secretLength: secret.length,
74
- secretPreview: secret ? secret.substring(0, 10) + '...' : 'EMPTY',
75
- });
76
- }
77
- // Support both field names: sessionToken (auth.ts JWT) and redisSessionId (legacy)
78
- const sessionToken = (token?.sessionToken || token?.redisSessionId);
79
- if (token && sessionToken) {
80
- const sessionData = await (0, session_store_1.getSession)(sessionToken);
81
- if (sessionData) {
82
- // The session exists in Redis
83
- // Check if access token is expired (for middleware decision-making)
84
- const accessTokenExpires = sessionData.idpAccessTokenExpires || 0;
85
- const accessTokenExpired = accessTokenExpires < Date.now();
86
- // Get requires2FA from cached client config (not session)
87
- // This is a client-wide setting from the broker handshake
88
- let requires2FA = true; // Default to true for security
89
- try {
90
- const cachedConfig = await (0, idp_client_config_1.getIDPClientConfig)();
91
- requires2FA = cachedConfig.authSettings?.require2FA ?? true;
92
- }
93
- catch (e) {
94
- console.warn('[API Viability] Could not get client config, defaulting requires2FA to true');
95
- }
96
- // CRITICAL: Check if MFA has expired (2FA TTL enforcement)
97
- // The session may have mfaVerified=true from days ago, but if mfaExpiresAt
98
- // has passed, we must treat 2FA as incomplete to force re-verification.
99
- const mfaExpiresAt = sessionData.mfaExpiresAt || 0;
100
- const mfaExpired = mfaExpiresAt > 0 && mfaExpiresAt < Date.now();
101
- // Check both field names for compatibility (mfaVerified is the normalized name)
102
- const sessionMfaComplete = sessionData.mfaVerified ?? sessionData.twoFactorComplete ?? false;
103
- const effectiveTwoFactorComplete = sessionMfaComplete && !mfaExpired;
104
- console.log('[VIABILITY] Session 2FA check:', {
105
- sessionToken: sessionToken.substring(0, 8) + '...',
106
- mfaVerified: sessionData.mfaVerified,
107
- twoFactorComplete: sessionData.twoFactorComplete,
108
- sessionMfaComplete,
109
- mfaExpired,
110
- effectiveTwoFactorComplete,
111
- });
112
- if (mfaExpired && sessionMfaComplete) {
113
- console.warn('[API Viability] MFA expired - forcing 2FA re-verification', {
114
- mfaExpiresAt: new Date(mfaExpiresAt).toISOString(),
115
- now: new Date().toISOString(),
116
- hoursExpiredAgo: ((Date.now() - mfaExpiresAt) / (1000 * 60 * 60)).toFixed(1)
117
- });
118
- }
119
- const response = {
120
- authenticated: true,
121
- sessionToken, // Include token for middleware tracking
122
- // 2FA fields - critical for middleware redirect logic
123
- requires2FA, // From cached client config (client-wide setting)
124
- twoFactorComplete: effectiveTwoFactorComplete, // From session, BUT respects MFA TTL
125
- // Token status for refresh decisions
126
- accessTokenExpired,
127
- hasRefreshToken: !!sessionData.idpRefreshToken
128
- };
129
- return server_1.NextResponse.json(response);
130
- }
131
- // CRITICAL: Cookie exists but Redis session is missing (stale cookie state)
132
- // Return sessionToken so middleware can detect this and clear the stale cookie
133
- console.warn('[VIABILITY] Stale cookie detected - session not in Redis');
134
- return server_1.NextResponse.json({
135
- authenticated: false,
136
- sessionToken // Include token to enable stale cookie detection
137
- });
138
- }
139
- // If there's no token at all, it's not authenticated
140
- return server_1.NextResponse.json({ authenticated: false });
141
- }
142
- catch (error) {
143
- console.error('[API Viability] Error checking session viability:', error);
144
- return server_1.NextResponse.json({ authenticated: false }, { status: 500 });
145
- }
146
- }
1
+ "use strict";
2
+ /**
3
+ * Session Viability Check API Handler for `@payez/next-mvp`
4
+ *
5
+ * This API route is called by the middleware to securely check if a session is valid.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.GET = GET;
9
+ const server_1 = require("next/server");
10
+ const session_store_1 = require("../../lib/session-store");
11
+ const auth_1 = require("../../server/auth");
12
+ const startup_init_1 = require("../../lib/startup-init");
13
+ const idp_client_config_1 = require("../../lib/idp-client-config");
14
+ async function GET(req) {
15
+ try {
16
+ // Ensure initialization is complete
17
+ if (!process.env.NEXTAUTH_SECRET) {
18
+ try {
19
+ await (0, startup_init_1.ensureInitialized)();
20
+ }
21
+ catch (error) {
22
+ // Initialization failed - return 503
23
+ console.error('[API Viability] Initialization failed - returning 503');
24
+ return server_1.NextResponse.json({
25
+ error: 'Service Unavailable',
26
+ message: 'Authentication service is not properly configured',
27
+ code: 'AUTH_NOT_INITIALIZED'
28
+ }, { status: 503 });
29
+ }
30
+ }
31
+ // Double-check after initialization attempt
32
+ if ((0, startup_init_1.isInitializationFailed)()) {
33
+ console.error('[API Viability] Initialization failed - returning 503');
34
+ return server_1.NextResponse.json({
35
+ error: 'Service Unavailable',
36
+ message: 'Authentication service is not properly configured',
37
+ code: 'AUTH_NOT_INITIALIZED'
38
+ }, { status: 503 });
39
+ }
40
+ // Get session from Better Auth
41
+ const betterAuthSession = await (0, auth_1.getSession)(req);
42
+ // Debug logging
43
+ if (!betterAuthSession) {
44
+ console.warn('[VIABILITY] getSession returned null');
45
+ }
46
+ const sessionToken = betterAuthSession?.session?.token;
47
+ if (betterAuthSession && sessionToken) {
48
+ const sessionData = await (0, session_store_1.getSession)(sessionToken);
49
+ if (sessionData) {
50
+ // The session exists in Redis
51
+ // Check if access token is expired (for middleware decision-making)
52
+ const accessTokenExpires = sessionData.idpAccessTokenExpires || 0;
53
+ const accessTokenExpired = accessTokenExpires < Date.now();
54
+ // Get requires2FA from cached client config (not session)
55
+ // This is a client-wide setting from the broker handshake
56
+ let requires2FA = true; // Default to true for security
57
+ try {
58
+ const cachedConfig = await (0, idp_client_config_1.getIDPClientConfig)();
59
+ requires2FA = cachedConfig.authSettings?.require2FA ?? true;
60
+ }
61
+ catch (e) {
62
+ console.warn('[API Viability] Could not get client config, defaulting requires2FA to true');
63
+ }
64
+ // CRITICAL: Check if MFA has expired (2FA TTL enforcement)
65
+ // The session may have mfaVerified=true from days ago, but if mfaExpiresAt
66
+ // has passed, we must treat 2FA as incomplete to force re-verification.
67
+ const mfaExpiresAt = sessionData.mfaExpiresAt || 0;
68
+ const mfaExpired = mfaExpiresAt > 0 && mfaExpiresAt < Date.now();
69
+ // Check both field names for compatibility (mfaVerified is the normalized name)
70
+ const sessionMfaComplete = sessionData.mfaVerified ?? sessionData.twoFactorComplete ?? false;
71
+ const effectiveTwoFactorComplete = sessionMfaComplete && !mfaExpired;
72
+ console.log('[VIABILITY] Session 2FA check:', {
73
+ sessionToken: sessionToken.substring(0, 8) + '...',
74
+ mfaVerified: sessionData.mfaVerified,
75
+ twoFactorComplete: sessionData.twoFactorComplete,
76
+ sessionMfaComplete,
77
+ mfaExpired,
78
+ effectiveTwoFactorComplete,
79
+ });
80
+ if (mfaExpired && sessionMfaComplete) {
81
+ console.warn('[API Viability] MFA expired - forcing 2FA re-verification', {
82
+ mfaExpiresAt: new Date(mfaExpiresAt).toISOString(),
83
+ now: new Date().toISOString(),
84
+ hoursExpiredAgo: ((Date.now() - mfaExpiresAt) / (1000 * 60 * 60)).toFixed(1)
85
+ });
86
+ }
87
+ const response = {
88
+ authenticated: true,
89
+ sessionToken, // Include token for middleware tracking
90
+ // 2FA fields - critical for middleware redirect logic
91
+ requires2FA, // From cached client config (client-wide setting)
92
+ twoFactorComplete: effectiveTwoFactorComplete, // From session, BUT respects MFA TTL
93
+ // Token status for refresh decisions
94
+ accessTokenExpired,
95
+ hasRefreshToken: !!sessionData.idpRefreshToken
96
+ };
97
+ return server_1.NextResponse.json(response);
98
+ }
99
+ // CRITICAL: Cookie exists but Redis session is missing (stale cookie state)
100
+ // Return sessionToken so middleware can detect this and clear the stale cookie
101
+ console.warn('[VIABILITY] Stale cookie detected - session not in Redis');
102
+ return server_1.NextResponse.json({
103
+ authenticated: false,
104
+ sessionToken // Include token to enable stale cookie detection
105
+ });
106
+ }
107
+ // If there's no token at all, it's not authenticated
108
+ return server_1.NextResponse.json({ authenticated: false });
109
+ }
110
+ catch (error) {
111
+ console.error('[API Viability] Error checking session viability:', error);
112
+ return server_1.NextResponse.json({ authenticated: false }, { status: 500 });
113
+ }
114
+ }
@@ -1,65 +1,59 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.POST = void 0;
4
- const server_1 = require("next/server");
5
- const jwt_1 = require("next-auth/jwt");
6
- const session_store_1 = require("../../lib/session-store");
7
- const app_slug_1 = require("../../lib/app-slug");
8
- /**
9
- * Force-expire access token for testing refresh flow.
10
- *
11
- * Sets the access token expiry to 2 minutes in the past,
12
- * which will trigger a refresh on the next API call.
13
- *
14
- * Usage in consuming app:
15
- * ```typescript
16
- * // app/api/test/force-expire/route.ts
17
- * export { POST } from '@payez/next-mvp/api-handlers/test/force-expire';
18
- * ```
19
- */
20
- const POST = async (req) => {
21
- try {
22
- const secret = process.env.NEXTAUTH_SECRET;
23
- if (!secret) {
24
- return server_1.NextResponse.json({ success: false, error: 'NEXTAUTH_SECRET not configured' }, { status: 500 });
25
- }
26
- const cookieName = (0, app_slug_1.getJwtCookieName)();
27
- const token = await (0, jwt_1.getToken)({ req, secret, cookieName });
28
- let sessionToken = token?.redisSessionId;
29
- if (!sessionToken) {
30
- const headerSessionToken = req.headers.get('x-session-token') || req.headers.get('X-Session-Token');
31
- if (headerSessionToken) {
32
- sessionToken = headerSessionToken;
33
- }
34
- else {
35
- console.warn('[TEST_EXPIRE] No session token in JWT cookie or X-Session-Token header');
36
- return server_1.NextResponse.json({ success: false, error: 'No session token' }, { status: 401 });
37
- }
38
- }
39
- const session = await (0, session_store_1.getSession)(sessionToken);
40
- if (!session) {
41
- return server_1.NextResponse.json({ success: false, error: 'Session not found' }, { status: 404 });
42
- }
43
- const now = Date.now();
44
- const forced = now - (2 * 60 * 1000); // two minutes ago
45
- const prev = session.idpAccessTokenExpires || null;
46
- await (0, session_store_1.updateSession)(sessionToken, { idpAccessTokenExpires: forced });
47
- console.log('[TEST_EXPIRE] Forced access token expiry for session', {
48
- sessionToken: sessionToken.substring(0, 8) + '...',
49
- previous: prev ? new Date(prev).toISOString() : null,
50
- newExpiry: new Date(forced).toISOString()
51
- });
52
- return server_1.NextResponse.json({
53
- success: true,
54
- previous: prev,
55
- previousIso: prev ? new Date(prev).toISOString() : null,
56
- newExpiry: forced,
57
- newExpiryIso: new Date(forced).toISOString()
58
- });
59
- }
60
- catch (e) {
61
- console.error('[TEST_EXPIRE] Error:', e);
62
- return server_1.NextResponse.json({ success: false, error: e instanceof Error ? e.message : String(e) }, { status: 500 });
63
- }
64
- };
65
- exports.POST = POST;
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.POST = void 0;
4
+ const server_1 = require("next/server");
5
+ const auth_1 = require("../../server/auth");
6
+ const session_store_1 = require("../../lib/session-store");
7
+ /**
8
+ * Force-expire access token for testing refresh flow.
9
+ *
10
+ * Sets the access token expiry to 2 minutes in the past,
11
+ * which will trigger a refresh on the next API call.
12
+ *
13
+ * Usage in consuming app:
14
+ * ```typescript
15
+ * // app/api/test/force-expire/route.ts
16
+ * export { POST } from '@payez/next-mvp/api-handlers/test/force-expire';
17
+ * ```
18
+ */
19
+ const POST = async (req) => {
20
+ try {
21
+ const betterAuthSession = await (0, auth_1.getSession)(req);
22
+ let sessionToken = betterAuthSession?.session?.token;
23
+ if (!sessionToken) {
24
+ const headerSessionToken = req.headers.get('x-session-token') || req.headers.get('X-Session-Token');
25
+ if (headerSessionToken) {
26
+ sessionToken = headerSessionToken;
27
+ }
28
+ else {
29
+ console.warn('[TEST_EXPIRE] No session token or X-Session-Token header');
30
+ return server_1.NextResponse.json({ success: false, error: 'No session token' }, { status: 401 });
31
+ }
32
+ }
33
+ const session = await (0, session_store_1.getSession)(sessionToken);
34
+ if (!session) {
35
+ return server_1.NextResponse.json({ success: false, error: 'Session not found' }, { status: 404 });
36
+ }
37
+ const now = Date.now();
38
+ const forced = now - (2 * 60 * 1000); // two minutes ago
39
+ const prev = session.idpAccessTokenExpires || null;
40
+ await (0, session_store_1.updateSession)(sessionToken, { idpAccessTokenExpires: forced });
41
+ console.log('[TEST_EXPIRE] Forced access token expiry for session', {
42
+ sessionToken: sessionToken.substring(0, 8) + '...',
43
+ previous: prev ? new Date(prev).toISOString() : null,
44
+ newExpiry: new Date(forced).toISOString()
45
+ });
46
+ return server_1.NextResponse.json({
47
+ success: true,
48
+ previous: prev,
49
+ previousIso: prev ? new Date(prev).toISOString() : null,
50
+ newExpiry: forced,
51
+ newExpiryIso: new Date(forced).toISOString()
52
+ });
53
+ }
54
+ catch (e) {
55
+ console.error('[TEST_EXPIRE] Error:', e);
56
+ return server_1.NextResponse.json({ success: false, error: e instanceof Error ? e.message : String(e) }, { status: 500 });
57
+ }
58
+ };
59
+ exports.POST = POST;