@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.
Files changed (115) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/dist/api-handlers/account/profile.d.ts +2 -39
  3. package/dist/api-handlers/account/profile.js +5 -2
  4. package/dist/api-handlers/anon/preferences.d.ts +1 -14
  5. package/dist/api-handlers/anon/preferences.js +5 -5
  6. package/dist/api-handlers/auth/credential-login.js +28 -4
  7. package/dist/api-handlers/health.d.ts +5 -0
  8. package/dist/api-handlers/health.js +48 -0
  9. package/dist/api-handlers/mfa/totp-confirm.js +8 -4
  10. package/dist/api-handlers/mfa/totp-enroll.js +8 -4
  11. package/dist/api-handlers/session/refresh-viability.d.ts +3 -15
  12. package/dist/api-handlers/session/refresh-viability.js +5 -2
  13. package/dist/api-handlers/session/viability.d.ts +3 -8
  14. package/dist/api-handlers/session/viability.js +11 -4
  15. package/dist/auth/better-auth-exposure.d.ts +11 -0
  16. package/dist/auth/better-auth-exposure.js +73 -0
  17. package/dist/auth/better-auth.d.ts +6 -0
  18. package/dist/auth/better-auth.js +42 -3
  19. package/dist/auth/route-config.js +9 -0
  20. package/dist/auth/session-reset-link.d.ts +32 -0
  21. package/dist/auth/session-reset-link.js +45 -1
  22. package/dist/components/auth/CredentialSignInForm.d.ts +7 -1
  23. package/dist/components/auth/CredentialSignInForm.js +13 -4
  24. package/dist/components/auth/EnrollAuthenticatorOffer.d.ts +21 -0
  25. package/dist/components/auth/EnrollAuthenticatorOffer.js +23 -0
  26. package/dist/components/mfa/QrCode.d.ts +6 -0
  27. package/dist/components/mfa/QrCode.js +14 -0
  28. package/dist/components/mfa/TotpEnrollPanel.d.ts +6 -0
  29. package/dist/components/mfa/TotpEnrollPanel.js +117 -0
  30. package/dist/edge/middleware.d.ts +1 -1
  31. package/dist/edge/middleware.js +2 -1
  32. package/dist/index.d.ts +1 -1
  33. package/dist/index.js +2 -1
  34. package/dist/lib/credential-sign-in-client.d.ts +26 -1
  35. package/dist/lib/credential-sign-in-client.js +32 -6
  36. package/dist/lib/idp-client-config.js +4 -1
  37. package/dist/lib/login-error-message.d.ts +3 -0
  38. package/dist/lib/login-error-message.js +12 -1
  39. package/dist/lib/mfa-json-guard.d.ts +12 -0
  40. package/dist/lib/mfa-json-guard.js +19 -0
  41. package/dist/lib/nextauth-secret.js +3 -1
  42. package/dist/lib/no-store.d.ts +13 -0
  43. package/dist/lib/no-store.js +35 -0
  44. package/dist/lib/payez-client-secret.d.ts +14 -0
  45. package/dist/lib/payez-client-secret.js +41 -0
  46. package/dist/lib/qrcode.d.ts +22 -0
  47. package/dist/lib/qrcode.js +362 -0
  48. package/dist/lib/sign-in-failure.d.ts +35 -0
  49. package/dist/lib/sign-in-failure.js +85 -0
  50. package/dist/lib/twofa-rejection.d.ts +2 -0
  51. package/dist/lib/twofa-rejection.js +5 -2
  52. package/dist/middleware/create-middleware.d.ts +24 -0
  53. package/dist/middleware/create-middleware.js +56 -9
  54. package/dist/middleware/prefetch-guard-status.d.ts +21 -0
  55. package/dist/middleware/prefetch-guard-status.js +42 -0
  56. package/dist/pages/enroll/page.d.ts +7 -0
  57. package/dist/pages/enroll/page.js +15 -0
  58. package/dist/pages/login/page.js +1 -1
  59. package/dist/routes/auth/session-reset.d.ts +11 -1
  60. package/dist/routes/auth/session-reset.js +38 -13
  61. package/dist/routes/auth/session.d.ts +2 -9
  62. package/dist/routes/auth/session.js +5 -2
  63. package/dist/routes/auth/viability.d.ts +3 -54
  64. package/dist/routes/auth/viability.js +16 -4
  65. package/dist/routes/health.d.ts +1 -0
  66. package/dist/routes/health.js +16 -0
  67. package/dist/routes/mfa/methods.d.ts +2 -2
  68. package/dist/routes/mfa/methods.js +3 -1
  69. package/dist/routes/passkeys/list.d.ts +2 -10
  70. package/dist/routes/passkeys/list.js +4 -2
  71. package/dist/routes/session/refresh-viability.d.ts +2 -1
  72. package/dist/routes/session/refresh-viability.js +4 -2
  73. package/dist/utils/api.d.ts +33 -6
  74. package/dist/utils/api.js +35 -2
  75. package/package.json +21 -1
  76. package/src/api-handlers/account/profile.ts +5 -1
  77. package/src/api-handlers/anon/preferences.ts +5 -4
  78. package/src/api-handlers/auth/credential-login.ts +29 -4
  79. package/src/api-handlers/health.ts +53 -0
  80. package/src/api-handlers/mfa/totp-confirm.ts +7 -4
  81. package/src/api-handlers/mfa/totp-enroll.ts +7 -4
  82. package/src/api-handlers/session/refresh-viability.ts +5 -1
  83. package/src/api-handlers/session/viability.ts +12 -4
  84. package/src/auth/better-auth-exposure.ts +69 -0
  85. package/src/auth/better-auth.ts +42 -3
  86. package/src/auth/route-config.ts +9 -0
  87. package/src/auth/session-reset-link.ts +47 -0
  88. package/src/components/auth/CredentialSignInForm.tsx +21 -3
  89. package/src/components/auth/EnrollAuthenticatorOffer.tsx +48 -0
  90. package/src/components/mfa/QrCode.tsx +24 -0
  91. package/src/components/mfa/TotpEnrollPanel.tsx +203 -0
  92. package/src/edge/middleware.ts +1 -1
  93. package/src/index.ts +1 -1
  94. package/src/lib/credential-sign-in-client.ts +48 -8
  95. package/src/lib/idp-client-config.ts +5 -1
  96. package/src/lib/login-error-message.ts +13 -1
  97. package/src/lib/mfa-json-guard.ts +19 -0
  98. package/src/lib/nextauth-secret.ts +3 -1
  99. package/src/lib/no-store.ts +30 -0
  100. package/src/lib/payez-client-secret.ts +39 -0
  101. package/src/lib/qrcode.ts +348 -0
  102. package/src/lib/sign-in-failure.ts +88 -0
  103. package/src/lib/twofa-rejection.ts +7 -2
  104. package/src/middleware/create-middleware.ts +69 -9
  105. package/src/middleware/prefetch-guard-status.ts +56 -0
  106. package/src/pages/enroll/page.tsx +22 -0
  107. package/src/pages/login/page.tsx +2 -1
  108. package/src/routes/auth/session-reset.ts +46 -11
  109. package/src/routes/auth/session.ts +5 -1
  110. package/src/routes/auth/viability.ts +16 -4
  111. package/src/routes/health.ts +14 -0
  112. package/src/routes/mfa/methods.ts +3 -1
  113. package/src/routes/passkeys/list.ts +5 -1
  114. package/src/routes/session/refresh-viability.ts +5 -1
  115. 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
