@payez/next-mvp 4.4.5 → 4.4.6
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/README.md +10 -3
- package/dist/api/auth-handler.js +5 -3
- package/dist/api-handlers/account/change-password.js +6 -3
- package/dist/api-handlers/account/masked-info.js +2 -1
- package/dist/api-handlers/account/recovery/forward.d.ts +14 -0
- package/dist/api-handlers/account/recovery/forward.js +105 -0
- package/dist/api-handlers/account/recovery/initiate.js +4 -0
- package/dist/api-handlers/account/recovery/send-code.d.ts +12 -2
- package/dist/api-handlers/account/recovery/send-code.js +11 -84
- package/dist/api-handlers/account/recovery/verify-code.d.ts +11 -2
- package/dist/api-handlers/account/recovery/verify-code.js +10 -84
- package/dist/api-handlers/account/reset-password.js +24 -1
- package/dist/api-handlers/account/send-code.d.ts +1 -15
- package/dist/api-handlers/account/send-code.js +3 -6
- package/dist/api-handlers/account/update-phone.js +6 -1
- package/dist/api-handlers/account/validate-password.js +3 -1
- package/dist/api-handlers/account/verify-email.d.ts +1 -17
- package/dist/api-handlers/account/verify-email.js +4 -6
- package/dist/api-handlers/account/verify-sms.d.ts +1 -17
- package/dist/api-handlers/account/verify-sms.js +4 -6
- package/dist/api-handlers/admin/analytics.js +12 -4
- package/dist/api-handlers/admin/audit.js +12 -4
- package/dist/api-handlers/admin/redis-sessions.js +16 -8
- package/dist/api-handlers/admin/sessions.js +14 -6
- package/dist/api-handlers/admin/site-logs.js +39 -16
- package/dist/api-handlers/admin/stats.js +14 -4
- package/dist/api-handlers/admin/users.js +14 -6
- package/dist/api-handlers/admin/vibe-data.js +19 -11
- package/dist/api-handlers/auth/jwks.js +2 -1
- package/dist/api-handlers/auth/login.js +13 -4
- package/dist/api-handlers/auth/passkey-signin.js +6 -0
- package/dist/api-handlers/auth/refresh.js +38 -11
- package/dist/api-handlers/auth/signout.js +2 -1
- package/dist/api-handlers/auth/status.js +4 -1
- package/dist/api-handlers/auth/update-session.d.ts +25 -4
- package/dist/api-handlers/auth/update-session.js +38 -11
- package/dist/api-handlers/auth/validate.js +4 -2
- package/dist/api-handlers/auth/verify-code.js +3 -2
- package/dist/api-handlers/mfa/backup-code-verify.d.ts +1 -18
- package/dist/api-handlers/mfa/backup-code-verify.js +4 -6
- package/dist/api-handlers/mfa/backup-codes.js +2 -1
- package/dist/api-handlers/mfa/methods.js +2 -1
- package/dist/api-handlers/mfa/totp-confirm.js +2 -1
- package/dist/api-handlers/mfa/totp-enroll.js +2 -1
- package/dist/api-handlers/mfa/totp-verify.d.ts +1 -19
- package/dist/api-handlers/mfa/totp-verify.js +4 -6
- package/dist/api-handlers/passkeys/list.js +2 -1
- package/dist/api-handlers/passkeys/manage.js +2 -1
- package/dist/api-handlers/passkeys/register-options.js +2 -1
- package/dist/api-handlers/passkeys/register-verify.js +2 -1
- package/dist/api-handlers/session/refresh-viability.d.ts +4 -2
- package/dist/api-handlers/session/refresh-viability.js +10 -7
- package/dist/api-handlers/session/viability.js +4 -3
- package/dist/api-handlers/test/force-expire.js +5 -3
- package/dist/auth/auth-decision.js +2 -1
- package/dist/auth/better-auth.d.ts +14 -0
- package/dist/auth/better-auth.js +35 -49
- package/dist/auth/utils/idp-client.js +2 -1
- package/dist/components/admin/DataBrowserTab.js +4 -1
- package/dist/components/admin/SessionsTab.js +9 -3
- package/dist/components/recovery/RequestedStep.d.ts +11 -0
- package/dist/components/recovery/RequestedStep.js +14 -0
- package/dist/components/recovery/SetPasswordStep.js +10 -1
- package/dist/lib/app-slug.d.ts +21 -0
- package/dist/lib/app-slug.js +51 -0
- package/dist/lib/ensure-fresh-access-token.d.ts +26 -0
- package/dist/lib/ensure-fresh-access-token.js +105 -4
- package/dist/lib/idp-client-config.d.ts +20 -0
- package/dist/lib/idp-client-config.js +66 -45
- package/dist/lib/idp-fetch.js +1 -1
- package/dist/lib/password-rules.d.ts +22 -0
- package/dist/lib/password-rules.js +43 -0
- package/dist/lib/safe-error-response.d.ts +67 -0
- package/dist/lib/safe-error-response.js +130 -0
- package/dist/lib/session-cookie-shape.d.ts +22 -19
- package/dist/lib/session-cookie-shape.js +14 -18
- package/dist/lib/session-store.d.ts +8 -1
- package/dist/lib/session-store.js +170 -24
- package/dist/lib/startup-init.d.ts +6 -0
- package/dist/lib/startup-init.js +30 -4
- package/dist/lib/token-lifecycle.js +2 -1
- package/dist/lib/token-log-id.d.ts +18 -0
- package/dist/lib/token-log-id.js +84 -0
- package/dist/lib/two-factor-verify-failure.d.ts +45 -0
- package/dist/lib/two-factor-verify-failure.js +143 -0
- package/dist/middleware/create-middleware.d.ts +2 -1
- package/dist/middleware/create-middleware.js +43 -8
- package/dist/pages/recovery/page.js +14 -4
- package/dist/pages/test-env/JwtInspectPage.js +7 -149
- package/dist/pages/test-env/RefreshTokenPage.js +4 -1
- package/dist/pages/test-env/redact-credentials.d.ts +6 -0
- package/dist/pages/test-env/redact-credentials.js +101 -0
- package/dist/pages/verify-code/page.js +2 -1
- package/dist/routes/auth/logout.d.ts +2 -1
- package/dist/routes/auth/logout.js +4 -2
- package/dist/routes/auth/session.d.ts +0 -3
- package/dist/routes/auth/session.js +16 -10
- package/dist/routes/auth/viability.d.ts +3 -2
- package/dist/routes/auth/viability.js +11 -7
- package/dist/server/auth.js +10 -6
- package/dist/server/decode-session.js +15 -26
- package/dist/server/verified-session-token.d.ts +13 -0
- package/dist/server/verified-session-token.js +103 -0
- package/dist/stores/authStore.js +14 -3
- package/dist/types/recovery.d.ts +1 -1
- package/dist/utils/api.d.ts +18 -0
- package/dist/utils/api.js +51 -9
- package/dist/vibe/sessions.js +2 -1
- package/package.json +2 -1
- package/scripts/verify-release-provenance.mjs +410 -0
- package/src/api/auth-handler.ts +5 -3
- package/src/api-handlers/account/change-password.ts +6 -3
- package/src/api-handlers/account/masked-info.ts +2 -1
- package/src/api-handlers/account/recovery/forward.ts +117 -0
- package/src/api-handlers/account/recovery/initiate.ts +3 -0
- package/src/api-handlers/account/recovery/send-code.ts +13 -99
- package/src/api-handlers/account/recovery/verify-code.ts +12 -99
- package/src/api-handlers/account/reset-password.ts +30 -1
- package/src/api-handlers/account/send-code.ts +3 -9
- package/src/api-handlers/account/update-phone.ts +6 -1
- package/src/api-handlers/account/validate-password.ts +3 -1
- package/src/api-handlers/account/verify-email.ts +4 -9
- package/src/api-handlers/account/verify-sms.ts +4 -9
- package/src/api-handlers/admin/analytics.ts +13 -5
- package/src/api-handlers/admin/audit.ts +13 -5
- package/src/api-handlers/admin/redis-sessions.ts +16 -8
- package/src/api-handlers/admin/sessions.ts +15 -7
- package/src/api-handlers/admin/site-logs.ts +39 -16
- package/src/api-handlers/admin/stats.ts +15 -5
- package/src/api-handlers/admin/users.ts +15 -7
- package/src/api-handlers/admin/vibe-data.ts +20 -12
- package/src/api-handlers/auth/jwks.ts +2 -1
- package/src/api-handlers/auth/login.ts +13 -4
- package/src/api-handlers/auth/passkey-signin.ts +4 -0
- package/src/api-handlers/auth/refresh.ts +38 -11
- package/src/api-handlers/auth/signout.ts +2 -1
- package/src/api-handlers/auth/status.ts +4 -1
- package/src/api-handlers/auth/update-session.ts +45 -11
- package/src/api-handlers/auth/validate.ts +4 -2
- package/src/api-handlers/auth/verify-code.ts +3 -2
- package/src/api-handlers/mfa/backup-code-verify.ts +4 -9
- package/src/api-handlers/mfa/backup-codes.ts +2 -1
- package/src/api-handlers/mfa/methods.ts +2 -1
- package/src/api-handlers/mfa/totp-confirm.ts +2 -1
- package/src/api-handlers/mfa/totp-enroll.ts +2 -1
- package/src/api-handlers/mfa/totp-verify.ts +4 -9
- package/src/api-handlers/passkeys/list.ts +2 -1
- package/src/api-handlers/passkeys/manage.ts +2 -1
- package/src/api-handlers/passkeys/register-options.ts +2 -1
- package/src/api-handlers/passkeys/register-verify.ts +2 -1
- package/src/api-handlers/session/refresh-viability.ts +10 -7
- package/src/api-handlers/session/viability.ts +4 -3
- package/src/api-handlers/test/force-expire.ts +5 -3
- package/src/auth/auth-decision.ts +2 -1
- package/src/auth/better-auth.ts +37 -55
- package/src/auth/utils/idp-client.ts +2 -1
- package/src/components/admin/DataBrowserTab.tsx +4 -1
- package/src/components/admin/SessionsTab.tsx +10 -3
- package/src/components/recovery/RequestedStep.tsx +30 -0
- package/src/components/recovery/SetPasswordStep.tsx +24 -3
- package/src/hooks/useViabilitySession.ts +2 -1
- package/src/lib/app-slug.ts +50 -0
- package/src/lib/ensure-fresh-access-token.ts +133 -4
- package/src/lib/idp-client-config.ts +91 -45
- package/src/lib/idp-fetch.ts +1 -1
- package/src/lib/password-rules.ts +50 -0
- package/src/lib/safe-error-response.ts +126 -0
- package/src/lib/session-cookie-shape.ts +23 -22
- package/src/lib/session-store.ts +167 -24
- package/src/lib/startup-init.ts +29 -4
- package/src/lib/token-lifecycle.ts +2 -1
- package/src/lib/token-log-id.ts +69 -0
- package/src/lib/two-factor-verify-failure.ts +167 -0
- package/src/middleware/create-middleware.ts +55 -9
- package/src/pages/recovery/page.tsx +21 -4
- package/src/pages/test-env/JwtInspectPage.tsx +11 -233
- package/src/pages/test-env/RefreshTokenPage.tsx +9 -4
- package/src/pages/test-env/redact-credentials.ts +96 -0
- package/src/pages/verify-code/page.tsx +2 -1
- package/src/routes/auth/logout.ts +4 -2
- package/src/routes/auth/session.ts +16 -10
- package/src/routes/auth/viability.ts +11 -7
- package/src/server/auth.ts +10 -6
- package/src/server/decode-session.ts +16 -26
- package/src/server/verified-session-token.ts +103 -0
- package/src/stores/authStore.ts +16 -5
- package/src/types/recovery.ts +1 -1
- package/src/utils/api.ts +47 -8
- package/src/vibe/sessions.ts +2 -1
|
@@ -28,22 +28,4 @@
|
|
|
28
28
|
* @package @payez/next-mvp
|
|
29
29
|
*/
|
|
30
30
|
import { NextRequest, NextResponse } from 'next/server';
|
|
31
|
-
export declare function POST(req: NextRequest): Promise<NextResponse<
|
|
32
|
-
success: boolean;
|
|
33
|
-
error: string;
|
|
34
|
-
code: string;
|
|
35
|
-
}> | NextResponse<{
|
|
36
|
-
success: boolean;
|
|
37
|
-
error: any;
|
|
38
|
-
code: any;
|
|
39
|
-
meta: {
|
|
40
|
-
attemptedRefresh: boolean;
|
|
41
|
-
};
|
|
42
|
-
}> | NextResponse<{
|
|
43
|
-
message: any;
|
|
44
|
-
requiresChallenge?: boolean | undefined;
|
|
45
|
-
nextStep?: "mfa_verify" | undefined;
|
|
46
|
-
success: boolean;
|
|
47
|
-
verificationSuccessful: boolean;
|
|
48
|
-
twoFactorSessionVerified: boolean;
|
|
49
|
-
}>>;
|
|
31
|
+
export declare function POST(req: NextRequest): Promise<NextResponse<unknown>>;
|
|
@@ -36,6 +36,7 @@ const env_1 = require("../../config/env");
|
|
|
36
36
|
const test_aware_get_token_1 = require("../../lib/test-aware-get-token");
|
|
37
37
|
const session_store_1 = require("../../lib/session-store");
|
|
38
38
|
const jwt_decode_1 = require("../../lib/jwt-decode");
|
|
39
|
+
const two_factor_verify_failure_1 = require("../../lib/two-factor-verify-failure");
|
|
39
40
|
async function POST(req) {
|
|
40
41
|
try {
|
|
41
42
|
const body = await req.json();
|
|
@@ -55,12 +56,9 @@ async function POST(req) {
|
|
|
55
56
|
body: JSON.stringify({ code }),
|
|
56
57
|
});
|
|
57
58
|
if (!result.ok) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
code: result.json?.code || 'IDP_ERROR',
|
|
62
|
-
meta: { attemptedRefresh: result.attemptedRefresh },
|
|
63
|
-
}, { status: result.status });
|
|
59
|
+
// PAY-1443: never relay the IDP's error text - it arrives as a C# anonymous-object
|
|
60
|
+
// ToString ("{ message = ... }") on the deployed IDP. Classify it, answer a fixed sentence.
|
|
61
|
+
return (0, two_factor_verify_failure_1.twoFactorVerifyFailure)(result, 'totp');
|
|
64
62
|
}
|
|
65
63
|
// Unwrap IDP envelope { success, data }
|
|
66
64
|
const unwrappedData = result.json?.data || result.json;
|
|
@@ -4,6 +4,7 @@ exports.GET = GET;
|
|
|
4
4
|
const server_1 = require("next/server");
|
|
5
5
|
const idp_fetch_1 = require("../../lib/idp-fetch");
|
|
6
6
|
const env_1 = require("../../config/env");
|
|
7
|
+
const safe_error_response_1 = require("../../lib/safe-error-response");
|
|
7
8
|
/**
|
|
8
9
|
* List the signed-in user's passkeys (GET /api/Passkeys, user JWT).
|
|
9
10
|
* Probe-verified live 2026-08-01 (anon -> 401 = routed).
|
|
@@ -15,7 +16,7 @@ async function GET(req) {
|
|
|
15
16
|
return server_1.NextResponse.json({
|
|
16
17
|
success: false,
|
|
17
18
|
message: 'Upstream error',
|
|
18
|
-
error: { code: 'UPSTREAM_SERVICE_ERROR', status: result.status, details: result.json },
|
|
19
|
+
error: { code: 'UPSTREAM_SERVICE_ERROR', status: result.status, details: (0, safe_error_response_1.projectIdpError)(result.json) },
|
|
19
20
|
meta: { attemptedRefresh: result.attemptedRefresh },
|
|
20
21
|
}, { status: result.status });
|
|
21
22
|
}
|
|
@@ -5,6 +5,7 @@ exports.DELETE = DELETE;
|
|
|
5
5
|
const server_1 = require("next/server");
|
|
6
6
|
const idp_fetch_1 = require("../../lib/idp-fetch");
|
|
7
7
|
const env_1 = require("../../config/env");
|
|
8
|
+
const safe_error_response_1 = require("../../lib/safe-error-response");
|
|
8
9
|
/**
|
|
9
10
|
* Manage ONE passkey (PATCH rename / DELETE revoke, /api/Passkeys/{id},
|
|
10
11
|
* user JWT). The id arrives from the Next dynamic segment via the routes
|
|
@@ -14,7 +15,7 @@ function upstreamError(result) {
|
|
|
14
15
|
return server_1.NextResponse.json({
|
|
15
16
|
success: false,
|
|
16
17
|
message: 'Upstream error',
|
|
17
|
-
error: { code: 'UPSTREAM_SERVICE_ERROR', status: result.status, details: result.json },
|
|
18
|
+
error: { code: 'UPSTREAM_SERVICE_ERROR', status: result.status, details: (0, safe_error_response_1.projectIdpError)(result.json) },
|
|
18
19
|
meta: { attemptedRefresh: result.attemptedRefresh },
|
|
19
20
|
}, { status: result.status });
|
|
20
21
|
}
|
|
@@ -4,6 +4,7 @@ exports.POST = POST;
|
|
|
4
4
|
const server_1 = require("next/server");
|
|
5
5
|
const idp_fetch_1 = require("../../lib/idp-fetch");
|
|
6
6
|
const env_1 = require("../../config/env");
|
|
7
|
+
const safe_error_response_1 = require("../../lib/safe-error-response");
|
|
7
8
|
/**
|
|
8
9
|
* Begin the passkey REGISTRATION ceremony (POST /api/Passkeys/register/options,
|
|
9
10
|
* user JWT — a credential change; the IDP may demand step-up, surfaced as-is).
|
|
@@ -27,7 +28,7 @@ async function POST(req) {
|
|
|
27
28
|
return server_1.NextResponse.json({
|
|
28
29
|
success: false,
|
|
29
30
|
message: 'Upstream error',
|
|
30
|
-
error: { code: 'UPSTREAM_SERVICE_ERROR', status: result.status, details: result.json },
|
|
31
|
+
error: { code: 'UPSTREAM_SERVICE_ERROR', status: result.status, details: (0, safe_error_response_1.projectIdpError)(result.json) },
|
|
31
32
|
meta: { attemptedRefresh: result.attemptedRefresh },
|
|
32
33
|
}, { status: result.status });
|
|
33
34
|
}
|
|
@@ -4,6 +4,7 @@ exports.POST = POST;
|
|
|
4
4
|
const server_1 = require("next/server");
|
|
5
5
|
const idp_fetch_1 = require("../../lib/idp-fetch");
|
|
6
6
|
const env_1 = require("../../config/env");
|
|
7
|
+
const safe_error_response_1 = require("../../lib/safe-error-response");
|
|
7
8
|
/**
|
|
8
9
|
* Finish the passkey REGISTRATION ceremony (POST /api/Passkeys/register/verify,
|
|
9
10
|
* user JWT). Body forwarded VERBATIM — it carries the enrolment handle plus the
|
|
@@ -26,7 +27,7 @@ async function POST(req) {
|
|
|
26
27
|
return server_1.NextResponse.json({
|
|
27
28
|
success: false,
|
|
28
29
|
message: 'Upstream error',
|
|
29
|
-
error: { code: 'UPSTREAM_SERVICE_ERROR', status: result.status, details: result.json },
|
|
30
|
+
error: { code: 'UPSTREAM_SERVICE_ERROR', status: result.status, details: (0, safe_error_response_1.projectIdpError)(result.json) },
|
|
30
31
|
meta: { attemptedRefresh: result.attemptedRefresh },
|
|
31
32
|
}, { status: result.status });
|
|
32
33
|
}
|
|
@@ -2,13 +2,15 @@ import { NextRequest, NextResponse } from 'next/server';
|
|
|
2
2
|
export declare function GET(req: NextRequest): Promise<NextResponse<{
|
|
3
3
|
canRefresh: boolean;
|
|
4
4
|
reason: string;
|
|
5
|
-
|
|
5
|
+
sessionTokenId: null;
|
|
6
6
|
}> | NextResponse<{
|
|
7
7
|
canRefresh: boolean;
|
|
8
8
|
reason: string;
|
|
9
|
-
|
|
9
|
+
sessionTokenId: string;
|
|
10
10
|
}> | NextResponse<{
|
|
11
11
|
canRefresh: boolean;
|
|
12
12
|
reason: string;
|
|
13
13
|
error: string;
|
|
14
|
+
code: string;
|
|
15
|
+
correlationId: string;
|
|
14
16
|
}>>;
|
|
@@ -6,6 +6,7 @@ const session_store_1 = require("../../lib/session-store");
|
|
|
6
6
|
const refresh_token_validator_1 = require("../../lib/refresh-token-validator");
|
|
7
7
|
const test_aware_get_token_1 = require("../../lib/test-aware-get-token");
|
|
8
8
|
const logger_1 = require("../../config/logger");
|
|
9
|
+
const token_log_id_1 = require("../../lib/token-log-id");
|
|
9
10
|
async function GET(req) {
|
|
10
11
|
try {
|
|
11
12
|
const sessionToken = req.nextUrl.searchParams.get('token');
|
|
@@ -16,24 +17,26 @@ async function GET(req) {
|
|
|
16
17
|
finalSessionToken = (token?.sessionToken || token?.redisSessionId);
|
|
17
18
|
}
|
|
18
19
|
if (!finalSessionToken) {
|
|
19
|
-
return server_1.NextResponse.json({ canRefresh: false, reason: 'not_logged_in',
|
|
20
|
+
return server_1.NextResponse.json({ canRefresh: false, reason: 'not_logged_in', sessionTokenId: null }, { status: 200 });
|
|
20
21
|
}
|
|
21
22
|
const refreshInProgress = await (0, session_store_1.isRefreshInProgress)(finalSessionToken);
|
|
22
23
|
if (refreshInProgress) {
|
|
23
24
|
// Still need to get session data for twoFactorComplete even when refresh is in progress
|
|
24
25
|
const sessionData = await (0, session_store_1.getSession)(finalSessionToken);
|
|
25
|
-
logger_1.logger.info('[REFRESH-VIABILITY] Refresh already in progress, telling middleware to wait', { sessionToken:
|
|
26
|
-
return server_1.NextResponse.json({ canRefresh: true, reason: 'refresh_in_progress', refreshInProgress: true,
|
|
26
|
+
logger_1.logger.info('[REFRESH-VIABILITY] Refresh already in progress, telling middleware to wait', { sessionToken: (0, token_log_id_1.tokenLogId)(finalSessionToken) });
|
|
27
|
+
return server_1.NextResponse.json({ canRefresh: true, reason: 'refresh_in_progress', refreshInProgress: true, sessionTokenId: (0, token_log_id_1.tokenLogId)(finalSessionToken), twoFactorComplete: sessionData?.mfaVerified ?? false }, { status: 200 });
|
|
27
28
|
}
|
|
28
29
|
const sessionData = await (0, session_store_1.getSession)(finalSessionToken);
|
|
29
30
|
if (!sessionData) {
|
|
30
|
-
return server_1.NextResponse.json({ canRefresh: false, reason: 'session_not_found',
|
|
31
|
+
return server_1.NextResponse.json({ canRefresh: false, reason: 'session_not_found', sessionTokenId: (0, token_log_id_1.tokenLogId)(finalSessionToken) }, { status: 200 });
|
|
31
32
|
}
|
|
32
33
|
const viabilityCheck = (0, refresh_token_validator_1.checkRefreshViability)(sessionData);
|
|
33
|
-
return server_1.NextResponse.json({ canRefresh: viabilityCheck.canRefresh, reason: viabilityCheck.reason, timeRemaining: viabilityCheck.timeRemaining, expiresAt: viabilityCheck.expiresAt, accessTokenExpired: viabilityCheck.accessTokenExpired, accessTokenTimeRemaining: viabilityCheck.accessTokenTimeRemaining,
|
|
34
|
+
return server_1.NextResponse.json({ canRefresh: viabilityCheck.canRefresh, reason: viabilityCheck.reason, timeRemaining: viabilityCheck.timeRemaining, expiresAt: viabilityCheck.expiresAt, accessTokenExpired: viabilityCheck.accessTokenExpired, accessTokenTimeRemaining: viabilityCheck.accessTokenTimeRemaining, sessionTokenId: (0, token_log_id_1.tokenLogId)(finalSessionToken), twoFactorComplete: sessionData.mfaVerified ?? false, userId: sessionData.userId, refreshInProgress: false });
|
|
34
35
|
}
|
|
35
36
|
catch (error) {
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
const correlationId = globalThis.crypto.randomUUID();
|
|
38
|
+
logger_1.logger.error('[REFRESH-VIABILITY] Error checking refresh viability', { correlationId, error: error instanceof Error ? error.message : String(error) });
|
|
39
|
+
// PAY-1438: the exception text stays in the log above; the caller gets the id.
|
|
40
|
+
return server_1.NextResponse.json({ canRefresh: false, reason: 'check_error', error: 'Failed to check refresh viability', code: 'INTERNAL_ERROR', correlationId }, { status: 500 });
|
|
38
41
|
}
|
|
39
42
|
}
|
|
@@ -11,6 +11,7 @@ const session_store_1 = require("../../lib/session-store");
|
|
|
11
11
|
const auth_1 = require("../../server/auth");
|
|
12
12
|
const startup_init_1 = require("../../lib/startup-init");
|
|
13
13
|
const idp_client_config_1 = require("../../lib/idp-client-config");
|
|
14
|
+
const token_log_id_1 = require("../../lib/token-log-id");
|
|
14
15
|
async function GET(req) {
|
|
15
16
|
try {
|
|
16
17
|
// Ensure initialization is complete (auth signing secret resolved from IDP)
|
|
@@ -109,7 +110,7 @@ async function GET(req) {
|
|
|
109
110
|
const sessionMfaComplete = sessionData.mfaVerified ?? false;
|
|
110
111
|
const effectiveTwoFactorComplete = sessionMfaComplete && !mfaExpired;
|
|
111
112
|
console.log('[VIABILITY] Session 2FA check:', {
|
|
112
|
-
sessionToken:
|
|
113
|
+
sessionToken: (0, token_log_id_1.tokenLogId)(sessionToken),
|
|
113
114
|
mfaVerified: sessionData.mfaVerified,
|
|
114
115
|
twoFactorComplete: sessionData.twoFactorComplete,
|
|
115
116
|
sessionMfaComplete,
|
|
@@ -125,7 +126,7 @@ async function GET(req) {
|
|
|
125
126
|
}
|
|
126
127
|
const response = {
|
|
127
128
|
authenticated: true,
|
|
128
|
-
sessionToken, //
|
|
129
|
+
sessionTokenId: (0, token_log_id_1.tokenLogId)(sessionToken), // PAY-1481: correlation id for middleware tracking, never the token
|
|
129
130
|
// 2FA fields - critical for middleware redirect logic
|
|
130
131
|
requires2FA, // From cached client config (client-wide setting)
|
|
131
132
|
twoFactorComplete: effectiveTwoFactorComplete, // From session, BUT respects MFA TTL
|
|
@@ -159,7 +160,7 @@ async function GET(req) {
|
|
|
159
160
|
console.warn('[VIABILITY] Stale cookie detected - session not in Redis');
|
|
160
161
|
return server_1.NextResponse.json({
|
|
161
162
|
authenticated: false,
|
|
162
|
-
sessionToken //
|
|
163
|
+
sessionTokenId: sessionToken ? (0, token_log_id_1.tokenLogId)(sessionToken) : null // PAY-1481: presence + correlation for stale cookie detection
|
|
163
164
|
});
|
|
164
165
|
}
|
|
165
166
|
// If there's no token at all, it's not authenticated
|
|
@@ -4,6 +4,8 @@ exports.POST = void 0;
|
|
|
4
4
|
const server_1 = require("next/server");
|
|
5
5
|
const auth_1 = require("../../server/auth");
|
|
6
6
|
const session_store_1 = require("../../lib/session-store");
|
|
7
|
+
const safe_error_response_1 = require("../../lib/safe-error-response");
|
|
8
|
+
const token_log_id_1 = require("../../lib/token-log-id");
|
|
7
9
|
/**
|
|
8
10
|
* ⚠️ DESTRUCTIVE TEST-ONLY HANDLER — DO NOT MOUNT IN PRODUCTION. ⚠️
|
|
9
11
|
*
|
|
@@ -134,7 +136,7 @@ const POST = async (req) => {
|
|
|
134
136
|
const prev = session.idpAccessTokenExpires || null;
|
|
135
137
|
await (0, session_store_1.updateSession)(sessionToken, { idpAccessTokenExpires: forced });
|
|
136
138
|
console.log('[TEST_EXPIRE] Forced access token expiry for session', {
|
|
137
|
-
sessionToken:
|
|
139
|
+
sessionToken: (0, token_log_id_1.tokenLogId)(sessionToken),
|
|
138
140
|
previous: prev ? new Date(prev).toISOString() : null,
|
|
139
141
|
newExpiry: new Date(forced).toISOString()
|
|
140
142
|
});
|
|
@@ -147,8 +149,8 @@ const POST = async (req) => {
|
|
|
147
149
|
});
|
|
148
150
|
}
|
|
149
151
|
catch (e) {
|
|
150
|
-
|
|
151
|
-
return server_1.NextResponse.json({ success: false, error:
|
|
152
|
+
const correlationId = (0, safe_error_response_1.logExceptionWithCorrelation)('[TEST_EXPIRE] Error:', e);
|
|
153
|
+
return server_1.NextResponse.json({ success: false, error: 'Failed to force expiry', code: 'INTERNAL_ERROR', correlationId }, { status: 500 });
|
|
152
154
|
}
|
|
153
155
|
};
|
|
154
156
|
exports.POST = POST;
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.makeAuthDecision = makeAuthDecision;
|
|
10
10
|
const safe_callback_1 = require("./safe-callback");
|
|
11
|
+
const token_log_id_1 = require("../lib/token-log-id");
|
|
11
12
|
/**
|
|
12
13
|
* Main decision function - pure, testable, deterministic
|
|
13
14
|
*/
|
|
@@ -99,7 +100,7 @@ function makeAuthDecision(context) {
|
|
|
99
100
|
pathname,
|
|
100
101
|
isLoginPage,
|
|
101
102
|
sessionExists: sessionStatus.exists,
|
|
102
|
-
sessionToken:
|
|
103
|
+
sessionToken: (0, token_log_id_1.tokenLogId)(sessionPointer.sessionToken)
|
|
103
104
|
});
|
|
104
105
|
// Already on the 2FA page
|
|
105
106
|
if (pathname === '/account-auth/verify-code') {
|
|
@@ -476,6 +476,20 @@ export declare function __getBetterAuthInstance(): unknown;
|
|
|
476
476
|
* cannot be applied retroactively.
|
|
477
477
|
*/
|
|
478
478
|
export declare function configureBetterAuth(opts: CreateBetterAuthInstanceOptions): void;
|
|
479
|
+
/**
|
|
480
|
+
* PAY-642 (card 338215): the init attempt no longer LATCHES A FAILURE.
|
|
481
|
+
*
|
|
482
|
+
* This used to be `state.initPromise ??= getIDPClientConfig(true).then(...)` with no .catch and no reset. One
|
|
483
|
+
* auth request against a cold process while the IdP was unreachable stored a rejected promise, and every later
|
|
484
|
+
* caller replayed it until the process restarted, even after the IdP recovered. This funnel backs every
|
|
485
|
+
* /api/auth/* route, the login GET handler and withAuth. server/auth.ts getAuthInstance() already used
|
|
486
|
+
* createSingleFlight for this exact bug; this sibling funnel was missed.
|
|
487
|
+
*
|
|
488
|
+
* Now the attempt runs through the same helper: concurrent callers share one attempt, a failure still rejects
|
|
489
|
+
* for every caller of THAT attempt (loud), the next call starts a fresh attempt, and a success is cached in
|
|
490
|
+
* state.cachedInstance as before. The flight lives on the globalThis state for the same reason the instance
|
|
491
|
+
* does (duplicated route chunks must share one init).
|
|
492
|
+
*/
|
|
479
493
|
export declare function getBetterAuthInstance(): Promise<any>;
|
|
480
494
|
/**
|
|
481
495
|
* Get flag-gated auth handler for Next.js route.
|
package/dist/auth/better-auth.js
CHANGED
|
@@ -68,7 +68,9 @@ const generic_oauth_1 = require("better-auth/plugins/generic-oauth");
|
|
|
68
68
|
const idp_client_config_1 = require("../lib/idp-client-config");
|
|
69
69
|
const app_slug_1 = require("../lib/app-slug");
|
|
70
70
|
const redis_1 = require("../lib/redis");
|
|
71
|
+
const single_flight_1 = require("../lib/single-flight");
|
|
71
72
|
const session_store_1 = require("../lib/session-store");
|
|
73
|
+
const token_log_id_1 = require("../lib/token-log-id");
|
|
72
74
|
/**
|
|
73
75
|
* 185121: short sha256 fingerprint for secrets/tokens in log lines — enough
|
|
74
76
|
* to tell "same" from "divergent", never enough to reconstruct. 12 hex chars
|
|
@@ -463,6 +465,7 @@ function buildBetterAuthProviders(config) {
|
|
|
463
465
|
* Call after getIDPClientConfig() resolves.
|
|
464
466
|
*/
|
|
465
467
|
function createBetterAuthInstance(idpConfig, opts = {}) {
|
|
468
|
+
(0, app_slug_1.assertAppSlugMatchesIdp)(idpConfig.clientSlug);
|
|
466
469
|
const appSlug = idpConfig.clientSlug || (0, app_slug_1.getAppSlug)();
|
|
467
470
|
// 185121 line 1: name the secret this instance PINNED, as a hash, never the
|
|
468
471
|
// secret. 183047's surviving fork is HMAC-fail vs record-miss; an HMAC-fail
|
|
@@ -799,18 +802,33 @@ function configureBetterAuth(opts) {
|
|
|
799
802
|
}
|
|
800
803
|
state.configuredOpts = opts;
|
|
801
804
|
}
|
|
805
|
+
/**
|
|
806
|
+
* PAY-642 (card 338215): the init attempt no longer LATCHES A FAILURE.
|
|
807
|
+
*
|
|
808
|
+
* This used to be `state.initPromise ??= getIDPClientConfig(true).then(...)` with no .catch and no reset. One
|
|
809
|
+
* auth request against a cold process while the IdP was unreachable stored a rejected promise, and every later
|
|
810
|
+
* caller replayed it until the process restarted, even after the IdP recovered. This funnel backs every
|
|
811
|
+
* /api/auth/* route, the login GET handler and withAuth. server/auth.ts getAuthInstance() already used
|
|
812
|
+
* createSingleFlight for this exact bug; this sibling funnel was missed.
|
|
813
|
+
*
|
|
814
|
+
* Now the attempt runs through the same helper: concurrent callers share one attempt, a failure still rejects
|
|
815
|
+
* for every caller of THAT attempt (loud), the next call starts a fresh attempt, and a success is cached in
|
|
816
|
+
* state.cachedInstance as before. The flight lives on the globalThis state for the same reason the instance
|
|
817
|
+
* does (duplicated route chunks must share one init).
|
|
818
|
+
*/
|
|
802
819
|
async function getBetterAuthInstance() {
|
|
803
820
|
if (state.cachedInstance)
|
|
804
821
|
return state.cachedInstance;
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
822
|
+
state.initFlight ??= (0, single_flight_1.createSingleFlight)(() => (0, idp_client_config_1.getIDPClientConfig)(true).then(config => {
|
|
823
|
+
const instance = createBetterAuthInstance(config, state.configuredOpts);
|
|
824
|
+
state.cachedInstance = instance;
|
|
825
|
+
console.log('[BETTER_AUTH] Instance created for', config.clientSlug || config.clientId);
|
|
826
|
+
return instance;
|
|
827
|
+
}));
|
|
828
|
+
const attempt = state.initFlight.get();
|
|
829
|
+
// Kept for callers that inspect it; it always points at the CURRENT attempt, never a settled failure.
|
|
830
|
+
state.initPromise = attempt;
|
|
831
|
+
return attempt;
|
|
814
832
|
}
|
|
815
833
|
/**
|
|
816
834
|
* Get flag-gated auth handler for Next.js route.
|
|
@@ -851,7 +869,7 @@ async function exchangeOAuthForIdpTokens(sessionToken, provider = 'google') {
|
|
|
851
869
|
// Read the BA session from Redis
|
|
852
870
|
const baRaw = await (0, redis_1.getRedis)().get(baKey).catch(() => null);
|
|
853
871
|
if (!baRaw) {
|
|
854
|
-
console.warn('[BETTER_AUTH] exchangeOAuthForIdpTokens: session not found in Redis for token',
|
|
872
|
+
console.warn('[BETTER_AUTH] exchangeOAuthForIdpTokens: session not found in Redis for token', (0, token_log_id_1.tokenLogId)(sessionToken));
|
|
855
873
|
return false;
|
|
856
874
|
}
|
|
857
875
|
const baData = JSON.parse(baRaw);
|
|
@@ -922,7 +940,9 @@ async function exchangeOAuthForIdpTokens(sessionToken, provider = 'google') {
|
|
|
922
940
|
}),
|
|
923
941
|
});
|
|
924
942
|
const oauthResText = await oauthRes.text();
|
|
925
|
-
|
|
943
|
+
// PAY-1427 (QAPert, PR 154 thread 326): this printed the first 500 chars of the oauth-callback TOKEN
|
|
944
|
+
// envelope on EVERY successful OAuth login (it runs before the !ok check). Status + length only.
|
|
945
|
+
console.log('[BETTER_AUTH] IDP oauth-callback response:', { status: oauthRes.status, bodyLength: oauthResText.length });
|
|
926
946
|
if (!oauthRes.ok) {
|
|
927
947
|
console.error('[BETTER_AUTH] IDP oauth-callback failed:', oauthRes.status);
|
|
928
948
|
/**
|
|
@@ -1154,7 +1174,7 @@ async function storeIdpTokensInSession(sessionToken, idpTokenData, authProvider)
|
|
|
1154
1174
|
// conjured here would carry no Better Auth cookie, so it would be
|
|
1155
1175
|
// unreachable by the browser and invisible to every other consumer —
|
|
1156
1176
|
// failing as "silently signed out" rather than as an error.
|
|
1157
|
-
console.warn('[BETTER_AUTH] storeIdpTokensInSession: no BA session for token',
|
|
1177
|
+
console.warn('[BETTER_AUTH] storeIdpTokensInSession: no BA session for token', (0, token_log_id_1.tokenLogId)(sessionToken));
|
|
1158
1178
|
return false;
|
|
1159
1179
|
}
|
|
1160
1180
|
const baData = JSON.parse(baRaw);
|
|
@@ -1256,26 +1276,7 @@ function createAuthGetHandler(loginPath = '/account-auth/login') {
|
|
|
1256
1276
|
return new Response(null, { status: 302, headers: { location: loginPath } });
|
|
1257
1277
|
}
|
|
1258
1278
|
}
|
|
1259
|
-
// After successful OAuth callback: exchange
|
|
1260
|
-
//
|
|
1261
|
-
// ⚠ WAS "exchange Google identity" — and it meant it literally. This call
|
|
1262
|
-
// site passed no provider, so it took exchangeOAuthForIdpTokens's
|
|
1263
|
-
// `provider: string = 'google'` default and asserted "google" to
|
|
1264
|
-
// /api/ExternalAuth/oauth-callback for EVERY provider. That was harmless
|
|
1265
|
-
// while Google was the only one wired, and became wrong the moment a second
|
|
1266
|
-
// provider shipped: a GitHub sign-in forwarded a GitHub access_token
|
|
1267
|
-
// labelled `provider: 'google'`, so the IDP tried to verify it as a Google
|
|
1268
|
-
// identity and fail-closed with 401 UNVERIFIED_ASSERTION in
|
|
1269
|
-
// `response_time_ms: 0` — it never had to call anyone to know it was wrong.
|
|
1270
|
-
//
|
|
1271
|
-
// The diagnostics made this maximally confusing: `access_token_forwarded:
|
|
1272
|
-
// true` is correct and the token really did arrive. The FIELD was fine; the
|
|
1273
|
-
// LABEL on it was a lie. Nothing on the client half is broken.
|
|
1274
|
-
//
|
|
1275
|
-
// The provider was never unavailable — it is the path segment this very
|
|
1276
|
-
// `if` matches on (/api/auth/callback/github). Derive it instead of
|
|
1277
|
-
// defaulting, because a default here cannot be right for more than one
|
|
1278
|
-
// provider at a time.
|
|
1279
|
+
// After successful OAuth callback: exchange Google identity for IDP tokens
|
|
1279
1280
|
const url = new URL(request.url);
|
|
1280
1281
|
if (url.pathname.includes('/callback/') && response.status === 302) {
|
|
1281
1282
|
try {
|
|
@@ -1289,23 +1290,8 @@ function createAuthGetHandler(loginPath = '/account-auth/login') {
|
|
|
1289
1290
|
headers.set('cookie', cookieHeader);
|
|
1290
1291
|
const session = await auth.api.getSession({ headers });
|
|
1291
1292
|
if (session?.session?.token) {
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
.split('/callback/')[1]
|
|
1295
|
-
?.split('/')[0]
|
|
1296
|
-
?.split('?')[0]
|
|
1297
|
-
?.trim()
|
|
1298
|
-
.toLowerCase() || '';
|
|
1299
|
-
if (!callbackProvider) {
|
|
1300
|
-
// Do NOT quietly fall back to 'google'. That silent default is the
|
|
1301
|
-
// exact defect this block fixes, and re-introducing it here would
|
|
1302
|
-
// reproduce it for whatever unparseable shape got us here.
|
|
1303
|
-
console.error('[BETTER_AUTH] OAuth callback matched /callback/ but no provider could be derived from', url.pathname, '— refusing to assert a guessed provider to the IDP. No exchange performed.');
|
|
1304
|
-
}
|
|
1305
|
-
else {
|
|
1306
|
-
console.log('[BETTER_AUTH] Got session token from callback:', session.session.token.substring(0, 10), '| provider:', callbackProvider, '| email:', session.user?.email);
|
|
1307
|
-
await exchangeOAuthForIdpTokens(session.session.token, callbackProvider);
|
|
1308
|
-
}
|
|
1293
|
+
console.log('[BETTER_AUTH] Got session token from callback:', (0, token_log_id_1.tokenLogId)(session.session.token), '| email:', session.user?.email);
|
|
1294
|
+
await exchangeOAuthForIdpTokens(session.session.token);
|
|
1309
1295
|
}
|
|
1310
1296
|
else {
|
|
1311
1297
|
console.warn('[BETTER_AUTH] Could not get session after OAuth callback');
|
|
@@ -183,7 +183,8 @@ async function idpRefreshToken(refreshToken, mfaContext) {
|
|
|
183
183
|
});
|
|
184
184
|
if (!response.ok) {
|
|
185
185
|
const errorText = await response.text().catch(() => 'Unknown error');
|
|
186
|
-
|
|
186
|
+
// PAY-1427: never the raw upstream body (the refresh.ts:528 twin PR 139 fixed). Status + length only.
|
|
187
|
+
console.error('[IDP_CLIENT] Token refresh failed:', { status: response.status, bodyLength: errorText.length });
|
|
187
188
|
return {
|
|
188
189
|
success: false,
|
|
189
190
|
error: {
|
|
@@ -174,7 +174,10 @@ function DataBrowserTab({ isDark = true, apiBasePath = '/api/admin/vibe', focusC
|
|
|
174
174
|
});
|
|
175
175
|
if (!response.ok) {
|
|
176
176
|
const errData = await response.json().catch(() => ({}));
|
|
177
|
-
|
|
177
|
+
// PAY-1425: the handler returns a fixed message plus a correlationId (never upstream
|
|
178
|
+
// text). Surface the id so an admin can quote it to an operator.
|
|
179
|
+
const ref = errData.error?.correlationId ? ` (ref ${errData.error.correlationId})` : '';
|
|
180
|
+
throw new Error(`${errData.error?.message || 'Delete failed'}${ref}`);
|
|
178
181
|
}
|
|
179
182
|
setSuccess(`Record ${id} deleted`);
|
|
180
183
|
setTimeout(() => setSuccess(null), 3000);
|
|
@@ -7,6 +7,12 @@ const react_1 = require("react");
|
|
|
7
7
|
// -----------------------------------------------------------------------------
|
|
8
8
|
// COMPONENT
|
|
9
9
|
// -----------------------------------------------------------------------------
|
|
10
|
+
// PAY-1429: admin handlers return a fixed message plus a correlationId (never upstream or
|
|
11
|
+
// exception text). Surface the id so an admin can quote it to an operator.
|
|
12
|
+
function errorWithRef(data, fallback) {
|
|
13
|
+
const msg = typeof data?.error === 'string' ? data.error : fallback;
|
|
14
|
+
return data?.correlationId ? `${msg} (ref ${data.correlationId})` : msg;
|
|
15
|
+
}
|
|
10
16
|
function SessionsTab({ isDark = true, apiBasePath = '/api/admin/sessions' }) {
|
|
11
17
|
const [sessions, setSessions] = (0, react_1.useState)([]);
|
|
12
18
|
const [isLoading, setIsLoading] = (0, react_1.useState)(false);
|
|
@@ -41,7 +47,7 @@ function SessionsTab({ isDark = true, apiBasePath = '/api/admin/sessions' }) {
|
|
|
41
47
|
setLastRefresh(new Date());
|
|
42
48
|
}
|
|
43
49
|
else {
|
|
44
|
-
setError(data
|
|
50
|
+
setError(errorWithRef(data, 'Failed to fetch sessions'));
|
|
45
51
|
}
|
|
46
52
|
}
|
|
47
53
|
catch (err) {
|
|
@@ -91,7 +97,7 @@ function SessionsTab({ isDark = true, apiBasePath = '/api/admin/sessions' }) {
|
|
|
91
97
|
await fetchStats();
|
|
92
98
|
}
|
|
93
99
|
else {
|
|
94
|
-
alert(data
|
|
100
|
+
alert(errorWithRef(data, 'Failed to revoke session'));
|
|
95
101
|
}
|
|
96
102
|
}
|
|
97
103
|
catch (err) {
|
|
@@ -120,7 +126,7 @@ function SessionsTab({ isDark = true, apiBasePath = '/api/admin/sessions' }) {
|
|
|
120
126
|
await fetchStats();
|
|
121
127
|
}
|
|
122
128
|
else {
|
|
123
|
-
alert(data
|
|
129
|
+
alert(errorWithRef(data, 'Failed to revoke sessions'));
|
|
124
130
|
}
|
|
125
131
|
}
|
|
126
132
|
catch (err) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
onGoToLogin: () => void;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* PAY-1588 (rigpert 60711): what a caller sees when the recovery request produced NO recovery session - an unknown
|
|
6
|
+
* address, a locked account, a throttled caller. ONE sentence, identical for all of them, so the screen cannot be used
|
|
7
|
+
* to tell which. It must not claim a reset happened (that is CompleteStep, reached only after a successful reset) and it
|
|
8
|
+
* must not name a cause.
|
|
9
|
+
*/
|
|
10
|
+
export declare function RequestedStep({ onGoToLogin }: Props): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
'use client';
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.RequestedStep = RequestedStep;
|
|
5
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
|
+
/**
|
|
7
|
+
* PAY-1588 (rigpert 60711): what a caller sees when the recovery request produced NO recovery session - an unknown
|
|
8
|
+
* address, a locked account, a throttled caller. ONE sentence, identical for all of them, so the screen cannot be used
|
|
9
|
+
* to tell which. It must not claim a reset happened (that is CompleteStep, reached only after a successful reset) and it
|
|
10
|
+
* must not name a cause.
|
|
11
|
+
*/
|
|
12
|
+
function RequestedStep({ onGoToLogin }) {
|
|
13
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "bg-white border border-gray-300 rounded p-8 text-center", children: [(0, jsx_runtime_1.jsx)("h2", { className: "text-2xl font-semibold text-gray-900 mb-2", children: "Check your email" }), (0, jsx_runtime_1.jsx)("p", { className: "text-gray-600 mb-6", children: "If an account exists for that email, we've sent instructions to reset your password." }), (0, jsx_runtime_1.jsx)("button", { onClick: onGoToLogin, className: "w-full py-2 px-4 border border-gray-300 rounded text-gray-900 font-medium hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-400", children: "Back to sign in" })] }));
|
|
14
|
+
}
|
|
@@ -4,17 +4,26 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
exports.SetPasswordStep = SetPasswordStep;
|
|
5
5
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
6
|
const react_1 = require("react");
|
|
7
|
+
const usePasswordValidation_1 = require("../../hooks/usePasswordValidation");
|
|
8
|
+
const password_rules_1 = require("../../lib/password-rules");
|
|
7
9
|
function SetPasswordStep({ onSubmit, loading }) {
|
|
8
10
|
const [password, setPassword] = (0, react_1.useState)('');
|
|
9
11
|
const [confirmPassword, setConfirmPassword] = (0, react_1.useState)('');
|
|
10
12
|
const [showPassword, setShowPassword] = (0, react_1.useState)(false);
|
|
11
13
|
const [showConfirmPassword, setShowConfirmPassword] = (0, react_1.useState)(false);
|
|
12
14
|
const passwordsMatch = password === confirmPassword && password !== '';
|
|
15
|
+
// PAY-1591: ADVISORY only. It shows the rules and what is missing; it never disables submit, because the validator
|
|
16
|
+
// may be down and the IdP is the authority (a dead validator must not trap a user who already spent their code).
|
|
17
|
+
const { setPassword: validateAsYouType, policy, failedRequirements } = (0, usePasswordValidation_1.usePasswordValidation)();
|
|
18
|
+
// The policy only comes back with a validation, so ask once on mount with a one-character probe (it never leaves the
|
|
19
|
+
// policy question; the answer is what the rules line is built from). Until it lands the line says the rules are coming.
|
|
20
|
+
(0, react_1.useEffect)(() => { validateAsYouType('a'); }, [validateAsYouType]);
|
|
21
|
+
const missing = failedRequirements.filter((r) => !/^(Unable to validate|Password validation)/i.test(r));
|
|
13
22
|
const handleSubmit = (e) => {
|
|
14
23
|
e.preventDefault();
|
|
15
24
|
if (passwordsMatch) {
|
|
16
25
|
onSubmit(password, confirmPassword);
|
|
17
26
|
}
|
|
18
27
|
};
|
|
19
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: "bg-white border border-gray-300 rounded p-8", children: [(0, jsx_runtime_1.jsx)("h2", { className: "text-xl font-semibold text-gray-900 mb-2", children: "Set New Password" }), (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-gray-600 mb-
|
|
28
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "bg-white border border-gray-300 rounded p-8", children: [(0, jsx_runtime_1.jsx)("h2", { className: "text-xl font-semibold text-gray-900 mb-2", children: "Set New Password" }), (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-gray-600 mb-2", children: "Choose a strong password for your account." }), (0, jsx_runtime_1.jsx)("p", { "data-testid": "password-rules", className: "text-sm text-gray-700 mb-6", children: (0, password_rules_1.passwordRulesText)(policy) }), (0, jsx_runtime_1.jsxs)("form", { onSubmit: handleSubmit, className: "space-y-4", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { htmlFor: "password", className: "block text-sm font-medium text-gray-900 mb-1", children: "New Password" }), (0, jsx_runtime_1.jsxs)("div", { className: "relative", children: [(0, jsx_runtime_1.jsx)("input", { type: showPassword ? 'text' : 'password', id: "password", value: password, onChange: (e) => { setPassword(e.target.value); validateAsYouType(e.target.value); }, required: true, disabled: loading, className: "w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-gray-900 focus:outline-none disabled:opacity-50 disabled:bg-gray-50 pr-10" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setShowPassword(!showPassword), className: "absolute right-2 top-1/2 -translate-y-1/2 text-gray-600 hover:text-gray-900", children: showPassword ? '👁️' : '👁️🗨️' })] })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { htmlFor: "confirmPassword", className: "block text-sm font-medium text-gray-900 mb-1", children: "Confirm New Password" }), (0, jsx_runtime_1.jsxs)("div", { className: "relative", children: [(0, jsx_runtime_1.jsx)("input", { type: showConfirmPassword ? 'text' : 'password', id: "confirmPassword", value: confirmPassword, onChange: (e) => setConfirmPassword(e.target.value), required: true, disabled: loading, className: "w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-gray-900 focus:outline-none disabled:opacity-50 disabled:bg-gray-50 pr-10" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setShowConfirmPassword(!showConfirmPassword), className: "absolute right-2 top-1/2 -translate-y-1/2 text-gray-600 hover:text-gray-900", children: showConfirmPassword ? '👁️' : '👁️🗨️' })] })] }), password && missing.length > 0 && ((0, jsx_runtime_1.jsx)("ul", { "data-testid": "password-missing", className: "text-sm text-gray-700 list-disc pl-5", children: missing.map((r) => ((0, jsx_runtime_1.jsx)("li", { children: r }, r))) })), !passwordsMatch && confirmPassword && ((0, jsx_runtime_1.jsx)("div", { className: "bg-red-50 border border-red-200 rounded p-3", children: (0, jsx_runtime_1.jsx)("p", { className: "text-red-700 text-sm", children: "Passwords do not match" }) })), (0, jsx_runtime_1.jsx)("button", { type: "submit", disabled: !passwordsMatch || loading, className: "w-full py-2 px-4 border border-gray-300 rounded text-gray-900 font-medium hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-900 disabled:opacity-50 disabled:cursor-not-allowed", children: loading ? 'Resetting Password...' : 'Reset Password' })] })] }));
|
|
20
29
|
}
|
package/dist/lib/app-slug.d.ts
CHANGED
|
@@ -14,6 +14,27 @@
|
|
|
14
14
|
* Caches the result for performance.
|
|
15
15
|
*/
|
|
16
16
|
export declare function getAppSlug(): string;
|
|
17
|
+
/**
|
|
18
|
+
* The session cookie name has TWO producers: better-auth mints `${idpConfig.clientSlug || getAppSlug()}.session-token`, while
|
|
19
|
+
* the gate and every reader use getSessionCookieName() = getAppSlug() = APP_SLUG || CLIENT_ID. When the IdP's slug is known and
|
|
20
|
+
* differs, the gate looks for a cookie that was never set and every better-auth (Google) session bounces to
|
|
21
|
+
* /sign-in?error=SessionInvalid straight after a successful 2FA (rigpert 60743, live on :4797 with CLIENT_ID=idp-admin-ui-graft
|
|
22
|
+
* against clientSlug idp_admin_ui_graft). FAIL CLOSED: refuse rather than run with two names. No IdP slug -> nothing to compare.
|
|
23
|
+
*/
|
|
24
|
+
export declare function slugMismatchReason(idpClientSlug: string | null | undefined): string | null;
|
|
25
|
+
/**
|
|
26
|
+
* rigpert 60813: when the IdP returns NO client slug for this app (an unknown client: the config fetch failed or came back
|
|
27
|
+
* without one), startup must fail LOUD. Silently defaulting would run with a cookie name nobody verified against the IdP. The
|
|
28
|
+
* startup path swallows a failed config fetch when BETTER_AUTH_SECRET is supplied by env, so without this an unregistered
|
|
29
|
+
* CLIENT_ID (e.g. payez_idp_customer_web, absent on dev-93) would boot 'fine'.
|
|
30
|
+
*/
|
|
31
|
+
export declare function assertIdpSlugKnown(idpClientSlug: string | null | undefined): void;
|
|
32
|
+
/**
|
|
33
|
+
* The health-endpoint view: a mismatch is always reportable; an UNKNOWN slug is reported only once startup has actually been
|
|
34
|
+
* attempted (before that nothing is known yet and health must not go red on a pod that has not started initialising).
|
|
35
|
+
*/
|
|
36
|
+
export declare function slugStateReason(idpClientSlug: string | null | undefined, startupAttempted: boolean): string | null;
|
|
37
|
+
export declare function assertAppSlugMatchesIdp(idpClientSlug: string | null | undefined): void;
|
|
17
38
|
/**
|
|
18
39
|
* Clears the cached slug (useful for testing).
|
|
19
40
|
*/
|