@payez/next-mvp 4.12.1 → 4.13.1
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/CHANGELOG.md +57 -0
- package/dist/api-handlers/account/profile.d.ts +2 -39
- package/dist/api-handlers/account/profile.js +5 -2
- package/dist/api-handlers/anon/preferences.d.ts +1 -14
- package/dist/api-handlers/anon/preferences.js +5 -5
- package/dist/api-handlers/auth/credential-login.js +28 -4
- package/dist/api-handlers/health.d.ts +5 -0
- package/dist/api-handlers/health.js +48 -0
- package/dist/api-handlers/mfa/totp-confirm.js +8 -4
- package/dist/api-handlers/mfa/totp-enroll.js +8 -4
- package/dist/api-handlers/session/refresh-viability.d.ts +3 -15
- package/dist/api-handlers/session/refresh-viability.js +5 -2
- package/dist/api-handlers/session/viability.d.ts +3 -8
- package/dist/api-handlers/session/viability.js +11 -4
- package/dist/auth/better-auth-exposure.d.ts +11 -0
- package/dist/auth/better-auth-exposure.js +73 -0
- package/dist/auth/better-auth.d.ts +6 -0
- package/dist/auth/better-auth.js +42 -3
- package/dist/auth/route-config.js +9 -0
- package/dist/auth/session-reset-link.d.ts +32 -0
- package/dist/auth/session-reset-link.js +45 -1
- package/dist/components/auth/CredentialSignInForm.d.ts +7 -1
- package/dist/components/auth/CredentialSignInForm.js +13 -4
- package/dist/components/auth/EnrollAuthenticatorOffer.d.ts +21 -0
- package/dist/components/auth/EnrollAuthenticatorOffer.js +23 -0
- package/dist/components/mfa/QrCode.d.ts +6 -0
- package/dist/components/mfa/QrCode.js +14 -0
- package/dist/components/mfa/TotpEnrollPanel.d.ts +6 -0
- package/dist/components/mfa/TotpEnrollPanel.js +117 -0
- package/dist/edge/middleware.d.ts +1 -1
- package/dist/edge/middleware.js +2 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -1
- package/dist/lib/credential-sign-in-client.d.ts +26 -1
- package/dist/lib/credential-sign-in-client.js +32 -6
- package/dist/lib/idp-client-config.js +4 -1
- package/dist/lib/login-error-message.d.ts +3 -0
- package/dist/lib/login-error-message.js +12 -1
- package/dist/lib/mfa-json-guard.d.ts +12 -0
- package/dist/lib/mfa-json-guard.js +19 -0
- package/dist/lib/nextauth-secret.js +3 -1
- package/dist/lib/no-store.d.ts +13 -0
- package/dist/lib/no-store.js +35 -0
- package/dist/lib/payez-client-secret.d.ts +14 -0
- package/dist/lib/payez-client-secret.js +41 -0
- package/dist/lib/qrcode.d.ts +22 -0
- package/dist/lib/qrcode.js +362 -0
- package/dist/lib/sign-in-failure.d.ts +35 -0
- package/dist/lib/sign-in-failure.js +85 -0
- package/dist/lib/twofa-rejection.d.ts +2 -0
- package/dist/lib/twofa-rejection.js +5 -2
- package/dist/middleware/create-middleware.d.ts +24 -0
- package/dist/middleware/create-middleware.js +56 -9
- package/dist/middleware/prefetch-guard-status.d.ts +21 -0
- package/dist/middleware/prefetch-guard-status.js +42 -0
- package/dist/pages/enroll/page.d.ts +7 -0
- package/dist/pages/enroll/page.js +15 -0
- package/dist/pages/login/page.js +1 -1
- package/dist/routes/auth/session-reset.d.ts +11 -1
- package/dist/routes/auth/session-reset.js +38 -13
- package/dist/routes/auth/session.d.ts +2 -9
- package/dist/routes/auth/session.js +5 -2
- package/dist/routes/auth/viability.d.ts +3 -54
- package/dist/routes/auth/viability.js +16 -4
- package/dist/routes/health.d.ts +1 -0
- package/dist/routes/health.js +16 -0
- package/dist/routes/mfa/methods.d.ts +2 -2
- package/dist/routes/mfa/methods.js +3 -1
- package/dist/routes/passkeys/list.d.ts +2 -10
- package/dist/routes/passkeys/list.js +4 -2
- package/dist/routes/session/refresh-viability.d.ts +2 -1
- package/dist/routes/session/refresh-viability.js +4 -2
- package/dist/utils/api.d.ts +33 -6
- package/dist/utils/api.js +35 -2
- package/package.json +21 -1
- package/src/api-handlers/account/profile.ts +5 -1
- package/src/api-handlers/anon/preferences.ts +5 -4
- package/src/api-handlers/auth/credential-login.ts +29 -4
- package/src/api-handlers/health.ts +53 -0
- package/src/api-handlers/mfa/totp-confirm.ts +7 -4
- package/src/api-handlers/mfa/totp-enroll.ts +7 -4
- package/src/api-handlers/session/refresh-viability.ts +5 -1
- package/src/api-handlers/session/viability.ts +12 -4
- package/src/auth/better-auth-exposure.ts +69 -0
- package/src/auth/better-auth.ts +42 -3
- package/src/auth/route-config.ts +9 -0
- package/src/auth/session-reset-link.ts +47 -0
- package/src/components/auth/CredentialSignInForm.tsx +21 -3
- package/src/components/auth/EnrollAuthenticatorOffer.tsx +48 -0
- package/src/components/mfa/QrCode.tsx +24 -0
- package/src/components/mfa/TotpEnrollPanel.tsx +203 -0
- package/src/edge/middleware.ts +1 -1
- package/src/index.ts +1 -1
- package/src/lib/credential-sign-in-client.ts +48 -8
- package/src/lib/idp-client-config.ts +5 -1
- package/src/lib/login-error-message.ts +13 -1
- package/src/lib/mfa-json-guard.ts +19 -0
- package/src/lib/nextauth-secret.ts +3 -1
- package/src/lib/no-store.ts +30 -0
- package/src/lib/payez-client-secret.ts +39 -0
- package/src/lib/qrcode.ts +348 -0
- package/src/lib/sign-in-failure.ts +88 -0
- package/src/lib/twofa-rejection.ts +7 -2
- package/src/middleware/create-middleware.ts +69 -9
- package/src/middleware/prefetch-guard-status.ts +56 -0
- package/src/pages/enroll/page.tsx +22 -0
- package/src/pages/login/page.tsx +2 -1
- package/src/routes/auth/session-reset.ts +46 -11
- package/src/routes/auth/session.ts +5 -1
- package/src/routes/auth/viability.ts +16 -4
- package/src/routes/health.ts +14 -0
- package/src/routes/mfa/methods.ts +3 -1
- package/src/routes/passkeys/list.ts +5 -1
- package/src/routes/session/refresh-viability.ts +5 -1
- package/src/utils/api.ts +50 -8
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PAY-2218 P1 (BAPert 86123, reworded 86209): say, once, at middleware creation, that the HEADER-based router-prefetch guard cannot see router prefetches
|
|
3
|
+
* on this Next version. After P2 the session-cookie clear no longer depends on it (it keys on Sec-Fetch-Mode/Dest), so this is information, not a call to
|
|
4
|
+
* migrate. No behaviour change: this only logs.
|
|
5
|
+
*
|
|
6
|
+
* MEASURED (adapter.js read in each version's own node_modules): Next 15.5.9 and 16.1.5 delete the flight headers (rsc, next-router-prefetch, ...)
|
|
7
|
+
* from the request middleware sees UNCONDITIONALLY, and 15.5.9 also strips `_rsc` from the URL, so isPrefetchRequest() is false for every router
|
|
8
|
+
* prefetch and no config can change that. Next 16.2.x skips the strip when `skipProxyUrlNormalize: true`.
|
|
9
|
+
*
|
|
10
|
+
* NOT warned, on purpose: Next >= 16.2 (the flag is a build-time define that user middleware code cannot reliably read back, so a warning there
|
|
11
|
+
* would be a guess) and Next 14 (not measured). Unparseable or missing version: silent.
|
|
12
|
+
*/
|
|
13
|
+
export type PrefetchGuardStatus = {
|
|
14
|
+
dead: boolean;
|
|
15
|
+
reason: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function prefetchGuardStatus(nextVersion: string | undefined | null): PrefetchGuardStatus;
|
|
18
|
+
/** Logs PAYEZ_PREFETCH_GUARD_DEAD once per process when the guard is dead on this Next version. Returns true when it logged. */
|
|
19
|
+
export declare function warnPrefetchGuardDeadOnce(warn: (message: string, data?: unknown) => void, nextVersion?: string | undefined | null): boolean;
|
|
20
|
+
/** Test seam: forget that the warning was logged. */
|
|
21
|
+
export declare function resetPrefetchGuardWarningForTests(): void;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.prefetchGuardStatus = prefetchGuardStatus;
|
|
4
|
+
exports.warnPrefetchGuardDeadOnce = warnPrefetchGuardDeadOnce;
|
|
5
|
+
exports.resetPrefetchGuardWarningForTests = resetPrefetchGuardWarningForTests;
|
|
6
|
+
function prefetchGuardStatus(nextVersion) {
|
|
7
|
+
const m = /^(\d+)\.(\d+)\./.exec(String(nextVersion ?? '').trim());
|
|
8
|
+
if (!m)
|
|
9
|
+
return { dead: false, reason: 'unknown version' };
|
|
10
|
+
const major = Number(m[1]);
|
|
11
|
+
const minor = Number(m[2]);
|
|
12
|
+
if (major === 15 || (major === 16 && minor < 2)) {
|
|
13
|
+
return { dead: true, reason: 'strips the router-prefetch headers (rsc, next-router-prefetch) before middleware runs and has no opt-out' };
|
|
14
|
+
}
|
|
15
|
+
return { dead: false, reason: 'not measured as dead' };
|
|
16
|
+
}
|
|
17
|
+
function detectNextVersion() {
|
|
18
|
+
try {
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
20
|
+
return require('next/package.json').version;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
let warned = false;
|
|
27
|
+
/** Logs PAYEZ_PREFETCH_GUARD_DEAD once per process when the guard is dead on this Next version. Returns true when it logged. */
|
|
28
|
+
function warnPrefetchGuardDeadOnce(warn, nextVersion = detectNextVersion()) {
|
|
29
|
+
if (warned)
|
|
30
|
+
return false;
|
|
31
|
+
const s = prefetchGuardStatus(nextVersion);
|
|
32
|
+
if (!s.dead)
|
|
33
|
+
return false;
|
|
34
|
+
warned = true;
|
|
35
|
+
warn(`PAYEZ_PREFETCH_GUARD_DEAD next=${nextVersion}: this Next version ${s.reason}, so isPrefetchRequest() cannot see a router prefetch. ` +
|
|
36
|
+
"The session-cookie clear relies on the browser's Sec-Fetch-Mode/Dest instead (PAY-2218 P2); non-browser clients and browsers that send no Sec-Fetch headers are not covered.");
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
/** Test seam: forget that the warning was logged. */
|
|
40
|
+
function resetPrefetchGuardWarningForTests() {
|
|
41
|
+
warned = false;
|
|
42
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PAY-2631: /account-auth/enroll in a generated app. The page SHELL is public (every /account-auth/* path is public by default);
|
|
3
|
+
* what needs a session is each action: enrol and confirm answer 401 without one and the panel then says to sign in (measured on a
|
|
4
|
+
* scaffolded app: POST enrol with no cookie -> 401, no secret in the body). The scaffold lists the path on the 2FA bypass list
|
|
5
|
+
* because public routes still redirect a 2FA-incomplete session to verify-code, which sends enroll_mfa users back here: a loop.
|
|
6
|
+
*/
|
|
7
|
+
export default function EnrollPage(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
'use client';
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.default = EnrollPage;
|
|
5
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
|
+
const TotpEnrollPanel_1 = require("../../components/mfa/TotpEnrollPanel");
|
|
7
|
+
/**
|
|
8
|
+
* PAY-2631: /account-auth/enroll in a generated app. The page SHELL is public (every /account-auth/* path is public by default);
|
|
9
|
+
* what needs a session is each action: enrol and confirm answer 401 without one and the panel then says to sign in (measured on a
|
|
10
|
+
* scaffolded app: POST enrol with no cookie -> 401, no secret in the body). The scaffold lists the path on the 2FA bypass list
|
|
11
|
+
* because public routes still redirect a 2FA-incomplete session to verify-code, which sends enroll_mfa users back here: a loop.
|
|
12
|
+
*/
|
|
13
|
+
function EnrollPage() {
|
|
14
|
+
return ((0, jsx_runtime_1.jsxs)("main", { style: { maxWidth: 480, margin: '48px auto', padding: '0 16px' }, children: [(0, jsx_runtime_1.jsx)("h1", { children: "Set up an authenticator app" }), (0, jsx_runtime_1.jsx)(TotpEnrollPanel_1.TotpEnrollPanel, {}), (0, jsx_runtime_1.jsx)("p", { style: { marginTop: 24 }, children: (0, jsx_runtime_1.jsx)("a", { href: "/", children: "Continue to the app" }) })] }));
|
|
15
|
+
}
|
package/dist/pages/login/page.js
CHANGED
|
@@ -180,7 +180,7 @@ function LoginForm({ altAuthSlot }) {
|
|
|
180
180
|
.themed-status-submitting svg {
|
|
181
181
|
color: ${buttonColor} !important;
|
|
182
182
|
}
|
|
183
|
-
` } }), (0, jsx_runtime_1.jsx)("div", { className: `min-h-screen flex items-center justify-center px-4 ${colors.background}`, children: (0, jsx_runtime_1.jsx)("div", { className: "max-w-md w-full", children: (0, jsx_runtime_1.jsxs)("div", { className: `rounded-2xl shadow-xl p-8 ${colors.card} ${colors.border} text-gray-800`, children: [(0, jsx_runtime_1.jsxs)("div", { className: "text-center mb-8", children: [(0, jsx_runtime_1.jsx)("h1", { className: "text-3xl font-bold mb-2", style: { color: 'inherit' }, children: branding.tagline || 'Welcome Back' }), (0, jsx_runtime_1.jsx)("p", { className: colors.muted, children: "Sign in to your account" })] }), shownError && ((0, jsx_runtime_1.jsx)("div", { role: "alert", "data-testid": "login-error", className: "mb-6 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700", children: shownError })), googleEnabled && ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: () => void handleGoogleSignIn(), disabled: googleLoading || isSubmitting, "data-testid": "google-sign-in", className: "w-full px-4 py-3 mb-6 rounded-lg border border-gray-300 font-semibold transition-colors flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-50", style: { backgroundColor: 'white', color: '#1f2937' }, children: [googleLoading ? ((0, jsx_runtime_1.jsxs)("svg", { className: "animate-spin w-5 h-5", fill: "none", viewBox: "0 0 24 24", children: [(0, jsx_runtime_1.jsx)("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), (0, jsx_runtime_1.jsx)("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" })] })) : ((0, jsx_runtime_1.jsxs)("svg", { className: "w-5 h-5", viewBox: "0 0 24 24", fill: "currentColor", children: [(0, jsx_runtime_1.jsx)("path", { d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z", fill: "#4285F4" }), (0, jsx_runtime_1.jsx)("path", { d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z", fill: "#34A853" }), (0, jsx_runtime_1.jsx)("path", { d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z", fill: "#FBBC05" }), (0, jsx_runtime_1.jsx)("path", { d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z", fill: "#EA4335" })] })), googleLoading ? 'Redirecting to Google…' : 'Continue with Google'] }) })), altAuthSlot && (0, jsx_runtime_1.jsx)("div", { className: "mt-4", children: altAuthSlot }), (0, jsx_runtime_1.jsx)("div", { className: `mt-6 text-center text-sm ${colors.muted}`, children: (0, jsx_runtime_1.jsx)("a", { href: "/account-auth/recovery", className: "themed-link hover:underline", children: "Start account recovery" }) })] }) }) })] }));
|
|
183
|
+
` } }), (0, jsx_runtime_1.jsx)("div", { className: `min-h-screen flex items-center justify-center px-4 ${colors.background}`, children: (0, jsx_runtime_1.jsx)("div", { className: "max-w-md w-full", children: (0, jsx_runtime_1.jsxs)("div", { className: `rounded-2xl shadow-xl p-8 ${colors.card} ${colors.border} text-gray-800`, children: [(0, jsx_runtime_1.jsxs)("div", { className: "text-center mb-8", children: [(0, jsx_runtime_1.jsx)("h1", { className: "text-3xl font-bold mb-2", style: { color: 'inherit' }, children: branding.tagline || 'Welcome Back' }), (0, jsx_runtime_1.jsx)("p", { className: colors.muted, children: "Sign in to your account" })] }), shownError && ((0, jsx_runtime_1.jsx)("div", { role: "alert", "data-testid": "login-error", "data-error-code": loginError === undefined ? ((0, login_error_message_1.loginErrorCode)(urlError) ?? undefined) : undefined, className: "mb-6 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700", children: shownError })), googleEnabled && ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: () => void handleGoogleSignIn(), disabled: googleLoading || isSubmitting, "data-testid": "google-sign-in", className: "w-full px-4 py-3 mb-6 rounded-lg border border-gray-300 font-semibold transition-colors flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-50", style: { backgroundColor: 'white', color: '#1f2937' }, children: [googleLoading ? ((0, jsx_runtime_1.jsxs)("svg", { className: "animate-spin w-5 h-5", fill: "none", viewBox: "0 0 24 24", children: [(0, jsx_runtime_1.jsx)("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), (0, jsx_runtime_1.jsx)("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" })] })) : ((0, jsx_runtime_1.jsxs)("svg", { className: "w-5 h-5", viewBox: "0 0 24 24", fill: "currentColor", children: [(0, jsx_runtime_1.jsx)("path", { d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z", fill: "#4285F4" }), (0, jsx_runtime_1.jsx)("path", { d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z", fill: "#34A853" }), (0, jsx_runtime_1.jsx)("path", { d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z", fill: "#FBBC05" }), (0, jsx_runtime_1.jsx)("path", { d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z", fill: "#EA4335" })] })), googleLoading ? 'Redirecting to Google…' : 'Continue with Google'] }) })), altAuthSlot && (0, jsx_runtime_1.jsx)("div", { className: "mt-4", children: altAuthSlot }), (0, jsx_runtime_1.jsx)("div", { className: `mt-6 text-center text-sm ${colors.muted}`, children: (0, jsx_runtime_1.jsx)("a", { href: "/account-auth/recovery", className: "themed-link hover:underline", children: "Start account recovery" }) })] }) }) })] }));
|
|
184
184
|
}
|
|
185
185
|
function LoginPageFallback() {
|
|
186
186
|
const colors = (0, useTheme_1.useColors)();
|
|
@@ -18,4 +18,14 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { NextRequest, NextResponse } from 'next/server';
|
|
20
20
|
export declare const dynamic = "force-dynamic";
|
|
21
|
-
export
|
|
21
|
+
export interface SessionResetOptions {
|
|
22
|
+
/**
|
|
23
|
+
* PAY-2218 opt-in, OFF by default (BAPert 83102). OFF: only a link signed with the env secret (BETTER_AUTH_SECRET / NEXTAUTH_SECRET) clears, so every app that can
|
|
24
|
+
* see the secret stays strict. ON: a link signed with the key derived from this request's own session cookie also clears. Turn it on only where the EDGE middleware
|
|
25
|
+
* cannot see the env secret, and set `sessionResetCookieKey: true` on createMvpMiddleware too, or the route refuses the link the middleware signed.
|
|
26
|
+
*/
|
|
27
|
+
cookieKey?: boolean;
|
|
28
|
+
}
|
|
29
|
+
export declare function createSessionResetHandler(options?: SessionResetOptions): (request: NextRequest) => Promise<NextResponse>;
|
|
30
|
+
/** The strict default: env-signed links only. An app that opted in uses createSessionResetHandler({ cookieKey: true }). */
|
|
31
|
+
export declare const GET: (request: NextRequest) => Promise<NextResponse>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.dynamic = void 0;
|
|
4
|
-
exports.
|
|
3
|
+
exports.GET = exports.dynamic = void 0;
|
|
4
|
+
exports.createSessionResetHandler = createSessionResetHandler;
|
|
5
5
|
/**
|
|
6
6
|
* PAY-2218 PR 2: the session-reset route. Mount it at app/api/auth/session-reset/route.ts:
|
|
7
7
|
*
|
|
@@ -26,15 +26,40 @@ const session_cookie_clear_1 = require("../../auth/session-cookie-clear");
|
|
|
26
26
|
const app_slug_1 = require("../../lib/app-slug");
|
|
27
27
|
const session_reset_link_1 = require("../../auth/session-reset-link");
|
|
28
28
|
exports.dynamic = 'force-dynamic';
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
(0,
|
|
39
|
-
|
|
29
|
+
function createSessionResetHandler(options = {}) {
|
|
30
|
+
const cookieKey = options.cookieKey === true;
|
|
31
|
+
let splitLogged = false;
|
|
32
|
+
let halfSetLogged = false;
|
|
33
|
+
return async function GET(request) {
|
|
34
|
+
if ((0, create_middleware_1.isPrefetchRequest)(request)) {
|
|
35
|
+
return new server_1.NextResponse(null, { status: 204, headers: { 'cache-control': 'no-store' } });
|
|
36
|
+
}
|
|
37
|
+
const bound = (0, session_reset_link_1.boundSessionCookie)((n) => request.cookies.get(n)?.value, (0, app_slug_1.getSessionCookieName)(), (0, app_slug_1.getSecureSessionCookieName)());
|
|
38
|
+
const env = (0, session_reset_link_1.resetSecret)();
|
|
39
|
+
const { params, signed, via } = cookieKey
|
|
40
|
+
? await (0, session_reset_link_1.readResetRequestAnyKey)(request.nextUrl.searchParams, env, bound)
|
|
41
|
+
: { ...(await (0, session_reset_link_1.readResetRequest)(request.nextUrl.searchParams, env, bound)), via: null };
|
|
42
|
+
// The dev-93 prefetch walk reads this line: a link that verified ONLY with the cookie-derived key while this route sees the env secret is the
|
|
43
|
+
// edge-blind / route-sighted split deployment. Once per process, no values.
|
|
44
|
+
if (cookieKey && signed && via === 'cookie' && env && !splitLogged) {
|
|
45
|
+
splitLogged = true;
|
|
46
|
+
console.info('[next-mvp] session-reset: a link verified with the cookie-derived key while the env secret is visible to this route (the edge middleware signed without it: split deployment)');
|
|
47
|
+
}
|
|
48
|
+
// HALF-SET (NightHawk 83161): this route is strict (cookieKey OFF) but the middleware signed with the cookie-derived key (sessionResetCookieKey ON). The link is refused
|
|
49
|
+
// and the stale cookie survives. Probe ONLY refused links, for the cookie-derived key alone, and say so once per process. No values.
|
|
50
|
+
if (!cookieKey && !signed && !halfSetLogged) {
|
|
51
|
+
const probe = await (0, session_reset_link_1.readResetRequestAnyKey)(request.nextUrl.searchParams, undefined, bound);
|
|
52
|
+
if (probe.signed) {
|
|
53
|
+
halfSetLogged = true;
|
|
54
|
+
console.warn('[next-mvp] session-reset: refused a link signed with the cookie-derived key because this route has cookieKey OFF; the middleware has sessionResetCookieKey ON. Set createSessionResetHandler({ cookieKey: true }) or turn the middleware option off');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const response = server_1.NextResponse.redirect(new URL((0, session_reset_link_1.loginLocation)(params), request.url), 307);
|
|
58
|
+
response.headers.set('cache-control', 'no-store');
|
|
59
|
+
if (signed)
|
|
60
|
+
(0, session_cookie_clear_1.expireSessionCookies)(response, Array.from(request.cookies, ([name]) => name));
|
|
61
|
+
return response;
|
|
62
|
+
};
|
|
40
63
|
}
|
|
64
|
+
/** The strict default: env-signed links only. An app that opted in uses createSessionResetHandler({ cookieKey: true }). */
|
|
65
|
+
exports.GET = createSessionResetHandler();
|
|
@@ -13,15 +13,8 @@
|
|
|
13
13
|
* @since auth-ready-v2
|
|
14
14
|
*/
|
|
15
15
|
import { NextRequest, NextResponse } from 'next/server';
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
*
|
|
19
|
-
* Returns the current session information including:
|
|
20
|
-
* - User details
|
|
21
|
-
* - Token expiry status
|
|
22
|
-
* - Session validity
|
|
23
|
-
*/
|
|
24
|
-
export declare function GET(req: NextRequest): Promise<NextResponse<{}>>;
|
|
16
|
+
/** PAY-2994: session state is per-user and some answers carry tokens; never cached (noStore). */
|
|
17
|
+
export declare const GET: (req: NextRequest) => Promise<Response>;
|
|
25
18
|
/**
|
|
26
19
|
* POST /api/auth/session - Update session METADATA
|
|
27
20
|
*
|
|
@@ -14,8 +14,9 @@
|
|
|
14
14
|
* @since auth-ready-v2
|
|
15
15
|
*/
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.GET =
|
|
17
|
+
exports.GET = void 0;
|
|
18
18
|
exports.POST = POST;
|
|
19
|
+
const no_store_1 = require("../../lib/no-store");
|
|
19
20
|
const server_1 = require("next/server");
|
|
20
21
|
const auth_1 = require("../../server/auth");
|
|
21
22
|
const session_store_1 = require("../../lib/session-store");
|
|
@@ -29,7 +30,7 @@ const token_log_id_1 = require("../../lib/token-log-id");
|
|
|
29
30
|
* - Token expiry status
|
|
30
31
|
* - Session validity
|
|
31
32
|
*/
|
|
32
|
-
async function
|
|
33
|
+
async function getHandler(req) {
|
|
33
34
|
try {
|
|
34
35
|
const authSession = await (0, auth_1.getSession)(req);
|
|
35
36
|
if (!authSession) {
|
|
@@ -116,6 +117,8 @@ async function GET(req) {
|
|
|
116
117
|
}, { status: 500 });
|
|
117
118
|
}
|
|
118
119
|
}
|
|
120
|
+
/** PAY-2994: session state is per-user and some answers carry tokens; never cached (noStore). */
|
|
121
|
+
exports.GET = (0, no_store_1.noStore)(getHandler);
|
|
119
122
|
/**
|
|
120
123
|
* The fields this route USED to accept from the request body and must never accept
|
|
121
124
|
* again. Cards 308833 (critical) / 312092 (remediation spec) / 318272 / 311425.
|
|
@@ -13,57 +13,6 @@
|
|
|
13
13
|
* @version 2.0.0
|
|
14
14
|
* @since auth-ready-v2
|
|
15
15
|
*/
|
|
16
|
-
import { NextRequest
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
* GET /api/session/viability - Check if session is viable
|
|
20
|
-
*
|
|
21
|
-
* Returns:
|
|
22
|
-
* - viable: boolean - Whether the session can be used
|
|
23
|
-
* - needsRefresh: boolean - Whether a refresh is recommended
|
|
24
|
-
* - expiresIn: number - Seconds until token expires
|
|
25
|
-
*/
|
|
26
|
-
export declare function GET(req: NextRequest): Promise<NextResponse<{
|
|
27
|
-
viable: boolean;
|
|
28
|
-
needsRefresh: boolean;
|
|
29
|
-
authenticated: boolean;
|
|
30
|
-
revocationStatus: RevocationStatus;
|
|
31
|
-
unavailable: boolean;
|
|
32
|
-
error: string;
|
|
33
|
-
code: string;
|
|
34
|
-
}> | NextResponse<{
|
|
35
|
-
viable: boolean;
|
|
36
|
-
needsRefresh: boolean;
|
|
37
|
-
authenticated: boolean;
|
|
38
|
-
reason: string;
|
|
39
|
-
}> | NextResponse<{
|
|
40
|
-
tenantRequiresTwoFactor: boolean;
|
|
41
|
-
userHasCompletedTenantTwoFactorRequirements: boolean;
|
|
42
|
-
userStillNeedsTwoFactor: boolean;
|
|
43
|
-
tenantAllowed2FAMethods: string[] | null;
|
|
44
|
-
nextStep: "enroll_mfa" | "mfa_verify" | null | undefined;
|
|
45
|
-
requires2FA: boolean;
|
|
46
|
-
twoFactorComplete: boolean;
|
|
47
|
-
accessTokenExpired: boolean;
|
|
48
|
-
expiresAt: string;
|
|
49
|
-
roles: string[];
|
|
50
|
-
clientId: string | null;
|
|
51
|
-
forceInvalid: boolean;
|
|
52
|
-
unavailable: boolean;
|
|
53
|
-
revoked: boolean;
|
|
54
|
-
viable: boolean;
|
|
55
|
-
needsRefresh: boolean;
|
|
56
|
-
expiresIn: number;
|
|
57
|
-
hasRefreshToken: boolean;
|
|
58
|
-
authenticated: boolean;
|
|
59
|
-
sessionTokenId: string | null;
|
|
60
|
-
revocationStatus: RevocationStatus;
|
|
61
|
-
}> | NextResponse<{
|
|
62
|
-
viable: boolean;
|
|
63
|
-
needsRefresh: boolean;
|
|
64
|
-
authenticated: boolean;
|
|
65
|
-
revocationStatus: RevocationStatus;
|
|
66
|
-
error: string;
|
|
67
|
-
code: string;
|
|
68
|
-
correlationId: string;
|
|
69
|
-
}>>;
|
|
16
|
+
import { NextRequest } from 'next/server';
|
|
17
|
+
/** PAY-2994: session state is per-user and some answers carry tokens; never cached (noStore). */
|
|
18
|
+
export declare const GET: (req: NextRequest) => Promise<Response>;
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* @since auth-ready-v2
|
|
16
16
|
*/
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
-
exports.GET =
|
|
18
|
+
exports.GET = void 0;
|
|
19
19
|
const revocation_fields_1 = require("../../auth/revocation-fields");
|
|
20
20
|
const server_1 = require("next/server");
|
|
21
21
|
const auth_1 = require("../../server/auth");
|
|
@@ -23,6 +23,7 @@ const session_store_1 = require("../../lib/session-store");
|
|
|
23
23
|
const idp_client_config_1 = require("../../lib/idp-client-config");
|
|
24
24
|
const safe_error_response_1 = require("../../lib/safe-error-response");
|
|
25
25
|
const token_log_id_1 = require("../../lib/token-log-id");
|
|
26
|
+
const no_store_1 = require("../../lib/no-store");
|
|
26
27
|
const sessions_1 = require("../../vibe/sessions");
|
|
27
28
|
/**
|
|
28
29
|
* Get tenant-wide 2FA requirement from cached client config (from broker handshake)
|
|
@@ -135,7 +136,7 @@ function resolveClientId(sessionClientId) {
|
|
|
135
136
|
* - needsRefresh: boolean - Whether a refresh is recommended
|
|
136
137
|
* - expiresIn: number - Seconds until token expires
|
|
137
138
|
*/
|
|
138
|
-
async function
|
|
139
|
+
async function getHandler(req) {
|
|
139
140
|
try {
|
|
140
141
|
// PAY-2216: ask for the session's STATE, not a session-or-null. A null used to cover a store outage, a deleted
|
|
141
142
|
// session and no cookie alike, so this route answered 'No session found' to all three and the middleware could
|
|
@@ -187,6 +188,9 @@ async function GET(req) {
|
|
|
187
188
|
// stack just resolved the cookie cleanly. Keeps the two viability impls
|
|
188
189
|
// (this route + `api-handlers/session/viability.ts`) in lockstep.
|
|
189
190
|
let session = sessionToken ? await (0, session_store_1.getSession)(sessionToken) : null;
|
|
191
|
+
// PAY-2467: a row read from the CANONICAL store is the raw JSON the callback wrote, so an `mfaVerified` boolean on it is a
|
|
192
|
+
// MEASUREMENT (an absent key is a legacy row). Only the BA-record fallback collapses absent to false and needs mfaSignalPresent.
|
|
193
|
+
const fromCanonicalStore = !!session;
|
|
190
194
|
if (sessionToken && !session) {
|
|
191
195
|
session = await (0, session_store_1.getBetterAuthSession)(sessionToken);
|
|
192
196
|
if (session) {
|
|
@@ -278,9 +282,15 @@ async function GET(req) {
|
|
|
278
282
|
* middleware actually gates on; `tenantRequiresTwoFactor` stays pure —
|
|
279
283
|
* it names TENANT policy, and this override is not tenant state.
|
|
280
284
|
*/
|
|
281
|
-
const oauthSessionNeedsTwoFactor =
|
|
285
|
+
const oauthSessionNeedsTwoFactor =
|
|
286
|
+
// PAY-2467: the signal is present on the BA-fallback path (mfaSignalPresent) OR when the canonical row itself carries a boolean.
|
|
287
|
+
// Before this, the override was dead on the canonical path every consumer mounts, so gating rested on tenant config alone.
|
|
288
|
+
(session?.mfaSignalPresent === true || (fromCanonicalStore && typeof session?.mfaVerified === 'boolean')) &&
|
|
282
289
|
session?.mfaVerified === false &&
|
|
283
|
-
!(session.idpRefreshToken || session?.refreshToken)
|
|
290
|
+
!(session.idpRefreshToken || session?.refreshToken) &&
|
|
291
|
+
// An IdP DISCARD (refresh.ts / ensure-fresh-access-token.ts write idpRefreshToken '' with refreshTokenClearedReason 'IDP_DISCARD_TOKEN:<code>') leaves
|
|
292
|
+
// mfaVerified false and no refresh token too, but that is a DEAD session, not a pending second factor: it must not be sent to verify-code (QAPert 84816).
|
|
293
|
+
!String(session?.refreshTokenClearedReason ?? '').startsWith('IDP_DISCARD_TOKEN');
|
|
284
294
|
// --- Session revocation status (PAY-1882 step 4 producer half) ---
|
|
285
295
|
/*
|
|
286
296
|
* Resolve the four-state revocation status for this user (BAPert 66320).
|
|
@@ -486,3 +496,5 @@ async function GET(req) {
|
|
|
486
496
|
}, { status: 500 });
|
|
487
497
|
}
|
|
488
498
|
}
|
|
499
|
+
/** PAY-2994: session state is per-user and some answers carry tokens; never cached (noStore). */
|
|
500
|
+
exports.GET = (0, no_store_1.noStore)(getHandler);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const GET: () => Promise<Response>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GET = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Ready-to-use /api/health route: reports Redis reachability (PAY-2804).
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* // app/api/health/route.ts
|
|
10
|
+
* export const runtime = 'nodejs';
|
|
11
|
+
* export { GET } from '@payez/next-mvp/routes/health';
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
const health_1 = require("../api-handlers/health");
|
|
15
|
+
const redis_1 = require("../lib/redis");
|
|
16
|
+
exports.GET = (0, health_1.createHealthHandler)(() => (0, redis_1.getRedis)());
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* ```
|
|
11
11
|
*/
|
|
12
12
|
import { NextRequest } from 'next/server';
|
|
13
|
-
|
|
14
|
-
export declare const GET:
|
|
13
|
+
/** PAY-2994: the user's MFA methods; never cached (noStore). */
|
|
14
|
+
export declare const GET: (req: NextRequest) => Promise<Response>;
|
|
15
15
|
type Ctx = {
|
|
16
16
|
params: Promise<{
|
|
17
17
|
id: string;
|
|
@@ -3,7 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.GET = void 0;
|
|
4
4
|
exports.DELETE = DELETE;
|
|
5
5
|
const methods_1 = require("../../api-handlers/mfa/methods");
|
|
6
|
-
|
|
6
|
+
const no_store_1 = require("../../lib/no-store");
|
|
7
|
+
/** PAY-2994: the user's MFA methods; never cached (noStore). */
|
|
8
|
+
exports.GET = (0, no_store_1.noStore)(methods_1.GET);
|
|
7
9
|
async function DELETE(req, ctx) {
|
|
8
10
|
const { id } = await ctx.params;
|
|
9
11
|
return (0, methods_1.DELETE)(req, id);
|
|
@@ -1,10 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* @example
|
|
5
|
-
* ```typescript
|
|
6
|
-
* // app/api/passkeys/route.ts
|
|
7
|
-
* export { GET } from '@payez/next-mvp/routes/passkeys/list';
|
|
8
|
-
* ```
|
|
9
|
-
*/
|
|
10
|
-
export { GET } from '../../api-handlers/passkeys/list';
|
|
1
|
+
/** PAY-2994: the signed-in user's passkeys; never cached (noStore). */
|
|
2
|
+
export declare const GET: (req: import("next/server").NextRequest) => Promise<Response>;
|
|
@@ -10,5 +10,7 @@ exports.GET = void 0;
|
|
|
10
10
|
* export { GET } from '@payez/next-mvp/routes/passkeys/list';
|
|
11
11
|
* ```
|
|
12
12
|
*/
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
const list_1 = require("../../api-handlers/passkeys/list");
|
|
14
|
+
const no_store_1 = require("../../lib/no-store");
|
|
15
|
+
/** PAY-2994: the signed-in user's passkeys; never cached (noStore). */
|
|
16
|
+
exports.GET = (0, no_store_1.noStore)(list_1.GET);
|
|
@@ -13,4 +13,5 @@
|
|
|
13
13
|
* @version 2.0.0
|
|
14
14
|
* @since auth-ready-v2
|
|
15
15
|
*/
|
|
16
|
-
|
|
16
|
+
/** PAY-2994: refresh-token viability of this session; never cached (noStore). */
|
|
17
|
+
export declare const GET: (req: import("next/server").NextRequest) => Promise<Response>;
|
|
@@ -16,5 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
18
|
exports.GET = void 0;
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
const refresh_viability_1 = require("../../api-handlers/session/refresh-viability");
|
|
20
|
+
const no_store_1 = require("../../lib/no-store");
|
|
21
|
+
/** PAY-2994: refresh-token viability of this session; never cached (noStore). */
|
|
22
|
+
exports.GET = (0, no_store_1.noStore)(refresh_viability_1.GET);
|
package/dist/utils/api.d.ts
CHANGED
|
@@ -49,6 +49,38 @@ export declare const RESET_BREACHED_TEXT = "This password appears in a known dat
|
|
|
49
49
|
* they can retry.
|
|
50
50
|
*/
|
|
51
51
|
export declare function resetFailureText(status: number, rules?: string[], policy?: boolean, tokenInvalid?: boolean): string;
|
|
52
|
+
/**
|
|
53
|
+
* SMS-first sign-up (door 3b): when the phone code is right but the email is still unconfirmed, the server answers 200 success with
|
|
54
|
+
* `next_step = EMAIL_CONFIRMATION_REQUIRED` and NO reset token. `verifyOnboardingSmsCode` surfaces that as `nextStep`;
|
|
55
|
+
* `interpretSmsVerification` turns a result into ONE outcome, testing `nextStep` BEFORE `success`, so a consumer that uses it can
|
|
56
|
+
* never reach "verified" without a reset token or with an unfinished step. The sentences are the package's own, never the server's text.
|
|
57
|
+
*/
|
|
58
|
+
export declare const SMS_NEXT_STEP_EMAIL_CONFIRMATION_REQUIRED = "EMAIL_CONFIRMATION_REQUIRED";
|
|
59
|
+
export declare const SMS_EMAIL_CONFIRMATION_TEXT = "Your phone is verified. Confirm your email to finish signing up.";
|
|
60
|
+
export declare const SMS_MORE_STEPS_TEXT = "One more step is needed to finish signing up.";
|
|
61
|
+
export interface SmsCodeVerification {
|
|
62
|
+
success: boolean;
|
|
63
|
+
resetToken: string;
|
|
64
|
+
email: string;
|
|
65
|
+
clientKey: string;
|
|
66
|
+
/** Present when the server says more steps remain (an UNRECOGNISED value is kept as a non-empty string, never dropped). */
|
|
67
|
+
nextStep?: string;
|
|
68
|
+
}
|
|
69
|
+
export type SmsVerificationOutcome = {
|
|
70
|
+
kind: 'verified';
|
|
71
|
+
resetToken: string;
|
|
72
|
+
email: string;
|
|
73
|
+
clientKey: string;
|
|
74
|
+
} | {
|
|
75
|
+
kind: 'email_confirmation_required';
|
|
76
|
+
message: string;
|
|
77
|
+
} | {
|
|
78
|
+
kind: 'more_steps';
|
|
79
|
+
message: string;
|
|
80
|
+
} | {
|
|
81
|
+
kind: 'not_verified';
|
|
82
|
+
};
|
|
83
|
+
export declare function interpretSmsVerification(result: SmsCodeVerification): SmsVerificationOutcome;
|
|
52
84
|
export declare class AccountApi {
|
|
53
85
|
/**
|
|
54
86
|
* Get masked user info (email, phone, authenticator status)
|
|
@@ -90,12 +122,7 @@ export declare class AccountApi {
|
|
|
90
122
|
email: string;
|
|
91
123
|
clientKey: string;
|
|
92
124
|
}>;
|
|
93
|
-
verifyOnboardingSmsCode(onboardToken: string, code: string, clientKey?: string): Promise<
|
|
94
|
-
success: boolean;
|
|
95
|
-
resetToken: string;
|
|
96
|
-
email: string;
|
|
97
|
-
clientKey: string;
|
|
98
|
-
}>;
|
|
125
|
+
verifyOnboardingSmsCode(onboardToken: string, code: string, clientKey?: string): Promise<SmsCodeVerification>;
|
|
99
126
|
setOnboardingPassword(data: {
|
|
100
127
|
onboardToken: string;
|
|
101
128
|
email: string;
|
package/dist/utils/api.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.accountApi = exports.AccountApi = exports.RESET_BREACHED_TEXT = exports.RESET_NEUTRAL_TEXT = exports.RECOVERY_EXPIRED_TEXT = exports.ApiError = void 0;
|
|
3
|
+
exports.accountApi = exports.AccountApi = exports.SMS_MORE_STEPS_TEXT = exports.SMS_EMAIL_CONFIRMATION_TEXT = exports.SMS_NEXT_STEP_EMAIL_CONFIRMATION_REQUIRED = exports.RESET_BREACHED_TEXT = exports.RESET_NEUTRAL_TEXT = exports.RECOVERY_EXPIRED_TEXT = exports.ApiError = void 0;
|
|
4
4
|
exports.apiFetch = apiFetch;
|
|
5
5
|
exports.resetFailureText = resetFailureText;
|
|
6
|
+
exports.interpretSmsVerification = interpretSmsVerification;
|
|
6
7
|
const standardized_client_api_1 = require("../lib/standardized-client-api");
|
|
7
8
|
const password_rules_1 = require("../lib/password-rules");
|
|
8
9
|
class ApiError extends Error {
|
|
@@ -110,6 +111,30 @@ function resetFailureText(status, rules = [], policy = false, tokenInvalid = fal
|
|
|
110
111
|
}
|
|
111
112
|
return "We couldn't reset your password just now. Please try again.";
|
|
112
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* SMS-first sign-up (door 3b): when the phone code is right but the email is still unconfirmed, the server answers 200 success with
|
|
116
|
+
* `next_step = EMAIL_CONFIRMATION_REQUIRED` and NO reset token. `verifyOnboardingSmsCode` surfaces that as `nextStep`;
|
|
117
|
+
* `interpretSmsVerification` turns a result into ONE outcome, testing `nextStep` BEFORE `success`, so a consumer that uses it can
|
|
118
|
+
* never reach "verified" without a reset token or with an unfinished step. The sentences are the package's own, never the server's text.
|
|
119
|
+
*/
|
|
120
|
+
exports.SMS_NEXT_STEP_EMAIL_CONFIRMATION_REQUIRED = 'EMAIL_CONFIRMATION_REQUIRED';
|
|
121
|
+
exports.SMS_EMAIL_CONFIRMATION_TEXT = 'Your phone is verified. Confirm your email to finish signing up.';
|
|
122
|
+
exports.SMS_MORE_STEPS_TEXT = 'One more step is needed to finish signing up.';
|
|
123
|
+
function interpretSmsVerification(result) {
|
|
124
|
+
// success:false WINS over any step (BAPert 88508): a failed answer must never say "Your phone is verified".
|
|
125
|
+
if (!result.success)
|
|
126
|
+
return { kind: 'not_verified' };
|
|
127
|
+
// For every answer that is not a failure, nextStep comes BEFORE the token test: a step that is not finished is never "verified".
|
|
128
|
+
if (result.nextStep) {
|
|
129
|
+
return result.nextStep === exports.SMS_NEXT_STEP_EMAIL_CONFIRMATION_REQUIRED
|
|
130
|
+
? { kind: 'email_confirmation_required', message: exports.SMS_EMAIL_CONFIRMATION_TEXT }
|
|
131
|
+
: { kind: 'more_steps', message: exports.SMS_MORE_STEPS_TEXT };
|
|
132
|
+
}
|
|
133
|
+
// success with an EMPTY reset token is the shape that is dangerous (set-password would be called with ''): not verified.
|
|
134
|
+
if (!result.resetToken)
|
|
135
|
+
return { kind: 'more_steps', message: exports.SMS_MORE_STEPS_TEXT };
|
|
136
|
+
return { kind: 'verified', resetToken: result.resetToken, email: result.email, clientKey: result.clientKey };
|
|
137
|
+
}
|
|
113
138
|
class AccountApi {
|
|
114
139
|
/**
|
|
115
140
|
* Get masked user info (email, phone, authenticator status)
|
|
@@ -303,11 +328,19 @@ class AccountApi {
|
|
|
303
328
|
}
|
|
304
329
|
// Extract from nested data structure
|
|
305
330
|
const data = result.data?.data || result.data || {};
|
|
331
|
+
// next_step (door 3b): read from the nested shapes AND a flat body. Absent / null / '' = no step; a non-string value is an
|
|
332
|
+
// UNRECOGNISED step and is kept as a non-empty string (never dropped, never mistaken for the known one). That includes false and 0:
|
|
333
|
+
// a server that means "no step" must send nothing or null (or '').
|
|
334
|
+
const rawStep = data.next_step ?? result.next_step;
|
|
335
|
+
const nextStep = rawStep === undefined || rawStep === null || rawStep === ''
|
|
336
|
+
? undefined
|
|
337
|
+
: typeof rawStep === 'string' ? rawStep : 'UNRECOGNISED_NEXT_STEP';
|
|
306
338
|
return {
|
|
307
339
|
success: data.success !== false,
|
|
308
340
|
resetToken: data.reset_token || '',
|
|
309
341
|
email: data.email || '',
|
|
310
|
-
clientKey: data.client_key || ''
|
|
342
|
+
clientKey: data.client_key || '',
|
|
343
|
+
...(nextStep !== undefined ? { nextStep } : {})
|
|
311
344
|
};
|
|
312
345
|
}
|
|
313
346
|
async setOnboardingPassword(data, clientKey) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@payez/next-mvp",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.13.1",
|
|
4
4
|
"sideEffects": false,
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -505,6 +505,11 @@
|
|
|
505
505
|
"require": "./dist/api-handlers/auth/validate.js",
|
|
506
506
|
"default": "./api-handlers/auth/validate.js"
|
|
507
507
|
},
|
|
508
|
+
"./api-handlers/health": {
|
|
509
|
+
"types": "./dist/api-handlers/health.d.ts",
|
|
510
|
+
"require": "./dist/api-handlers/health.js",
|
|
511
|
+
"default": "./dist/api-handlers/health.js"
|
|
512
|
+
},
|
|
508
513
|
"./api-handlers/session/refresh-viability": {
|
|
509
514
|
"types": "./dist/api-handlers/session/refresh-viability.d.ts",
|
|
510
515
|
"require": "./dist/api-handlers/session/refresh-viability.js",
|
|
@@ -705,6 +710,11 @@
|
|
|
705
710
|
"require": "./dist/routes/auth/viability.js",
|
|
706
711
|
"default": "./dist/routes/auth/viability.js"
|
|
707
712
|
},
|
|
713
|
+
"./routes/health": {
|
|
714
|
+
"types": "./dist/routes/health.d.ts",
|
|
715
|
+
"require": "./dist/routes/health.js",
|
|
716
|
+
"default": "./dist/routes/health.js"
|
|
717
|
+
},
|
|
708
718
|
"./routes/session/refresh-viability": {
|
|
709
719
|
"types": "./dist/routes/session/refresh-viability.d.ts",
|
|
710
720
|
"require": "./dist/routes/session/refresh-viability.js",
|
|
@@ -1084,6 +1094,16 @@
|
|
|
1084
1094
|
"types": "./dist/lib/credential-sign-in-client.d.ts",
|
|
1085
1095
|
"require": "./dist/lib/credential-sign-in-client.js",
|
|
1086
1096
|
"default": "./dist/lib/credential-sign-in-client.js"
|
|
1097
|
+
},
|
|
1098
|
+
"./components/mfa/TotpEnrollPanel": {
|
|
1099
|
+
"types": "./dist/components/mfa/TotpEnrollPanel.d.ts",
|
|
1100
|
+
"require": "./dist/components/mfa/TotpEnrollPanel.js",
|
|
1101
|
+
"default": "./dist/components/mfa/TotpEnrollPanel.js"
|
|
1102
|
+
},
|
|
1103
|
+
"./dist/pages/enroll": {
|
|
1104
|
+
"types": "./dist/pages/enroll/page.d.ts",
|
|
1105
|
+
"require": "./dist/pages/enroll/page.js",
|
|
1106
|
+
"default": "./dist/pages/enroll/page.js"
|
|
1087
1107
|
}
|
|
1088
1108
|
},
|
|
1089
1109
|
"peerDependencies": {
|