+ }
@@ -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 declare function GET(request: NextRequest): Promise<NextResponse>;
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.GET = GET;
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
- async function GET(request) {
30
- if ((0, create_middleware_1.isPrefetchRequest)(request)) {
31
- return new server_1.NextResponse(null, { status: 204, headers: { 'cache-control': 'no-store' } });
32
- }
33
- const bound = (0, session_reset_link_1.boundSessionCookie)((n) => request.cookies.get(n)?.value, (0, app_slug_1.getSessionCookieName)(), (0, app_slug_1.getSecureSessionCookieName)());
34
- const { params, signed } = await (0, session_reset_link_1.readResetRequest)(request.nextUrl.searchParams, (0, session_reset_link_1.resetSecret)(), bound);
35
- const response = server_1.NextResponse.redirect(new URL((0, session_reset_link_1.loginLocation)(params), request.url), 307);
36
- response.headers.set('cache-control', 'no-store');
37
- if (signed)
38
- (0, session_cookie_clear_1.expireSessionCookies)(response, Array.from(request.cookies, ([name]) => name));
39
- return response;
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
- * GET /api/auth/session - Check current session status
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 = 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 GET(req) {
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, NextResponse } from 'next/server';
17
- import { type RevocationStatus } from '../../vibe/sessions';
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 = 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 GET(req) {
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 = session?.mfaSignalPresent === true &&
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
- import { GET as listMethods } from '../../api-handlers/mfa/methods';
14
- export declare const GET: typeof listMethods;
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
- exports.GET = methods_1.GET;
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
- * Ready-to-Use Passkey List Route
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
- var list_1 = require("../../api-handlers/passkeys/list");
14
- Object.defineProperty(exports, "GET", { enumerable: true, get: function () { return list_1.GET; } });
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
- export { GET } from '../../api-handlers/session/refresh-viability';
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
- var refresh_viability_1 = require("../../api-handlers/session/refresh-viability");
20
- Object.defineProperty(exports, "GET", { enumerable: true, get: function () { return refresh_viability_1.GET; } });
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);
@@ -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.12.1",
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": {