@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,167 +1,167 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ready-to-Use Session Management Route
|
|
3
|
-
*
|
|
4
|
-
* Provides pre-configured session handlers for checking and updating session state.
|
|
5
|
-
*
|
|
6
|
-
* @example
|
|
7
|
-
* ```typescript
|
|
8
|
-
* // app/api/auth/session/route.ts
|
|
9
|
-
* export { GET, POST } from '@payez/next-mvp/routes/auth/session';
|
|
10
|
-
* ```
|
|
11
|
-
*
|
|
12
|
-
* @version 2.0.0
|
|
13
|
-
* @since auth-ready-v2
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
17
|
-
import { getSession as getBetterAuthSession } from '../../server/auth';
|
|
18
|
-
import { getSession as getRedisSession, updateSession } from '../../lib/session-store';
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* GET /api/auth/session - Check current session status
|
|
22
|
-
*
|
|
23
|
-
* Returns the current session information including:
|
|
24
|
-
* - User details
|
|
25
|
-
* - Token expiry status
|
|
26
|
-
* - Session validity
|
|
27
|
-
*/
|
|
28
|
-
export async function GET(req: NextRequest) {
|
|
29
|
-
try {
|
|
30
|
-
const authSession = await getBetterAuthSession(req);
|
|
31
|
-
|
|
32
|
-
if (!authSession) {
|
|
33
|
-
console.warn('[SESSION_ROUTE] Better Auth session not found');
|
|
34
|
-
// MUST return empty {} — useSession() treats any non-empty
|
|
35
|
-
// response object as "authenticated", causing redirect loops on login page.
|
|
36
|
-
return NextResponse.json({});
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const redisSessionId = authSession.session?.token;
|
|
40
|
-
|
|
41
|
-
console.log('[SESSION_ROUTE] Session found:', {
|
|
42
|
-
userId: authSession.user?.id,
|
|
43
|
-
email: authSession.user?.email,
|
|
44
|
-
name: authSession.user?.name,
|
|
45
|
-
redisSessionId: redisSessionId ? redisSessionId.substring(0, 8) + '...' : 'MISSING',
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
// Fetch full session data from Redis
|
|
49
|
-
const session = redisSessionId ? await getRedisSession(redisSessionId) : null;
|
|
50
|
-
|
|
51
|
-
console.log('[SESSION_ROUTE] Redis session:', {
|
|
52
|
-
found: !!session,
|
|
53
|
-
userId: session?.userId,
|
|
54
|
-
roles: session?.roles,
|
|
55
|
-
hasAccessToken: !!session?.idpAccessToken,
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
// Return session format with Redis data
|
|
59
|
-
// useSession() expects: { user: {...}, expires: "..." }
|
|
60
|
-
// We enrich with all session data from Redis
|
|
61
|
-
return NextResponse.json({
|
|
62
|
-
user: {
|
|
63
|
-
id: session?.userId || authSession.user?.id,
|
|
64
|
-
email: session?.email || authSession.user?.email,
|
|
65
|
-
name: session?.name || authSession.user?.name,
|
|
66
|
-
image: authSession.user?.image || session?.image || null,
|
|
67
|
-
// Redis session data
|
|
68
|
-
roles: session?.roles || [],
|
|
69
|
-
twoFactorSessionVerified: session?.mfaVerified || false,
|
|
70
|
-
requiresTwoFactor: !session?.mfaVerified,
|
|
71
|
-
authenticationMethods: session?.authenticationMethods,
|
|
72
|
-
authenticationLevel: session?.authenticationLevel,
|
|
73
|
-
mfaCompletedAt: session?.mfaCompletedAt,
|
|
74
|
-
mfaExpiresAt: session?.mfaExpiresAt,
|
|
75
|
-
mfaValidityHours: session?.mfaValidityHours,
|
|
76
|
-
oauthProvider: session?.oauthProvider,
|
|
77
|
-
idpClientId: session?.idpClientId,
|
|
78
|
-
merchantId: session?.merchantId,
|
|
79
|
-
},
|
|
80
|
-
// Session tokens
|
|
81
|
-
sessionToken: redisSessionId,
|
|
82
|
-
accessToken: session?.idpAccessToken,
|
|
83
|
-
refreshToken: session?.idpRefreshToken,
|
|
84
|
-
accessTokenExpires: session?.idpAccessTokenExpires,
|
|
85
|
-
expires: authSession.session?.expiresAt
|
|
86
|
-
? new Date(authSession.session.expiresAt).toISOString()
|
|
87
|
-
: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
|
|
88
|
-
});
|
|
89
|
-
} catch (error) {
|
|
90
|
-
console.error('[SESSION_ROUTE] Error checking session:', error);
|
|
91
|
-
return NextResponse.json({
|
|
92
|
-
error: 'Failed to check session',
|
|
93
|
-
details: error instanceof Error ? error.message : 'Unknown error'
|
|
94
|
-
}, { status: 500 });
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* POST /api/auth/session - Update session data
|
|
100
|
-
*
|
|
101
|
-
* Allows updating session metadata (not tokens).
|
|
102
|
-
* Token refresh should use the /api/auth/refresh endpoint.
|
|
103
|
-
*
|
|
104
|
-
* Body:
|
|
105
|
-
* - metadata: object - Custom metadata to store in session
|
|
106
|
-
*/
|
|
107
|
-
export async function POST(req: NextRequest) {
|
|
108
|
-
try {
|
|
109
|
-
const authSession = await getBetterAuthSession(req);
|
|
110
|
-
|
|
111
|
-
if (!authSession) {
|
|
112
|
-
return NextResponse.json({
|
|
113
|
-
error: 'No session found',
|
|
114
|
-
code: 'UNAUTHORIZED'
|
|
115
|
-
}, { status: 401 });
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const sessionToken = authSession.session?.token;
|
|
119
|
-
if (!sessionToken) {
|
|
120
|
-
return NextResponse.json({
|
|
121
|
-
error: 'Invalid session',
|
|
122
|
-
code: 'INVALID_SESSION'
|
|
123
|
-
}, { status: 400 });
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const body = await req.json();
|
|
127
|
-
const { metadata, access_token, refresh_token, twoFactorComplete, twoFactorMethod } = body;
|
|
128
|
-
|
|
129
|
-
// Get current session from Redis
|
|
130
|
-
const session = await getRedisSession(sessionToken);
|
|
131
|
-
if (!session) {
|
|
132
|
-
return NextResponse.json({
|
|
133
|
-
error: 'Session not found',
|
|
134
|
-
code: 'SESSION_NOT_FOUND'
|
|
135
|
-
}, { status: 404 });
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Update session with new data
|
|
139
|
-
const updatedSession = {
|
|
140
|
-
...session,
|
|
141
|
-
...(access_token ? { accessToken: access_token } : {}),
|
|
142
|
-
...(refresh_token ? { refreshToken: refresh_token } : {}),
|
|
143
|
-
...(typeof twoFactorComplete === 'boolean' ? { twoFactorComplete } : {}),
|
|
144
|
-
...(twoFactorMethod ? { twoFactorMethod } : {}),
|
|
145
|
-
...(metadata ? {
|
|
146
|
-
metadata: {
|
|
147
|
-
...(session.metadata || {}),
|
|
148
|
-
...metadata,
|
|
149
|
-
updatedAt: new Date().toISOString()
|
|
150
|
-
}
|
|
151
|
-
} : {})
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
await updateSession(sessionToken, updatedSession);
|
|
155
|
-
|
|
156
|
-
return NextResponse.json({
|
|
157
|
-
success: true,
|
|
158
|
-
message: 'Session updated successfully'
|
|
159
|
-
});
|
|
160
|
-
} catch (error) {
|
|
161
|
-
console.error('[SESSION_ROUTE] Error updating session:', error);
|
|
162
|
-
return NextResponse.json({
|
|
163
|
-
error: 'Failed to update session',
|
|
164
|
-
details: error instanceof Error ? error.message : 'Unknown error'
|
|
165
|
-
}, { status: 500 });
|
|
166
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Ready-to-Use Session Management Route
|
|
3
|
+
*
|
|
4
|
+
* Provides pre-configured session handlers for checking and updating session state.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* // app/api/auth/session/route.ts
|
|
9
|
+
* export { GET, POST } from '@payez/next-mvp/routes/auth/session';
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* @version 2.0.0
|
|
13
|
+
* @since auth-ready-v2
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
17
|
+
import { getSession as getBetterAuthSession } from '../../server/auth';
|
|
18
|
+
import { getSession as getRedisSession, updateSession } from '../../lib/session-store';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* GET /api/auth/session - Check current session status
|
|
22
|
+
*
|
|
23
|
+
* Returns the current session information including:
|
|
24
|
+
* - User details
|
|
25
|
+
* - Token expiry status
|
|
26
|
+
* - Session validity
|
|
27
|
+
*/
|
|
28
|
+
export async function GET(req: NextRequest) {
|
|
29
|
+
try {
|
|
30
|
+
const authSession = await getBetterAuthSession(req);
|
|
31
|
+
|
|
32
|
+
if (!authSession) {
|
|
33
|
+
console.warn('[SESSION_ROUTE] Better Auth session not found');
|
|
34
|
+
// MUST return empty {} — useSession() treats any non-empty
|
|
35
|
+
// response object as "authenticated", causing redirect loops on login page.
|
|
36
|
+
return NextResponse.json({});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const redisSessionId = authSession.session?.token;
|
|
40
|
+
|
|
41
|
+
console.log('[SESSION_ROUTE] Session found:', {
|
|
42
|
+
userId: authSession.user?.id,
|
|
43
|
+
email: authSession.user?.email,
|
|
44
|
+
name: authSession.user?.name,
|
|
45
|
+
redisSessionId: redisSessionId ? redisSessionId.substring(0, 8) + '...' : 'MISSING',
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Fetch full session data from Redis
|
|
49
|
+
const session = redisSessionId ? await getRedisSession(redisSessionId) : null;
|
|
50
|
+
|
|
51
|
+
console.log('[SESSION_ROUTE] Redis session:', {
|
|
52
|
+
found: !!session,
|
|
53
|
+
userId: session?.userId,
|
|
54
|
+
roles: session?.roles,
|
|
55
|
+
hasAccessToken: !!session?.idpAccessToken,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Return session format with Redis data
|
|
59
|
+
// useSession() expects: { user: {...}, expires: "..." }
|
|
60
|
+
// We enrich with all session data from Redis
|
|
61
|
+
return NextResponse.json({
|
|
62
|
+
user: {
|
|
63
|
+
id: session?.userId || authSession.user?.id,
|
|
64
|
+
email: session?.email || authSession.user?.email,
|
|
65
|
+
name: session?.name || authSession.user?.name,
|
|
66
|
+
image: authSession.user?.image || session?.image || null,
|
|
67
|
+
// Redis session data
|
|
68
|
+
roles: session?.roles || [],
|
|
69
|
+
twoFactorSessionVerified: session?.mfaVerified || false,
|
|
70
|
+
requiresTwoFactor: !session?.mfaVerified,
|
|
71
|
+
authenticationMethods: session?.authenticationMethods,
|
|
72
|
+
authenticationLevel: session?.authenticationLevel,
|
|
73
|
+
mfaCompletedAt: session?.mfaCompletedAt,
|
|
74
|
+
mfaExpiresAt: session?.mfaExpiresAt,
|
|
75
|
+
mfaValidityHours: session?.mfaValidityHours,
|
|
76
|
+
oauthProvider: session?.oauthProvider,
|
|
77
|
+
idpClientId: session?.idpClientId,
|
|
78
|
+
merchantId: session?.merchantId,
|
|
79
|
+
},
|
|
80
|
+
// Session tokens
|
|
81
|
+
sessionToken: redisSessionId,
|
|
82
|
+
accessToken: session?.idpAccessToken,
|
|
83
|
+
refreshToken: session?.idpRefreshToken,
|
|
84
|
+
accessTokenExpires: session?.idpAccessTokenExpires,
|
|
85
|
+
expires: authSession.session?.expiresAt
|
|
86
|
+
? new Date(authSession.session.expiresAt).toISOString()
|
|
87
|
+
: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
|
|
88
|
+
});
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error('[SESSION_ROUTE] Error checking session:', error);
|
|
91
|
+
return NextResponse.json({
|
|
92
|
+
error: 'Failed to check session',
|
|
93
|
+
details: error instanceof Error ? error.message : 'Unknown error'
|
|
94
|
+
}, { status: 500 });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* POST /api/auth/session - Update session data
|
|
100
|
+
*
|
|
101
|
+
* Allows updating session metadata (not tokens).
|
|
102
|
+
* Token refresh should use the /api/auth/refresh endpoint.
|
|
103
|
+
*
|
|
104
|
+
* Body:
|
|
105
|
+
* - metadata: object - Custom metadata to store in session
|
|
106
|
+
*/
|
|
107
|
+
export async function POST(req: NextRequest) {
|
|
108
|
+
try {
|
|
109
|
+
const authSession = await getBetterAuthSession(req);
|
|
110
|
+
|
|
111
|
+
if (!authSession) {
|
|
112
|
+
return NextResponse.json({
|
|
113
|
+
error: 'No session found',
|
|
114
|
+
code: 'UNAUTHORIZED'
|
|
115
|
+
}, { status: 401 });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const sessionToken = authSession.session?.token;
|
|
119
|
+
if (!sessionToken) {
|
|
120
|
+
return NextResponse.json({
|
|
121
|
+
error: 'Invalid session',
|
|
122
|
+
code: 'INVALID_SESSION'
|
|
123
|
+
}, { status: 400 });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const body = await req.json();
|
|
127
|
+
const { metadata, access_token, refresh_token, twoFactorComplete, twoFactorMethod } = body;
|
|
128
|
+
|
|
129
|
+
// Get current session from Redis
|
|
130
|
+
const session = await getRedisSession(sessionToken);
|
|
131
|
+
if (!session) {
|
|
132
|
+
return NextResponse.json({
|
|
133
|
+
error: 'Session not found',
|
|
134
|
+
code: 'SESSION_NOT_FOUND'
|
|
135
|
+
}, { status: 404 });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Update session with new data
|
|
139
|
+
const updatedSession = {
|
|
140
|
+
...session,
|
|
141
|
+
...(access_token ? { accessToken: access_token } : {}),
|
|
142
|
+
...(refresh_token ? { refreshToken: refresh_token } : {}),
|
|
143
|
+
...(typeof twoFactorComplete === 'boolean' ? { twoFactorComplete } : {}),
|
|
144
|
+
...(twoFactorMethod ? { twoFactorMethod } : {}),
|
|
145
|
+
...(metadata ? {
|
|
146
|
+
metadata: {
|
|
147
|
+
...(session.metadata || {}),
|
|
148
|
+
...metadata,
|
|
149
|
+
updatedAt: new Date().toISOString()
|
|
150
|
+
}
|
|
151
|
+
} : {})
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
await updateSession(sessionToken, updatedSession);
|
|
155
|
+
|
|
156
|
+
return NextResponse.json({
|
|
157
|
+
success: true,
|
|
158
|
+
message: 'Session updated successfully'
|
|
159
|
+
});
|
|
160
|
+
} catch (error) {
|
|
161
|
+
console.error('[SESSION_ROUTE] Error updating session:', error);
|
|
162
|
+
return NextResponse.json({
|
|
163
|
+
error: 'Failed to update session',
|
|
164
|
+
details: error instanceof Error ? error.message : 'Unknown error'
|
|
165
|
+
}, { status: 500 });
|
|
166
|
+
}
|
|
167
167
|
}
|
|
@@ -35,6 +35,60 @@ async function getTenantRequiresTwoFactor(): Promise<boolean> {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Which 2FA methods the TENANT permits — cards 141688 + 141741.
|
|
40
|
+
*
|
|
41
|
+
* WHY THIS IS HERE: this endpoint answered "is 2FA required, and is it done?"
|
|
42
|
+
* and said nothing about WITH WHAT. Both the middleware gate and the
|
|
43
|
+
* verify-code page call this route, and NEITHER had any method data — so
|
|
44
|
+
* nothing in the system asked whether the user CAN complete the challenge it
|
|
45
|
+
* is about to demand. A user whose tenant allows only `totp`, with no
|
|
46
|
+
* authenticator enrolled, is sent to a page that offers email and sms.
|
|
47
|
+
*
|
|
48
|
+
* ⚠ THIS IS NOT A COMPLETION SIGNAL AND MUST NEVER BE READ AS ONE.
|
|
49
|
+
* It says what the tenant PERMITS. It says nothing about what the user has
|
|
50
|
+
* enrolled, and nothing about whether they have verified. The invariant
|
|
51
|
+
* (QAPert, acceptance J2/J5) is: METHOD-PRESENCE IS NOT VERIFIED. A user with
|
|
52
|
+
* one method is still challenged and still gated until they actually verify.
|
|
53
|
+
* `userHasCompletedTenantTwoFactorRequirements` remains the only field that
|
|
54
|
+
* answers "are they through".
|
|
55
|
+
*
|
|
56
|
+
* STILL MISSING, deliberately not guessed here: the ENROLLED set. That lives
|
|
57
|
+
* behind /api/mfa/methods and needs a call this route does not currently make.
|
|
58
|
+
* Allowed-only closes the "offered a method the tenant forbids" half; it does
|
|
59
|
+
* NOT close "has nothing enrolled and no way to enrol". Both halves are named
|
|
60
|
+
* in the cards so neither gets lost.
|
|
61
|
+
*
|
|
62
|
+
* NULL MEANS UNKNOWN — IT DOES NOT MEAN "NONE", AND IT DOES NOT MEAN
|
|
63
|
+
* "email and sms". If the client config cannot be read we do not know what the
|
|
64
|
+
* tenant permits, and every available guess is wrong in a way that matters:
|
|
65
|
+
*
|
|
66
|
+
* - guessing ['email','sms'] fails OPEN. On a totp-only tenant that offers
|
|
67
|
+
* the user two methods the tenant forbids — which is defect 141688 itself,
|
|
68
|
+
* re-created by its own fix during a config outage.
|
|
69
|
+
* - guessing [] fails CLOSED but locks the user out of a challenge they may
|
|
70
|
+
* be perfectly able to complete.
|
|
71
|
+
*
|
|
72
|
+
* So we report the truth (we don't know) and let the consumer decide, rather
|
|
73
|
+
* than encoding a guess that reads like a measurement. Same reasoning as the
|
|
74
|
+
* nullable require_2fa fix: "absent" has to be expressible, or callers cannot
|
|
75
|
+
* tell a real value from a default.
|
|
76
|
+
*
|
|
77
|
+
* No local default: `allowed2FAMethods` is non-optional on AuthSettings and is
|
|
78
|
+
* populated on BOTH config construction paths (idp-client-config.ts:333, :514)
|
|
79
|
+
* with the tenant's value or the layer's own ['email','sms']. Repeating that
|
|
80
|
+
* default here would be a second source for one setting, free to drift.
|
|
81
|
+
*/
|
|
82
|
+
async function getTenantAllowed2FAMethods(): Promise<string[] | null> {
|
|
83
|
+
try {
|
|
84
|
+
const config = await getIDPClientConfig();
|
|
85
|
+
return config.authSettings.allowed2FAMethods;
|
|
86
|
+
} catch {
|
|
87
|
+
console.warn('[VIABILITY] Could not get client config — reporting allowed 2FA methods as UNKNOWN (null), not guessing');
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
38
92
|
/**
|
|
39
93
|
* GET /api/session/viability - Check if session is viable
|
|
40
94
|
*
|
|
@@ -86,6 +140,31 @@ export async function GET(req: NextRequest) {
|
|
|
86
140
|
});
|
|
87
141
|
}
|
|
88
142
|
|
|
143
|
+
// Cards 141688/141741 — the IDP's routing decision, relayed verbatim.
|
|
144
|
+
// See the SessionData.nextStep doc. This is the field that tells the gate
|
|
145
|
+
// whether the user CAN complete a challenge; without it both consumers were
|
|
146
|
+
// sending zero-method users to a page they had no way to act on.
|
|
147
|
+
//
|
|
148
|
+
// Declared once, here, above every return: `session` is settled by this
|
|
149
|
+
// point, and all three authenticated responses must carry the SAME value.
|
|
150
|
+
// Deriving it per-branch would let the paths drift apart silently.
|
|
151
|
+
//
|
|
152
|
+
// NO `?? null` HERE, DELIBERATELY — and this is the whole safety argument:
|
|
153
|
+
//
|
|
154
|
+
// undefined = we never captured it. Every session minted BEFORE this
|
|
155
|
+
// change is in that state, and there are live ones in Redis
|
|
156
|
+
// right now. JSON.stringify omits the key, so the consumer
|
|
157
|
+
// sees no field and knows it is uninformed.
|
|
158
|
+
// null = the IDP affirmatively said "nothing outstanding".
|
|
159
|
+
//
|
|
160
|
+
// Collapsing the first into the second with `?? null` would tell the gate
|
|
161
|
+
// "fully authenticated, nothing to do" about every pre-existing session —
|
|
162
|
+
// including a zero-method user who must be sent to enrol. That is a
|
|
163
|
+
// fail-open on the entire existing session population, produced by a
|
|
164
|
+
// default that reads like a measurement. Same mistake as the nullable
|
|
165
|
+
// require_2fa case: absent has to stay expressible.
|
|
166
|
+
const nextStep = session?.nextStep;
|
|
167
|
+
|
|
89
168
|
// Check access token expiry
|
|
90
169
|
const now = Math.floor(Date.now() / 1000);
|
|
91
170
|
const accessTokenExpires = (token as any).accessTokenExpires || token.exp;
|
|
@@ -93,6 +172,8 @@ export async function GET(req: NextRequest) {
|
|
|
93
172
|
if (!accessTokenExpires) {
|
|
94
173
|
// No expiry info, assume viable but recommend refresh
|
|
95
174
|
const tenantRequiresTwoFactor = await getTenantRequiresTwoFactor();
|
|
175
|
+
// Cards 141688/141741 — see getTenantAllowed2FAMethods. NOT a completion signal.
|
|
176
|
+
const tenantAllowed2FAMethods = await getTenantAllowed2FAMethods();
|
|
96
177
|
|
|
97
178
|
// CRITICAL: Check if MFA has expired (2FA TTL enforcement)
|
|
98
179
|
const mfaExpiresAt = session?.mfaExpiresAt || 0;
|
|
@@ -115,6 +196,8 @@ export async function GET(req: NextRequest) {
|
|
|
115
196
|
tenantRequiresTwoFactor,
|
|
116
197
|
userHasCompletedTenantTwoFactorRequirements,
|
|
117
198
|
userStillNeedsTwoFactor,
|
|
199
|
+
tenantAllowed2FAMethods,
|
|
200
|
+
nextStep,
|
|
118
201
|
// Legacy field names for backwards compatibility
|
|
119
202
|
requires2FA: tenantRequiresTwoFactor,
|
|
120
203
|
twoFactorComplete: userHasCompletedTenantTwoFactorRequirements,
|
|
@@ -137,6 +220,8 @@ export async function GET(req: NextRequest) {
|
|
|
137
220
|
|
|
138
221
|
// CLEAR NAMING: Tenant-wide 2FA requirement from client config
|
|
139
222
|
const tenantRequiresTwoFactor = await getTenantRequiresTwoFactor();
|
|
223
|
+
// Cards 141688/141741 — see getTenantAllowed2FAMethods. NOT a completion signal.
|
|
224
|
+
const tenantAllowed2FAMethods = await getTenantAllowed2FAMethods();
|
|
140
225
|
|
|
141
226
|
// CRITICAL: Check if MFA has expired (2FA TTL enforcement)
|
|
142
227
|
// The session may have mfaVerified=true from days ago, but if mfaExpiresAt
|
|
@@ -181,6 +266,8 @@ export async function GET(req: NextRequest) {
|
|
|
181
266
|
tenantRequiresTwoFactor,
|
|
182
267
|
userHasCompletedTenantTwoFactorRequirements,
|
|
183
268
|
userStillNeedsTwoFactor,
|
|
269
|
+
tenantAllowed2FAMethods,
|
|
270
|
+
nextStep,
|
|
184
271
|
// Legacy names for backwards compatibility
|
|
185
272
|
requires2FA: tenantRequiresTwoFactor,
|
|
186
273
|
twoFactorComplete: userHasCompletedTenantTwoFactorRequirements,
|
|
@@ -203,6 +290,8 @@ export async function GET(req: NextRequest) {
|
|
|
203
290
|
tenantRequiresTwoFactor,
|
|
204
291
|
userHasCompletedTenantTwoFactorRequirements,
|
|
205
292
|
userStillNeedsTwoFactor,
|
|
293
|
+
tenantAllowed2FAMethods,
|
|
294
|
+
nextStep,
|
|
206
295
|
// Legacy names for backwards compatibility
|
|
207
296
|
requires2FA: tenantRequiresTwoFactor,
|
|
208
297
|
twoFactorComplete: userHasCompletedTenantTwoFactorRequirements,
|
package/src/routes/index.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @payez/next-mvp Route Module Exports
|
|
3
|
-
*
|
|
4
|
-
* Ready-to-use route handlers for quick integration
|
|
5
|
-
*
|
|
6
|
-
* @version 2.3.0
|
|
7
|
-
* @since auth-ready-v2
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
// Export auth routes
|
|
11
|
-
export * from './auth';
|
|
12
|
-
|
|
13
|
-
// Export account/2FA routes
|
|
14
|
-
export * from './account';
|
|
15
|
-
|
|
16
|
-
// Namespace exports for cleaner imports
|
|
17
|
-
export * as auth from './auth';
|
|
1
|
+
/**
|
|
2
|
+
* @payez/next-mvp Route Module Exports
|
|
3
|
+
*
|
|
4
|
+
* Ready-to-use route handlers for quick integration
|
|
5
|
+
*
|
|
6
|
+
* @version 2.3.0
|
|
7
|
+
* @since auth-ready-v2
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Export auth routes
|
|
11
|
+
export * from './auth';
|
|
12
|
+
|
|
13
|
+
// Export account/2FA routes
|
|
14
|
+
export * from './account';
|
|
15
|
+
|
|
16
|
+
// Namespace exports for cleaner imports
|
|
17
|
+
export * as auth from './auth';
|
|
18
18
|
export * as account from './account';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { POST } from '../../api-handlers/mfa/backup-code-verify';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ready-to-Use MFA Backup Codes Route
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```typescript
|
|
6
|
+
* // app/api/mfa/backup-codes/route.ts
|
|
7
|
+
* export { POST } from '@payez/next-mvp/routes/mfa/backup-codes';
|
|
8
|
+
* ```
|
|
9
|
+
*/
|
|
10
|
+
export { POST } from '../../api-handlers/mfa/backup-codes';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ready-to-Use MFA Methods Route (list / remove)
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```typescript
|
|
6
|
+
* // app/api/mfa/methods/route.ts
|
|
7
|
+
* export { GET } from '@payez/next-mvp/routes/mfa/methods';
|
|
8
|
+
* // app/api/mfa/methods/[id]/route.ts
|
|
9
|
+
* export { DELETE } from '@payez/next-mvp/routes/mfa/methods';
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
import { NextRequest } from 'next/server';
|
|
13
|
+
import { GET as listMethods, DELETE as deleteMethod } from '../../api-handlers/mfa/methods';
|
|
14
|
+
|
|
15
|
+
export const GET = listMethods;
|
|
16
|
+
|
|
17
|
+
type Ctx = { params: Promise<{ id: string }> };
|
|
18
|
+
|
|
19
|
+
export async function DELETE(req: NextRequest, ctx: Ctx) {
|
|
20
|
+
const { id } = await ctx.params;
|
|
21
|
+
return deleteMethod(req, id);
|
|
22
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ready-to-Use TOTP Verify Route (2FA CHALLENGE, not enrolment)
|
|
3
|
+
*
|
|
4
|
+
* Verifies an authenticator code at LOGIN and transitions the session to
|
|
5
|
+
* MFA-verified. Pair with totp-enroll/totp-confirm, which only ENROL a factor
|
|
6
|
+
* and deliberately do not touch session 2FA state.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* // app/api/mfa/totp/verify/route.ts
|
|
11
|
+
* export { POST } from '@payez/next-mvp/routes/mfa/totp-verify';
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export { POST } from '../../api-handlers/mfa/totp-verify';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ready-to-Use Passkey Manage Route (rename / revoke one key)
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```typescript
|
|
6
|
+
* // app/api/passkeys/[id]/route.ts
|
|
7
|
+
* export { PATCH, DELETE } from '@payez/next-mvp/routes/passkeys/manage';
|
|
8
|
+
* ```
|
|
9
|
+
*/
|
|
10
|
+
import { NextRequest } from 'next/server';
|
|
11
|
+
import { PATCH as patchOne, DELETE as deleteOne } from '../../api-handlers/passkeys/manage';
|
|
12
|
+
|
|
13
|
+
type Ctx = { params: Promise<{ id: string }> };
|
|
14
|
+
|
|
15
|
+
export async function PATCH(req: NextRequest, ctx: Ctx) {
|
|
16
|
+
const { id } = await ctx.params;
|
|
17
|
+
return patchOne(req, id);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function DELETE(req: NextRequest, ctx: Ctx) {
|
|
21
|
+
const { id } = await ctx.params;
|
|
22
|
+
return deleteOne(req, id);
|
|
23
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ready-to-Use Passkey Registration Options Route
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```typescript
|
|
6
|
+
* // app/api/passkeys/register/options/route.ts
|
|
7
|
+
* export { POST } from '@payez/next-mvp/routes/passkeys/register-options';
|
|
8
|
+
* ```
|
|
9
|
+
*/
|
|
10
|
+
export { POST } from '../../api-handlers/passkeys/register-options';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ready-to-Use Passkey Registration Verify Route
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```typescript
|
|
6
|
+
* // app/api/passkeys/register/verify/route.ts
|
|
7
|
+
* export { POST } from '@payez/next-mvp/routes/passkeys/register-verify';
|
|
8
|
+
* ```
|
|
9
|
+
*/
|
|
10
|
+
export { POST } from '../../api-handlers/passkeys/register-verify';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Session Routes Index
|
|
3
|
-
*
|
|
4
|
-
* Re-exports all session-related route handlers for easy importing.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export { GET as refreshViabilityGET } from './refresh-viability';
|
|
1
|
+
/**
|
|
2
|
+
* Session Routes Index
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all session-related route handlers for easy importing.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { GET as refreshViabilityGET } from './refresh-viability';
|