@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
package/README.md
CHANGED
|
@@ -145,9 +145,16 @@ IDP_URL=https://idp.payez.net # or http://localhost:32785 for local d
|
|
|
145
145
|
# NextAuth trusts request headers for OAuth callback URLs
|
|
146
146
|
AUTH_TRUST_HOST=true
|
|
147
147
|
|
|
148
|
-
#
|
|
149
|
-
# The
|
|
150
|
-
#
|
|
148
|
+
# Secrets are resolved ENV FIRST, IdP client config second (PAY-1460, 4.4.6+).
|
|
149
|
+
# The IdP is retiring the secrets it serves in client config, so production
|
|
150
|
+
# deployments should set these from a k8s secret:
|
|
151
|
+
# BETTER_AUTH_SECRET=... # session signing secret (canonical name)
|
|
152
|
+
# NEXTAUTH_SECRET=... # legacy alias, used only if BETTER_AUTH_SECRET is unset
|
|
153
|
+
# GOOGLE_CLIENT_SECRET=... # per OAuth provider: <PROVIDER>_CLIENT_SECRET,
|
|
154
|
+
# APPLE_CLIENT_SECRET=... # provider name upper-cased, non-alphanumerics as "_"
|
|
155
|
+
# Startup is FATAL only when BOTH the env and the IdP config lack a signing secret.
|
|
156
|
+
# Values are never logged; the log names the source ("authSecret source: env:BETTER_AUTH_SECRET").
|
|
157
|
+
# Env-sourced secrets are never written to the Redis config cache.
|
|
151
158
|
|
|
152
159
|
# Optional
|
|
153
160
|
REDIS_URL=redis://localhost:6379 # For session storage
|
package/dist/api/auth-handler.js
CHANGED
|
@@ -19,6 +19,7 @@ const server_1 = require("next/server");
|
|
|
19
19
|
const auth_1 = require("../server/auth");
|
|
20
20
|
const nanoid_1 = require("nanoid");
|
|
21
21
|
const session_store_1 = require("../lib/session-store");
|
|
22
|
+
const token_log_id_1 = require("../lib/token-log-id");
|
|
22
23
|
/**
|
|
23
24
|
* Creates an auth-aware handler with automatic token refresh
|
|
24
25
|
*
|
|
@@ -179,7 +180,7 @@ function createAuthHandler(options = {}) {
|
|
|
179
180
|
refreshRequestBody.two_factor_completed_at = new Date(currentSession.mfaCompletedAt).toISOString();
|
|
180
181
|
}
|
|
181
182
|
console.info('[AUTH_HANDLER] Executing refresh against IDP', {
|
|
182
|
-
sessionToken:
|
|
183
|
+
sessionToken: (0, token_log_id_1.tokenLogId)(sessionToken),
|
|
183
184
|
hasRefreshToken: true
|
|
184
185
|
});
|
|
185
186
|
const response = await fetch(`${idpBaseUrl}/api/ExternalAuth/refresh`, {
|
|
@@ -192,7 +193,8 @@ function createAuthHandler(options = {}) {
|
|
|
192
193
|
});
|
|
193
194
|
if (!response.ok) {
|
|
194
195
|
const errorText = await response.text().catch(() => 'Unknown error');
|
|
195
|
-
|
|
196
|
+
// PAY-1427: never the raw upstream body (the refresh.ts:528 twin PR 139 fixed). Status + length only.
|
|
197
|
+
console.error('[AUTH_HANDLER] Refresh failed:', { status: response.status, bodyLength: errorText.length });
|
|
196
198
|
return { success: false, error: `Refresh failed: ${response.status}` };
|
|
197
199
|
}
|
|
198
200
|
const data = await response.json();
|
|
@@ -317,7 +319,7 @@ function createAuthHandler(options = {}) {
|
|
|
317
319
|
const requestId = generateRequestId();
|
|
318
320
|
console.info('[AUTH_HANDLER] Token near expiry, initiating coordinated refresh', {
|
|
319
321
|
requestId,
|
|
320
|
-
sessionToken:
|
|
322
|
+
sessionToken: (0, token_log_id_1.tokenLogId)(authContext.sessionToken)
|
|
321
323
|
});
|
|
322
324
|
const refreshResult = await performCoordinatedRefresh(authContext.sessionToken, requestId);
|
|
323
325
|
if (refreshResult.success && refreshResult.accessToken) {
|
|
@@ -5,6 +5,7 @@ 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
7
|
const nanoid_1 = require("nanoid");
|
|
8
|
+
const safe_error_response_1 = require("../../lib/safe-error-response");
|
|
8
9
|
// ...
|
|
9
10
|
async function POST(req) {
|
|
10
11
|
const requestId = (0, nanoid_1.nanoid)();
|
|
@@ -88,7 +89,8 @@ async function POST(req) {
|
|
|
88
89
|
message: errorMessage,
|
|
89
90
|
error_code: responseData.error_code || 'CHANGE_PASSWORD_FAILED',
|
|
90
91
|
request_id: requestId,
|
|
91
|
-
|
|
92
|
+
// PAY-1438: only the password-policy messages the security pages display, never the raw envelope.
|
|
93
|
+
details: (0, safe_error_response_1.projectPasswordPolicyDetails)(responseData.details),
|
|
92
94
|
}, { status: idpResponse.status });
|
|
93
95
|
}
|
|
94
96
|
return server_1.NextResponse.json({
|
|
@@ -98,11 +100,12 @@ async function POST(req) {
|
|
|
98
100
|
});
|
|
99
101
|
}
|
|
100
102
|
catch (error) {
|
|
101
|
-
console.error('[CHANGE_PASSWORD] Error:', error);
|
|
102
103
|
const requestId = req.headers.get('x-request-id') ?? crypto.randomUUID();
|
|
104
|
+
// PAY-1438: the exception text stays in the log, keyed by the request_id the caller receives.
|
|
105
|
+
console.error('[CHANGE_PASSWORD] Error:', { request_id: requestId, error });
|
|
103
106
|
return server_1.NextResponse.json({
|
|
104
107
|
success: false,
|
|
105
|
-
message:
|
|
108
|
+
message: 'Failed to change password',
|
|
106
109
|
error_code: 'INTERNAL_ERROR',
|
|
107
110
|
request_id: requestId,
|
|
108
111
|
}, { status: 500 });
|
|
@@ -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
|
// IDP masked-info is POST and uses capital 'A' in /api/Account
|
|
8
9
|
async function POST(req) {
|
|
9
10
|
const url = `${env_1.ENV_CONFIG.IDP_URL}/api/Account/masked-info`;
|
|
@@ -24,7 +25,7 @@ async function POST(req) {
|
|
|
24
25
|
return server_1.NextResponse.json({
|
|
25
26
|
success: false,
|
|
26
27
|
message: 'Upstream error',
|
|
27
|
-
error: { code: 'UPSTREAM_SERVICE_ERROR', status: result.status, details: result.json },
|
|
28
|
+
error: { code: 'UPSTREAM_SERVICE_ERROR', status: result.status, details: (0, safe_error_response_1.projectIdpError)(result.json) },
|
|
28
29
|
meta: { attemptedRefresh: result.attemptedRefresh },
|
|
29
30
|
}, { status: result.status });
|
|
30
31
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
/** The recovery bearer from the inbound request, or null when absent / malformed. Never throws. */
|
|
3
|
+
export declare function inboundRecoveryBearer(req: Pick<NextRequest, 'headers'>): string | null;
|
|
4
|
+
export interface RecoveryForward {
|
|
5
|
+
/** IdP path after the host, e.g. /api/Account/recovery/send-code */
|
|
6
|
+
upstreamPath: string;
|
|
7
|
+
/** log/route label used for the client-key resolver */
|
|
8
|
+
where: string;
|
|
9
|
+
/** stable `error` code for an upstream that could not be reached */
|
|
10
|
+
failureCode: string;
|
|
11
|
+
/** user-facing fallback when the IdP gave no message */
|
|
12
|
+
fallbackMessage: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function forwardRecoveryCall(req: NextRequest, spec: RecoveryForward): Promise<NextResponse>;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.inboundRecoveryBearer = inboundRecoveryBearer;
|
|
4
|
+
exports.forwardRecoveryCall = forwardRecoveryCall;
|
|
5
|
+
const server_1 = require("next/server");
|
|
6
|
+
const env_1 = require("../../../config/env");
|
|
7
|
+
const idp_client_config_1 = require("../../../lib/idp-client-config");
|
|
8
|
+
const client_key_1 = require("../../../lib/client-key");
|
|
9
|
+
const safe_error_response_1 = require("../../../lib/safe-error-response");
|
|
10
|
+
/**
|
|
11
|
+
* Account recovery - the ONE forwarder behind /recovery/send-code and /recovery/verify-code (PAY-1588).
|
|
12
|
+
*
|
|
13
|
+
* THE DEFECT (QAPert, W1 walk on :4797 -> dev-93 IdP 90f766bd0, 2026-09-19): portal /account-auth/recovery,
|
|
14
|
+
* initiate -> 200, send-code -> 401 "Upstream error". The browser sends `Authorization: Bearer <recovery session
|
|
15
|
+
* token>` (the token `initiate` minted); these handlers went through idpFetchJSON, which sets Authorization ONLY from
|
|
16
|
+
* the server-side SESSION and never forwards the inbound header, so the IdP got no Bearer at all
|
|
17
|
+
* (AccountController.cs:872-885 = 401 MISSING_TOKEN). Positive control direct to the IdP: without Authorization 401,
|
|
18
|
+
* with it 200 and the email is sent.
|
|
19
|
+
*
|
|
20
|
+
* WHY THE 2026-08-02 REWRITE GOT IT WRONG. It reasoned "browsers send a session COOKIE and no Authorization header"
|
|
21
|
+
* and moved both handlers onto idpFetchJSON. That is true of every OTHER authenticated route. It is false of this
|
|
22
|
+
* flow: the caller is LOCKED OUT, so there is no session to resolve - the recovery session token IS the credential,
|
|
23
|
+
* and the browser is the only party that holds it. Resolving a token from a session that does not exist is why the
|
|
24
|
+
* entire recovery path, both halves, has been dead: it fails exactly when it is the only thing left that could help.
|
|
25
|
+
*
|
|
26
|
+
* WHAT THIS DOES INSTEAD
|
|
27
|
+
* - Forwards the inbound `Authorization: Bearer <token>` to the IdP and nothing else from the caller's headers.
|
|
28
|
+
* - Uses a direct fetch, NOT idpFetchJSON: a stray session belonging to someone else on the same browser must not
|
|
29
|
+
* be able to override the recovery credential with its own bearer (idpFetchJSON's session bearer wins over any
|
|
30
|
+
* caller-supplied Authorization).
|
|
31
|
+
* - FAILS LOUD when there is no usable bearer: 401 `missing_recovery_token`, without calling upstream. The old
|
|
32
|
+
* behaviour returned the IdP's opaque rejection, so the user saw recovery "not working" and the log showed an
|
|
33
|
+
* upstream error rather than the auth fault it was.
|
|
34
|
+
* - Shape-checks the token (a bearer scheme, one opaque token, sane length) before forwarding, so the header cannot
|
|
35
|
+
* be used to smuggle anything but a credential to the IdP. It is never logged.
|
|
36
|
+
* - Anonymous route (PAY-1438): on failure only the whitelisted envelope, never the IdP body.
|
|
37
|
+
*
|
|
38
|
+
* SERVER VALUE WINS for client_id (cards 268012 / 268410): applied AFTER the caller's payload spread, because these are
|
|
39
|
+
* unauthenticated endpoints and a browser-supplied client_id would be a tenant-targeting lever on a password-reset path.
|
|
40
|
+
*
|
|
41
|
+
* @package @payez/next-mvp
|
|
42
|
+
*/
|
|
43
|
+
/** One opaque token: JWT / base64url / opaque-hex alphabet, no whitespace or control characters, bounded length. */
|
|
44
|
+
const TOKEN_SHAPE = /^[A-Za-z0-9\-._~+/]+=*$/;
|
|
45
|
+
/** The recovery bearer from the inbound request, or null when absent / malformed. Never throws. */
|
|
46
|
+
function inboundRecoveryBearer(req) {
|
|
47
|
+
const raw = req.headers.get('authorization');
|
|
48
|
+
if (!raw)
|
|
49
|
+
return null;
|
|
50
|
+
const m = /^Bearer[ ]+(\S+)$/i.exec(raw.trim());
|
|
51
|
+
if (!m)
|
|
52
|
+
return null;
|
|
53
|
+
const token = m[1];
|
|
54
|
+
if (token.length < 16 || token.length > 4096)
|
|
55
|
+
return null;
|
|
56
|
+
return TOKEN_SHAPE.test(token) ? token : null;
|
|
57
|
+
}
|
|
58
|
+
async function recoveryClientKey(where) {
|
|
59
|
+
try {
|
|
60
|
+
const config = await (0, idp_client_config_1.getIDPClientConfig)();
|
|
61
|
+
return (0, client_key_1.resolveClientKey)(config, where).key;
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
console.error(`[${where}] could not resolve IDP client config for client_id:`, e);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function forwardRecoveryCall(req, spec) {
|
|
69
|
+
let payload = {};
|
|
70
|
+
try {
|
|
71
|
+
payload = await req.json();
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return server_1.NextResponse.json({ success: false, error: 'invalid_request', message: 'A JSON body is required' }, { status: 400 });
|
|
75
|
+
}
|
|
76
|
+
const bearer = inboundRecoveryBearer(req);
|
|
77
|
+
if (!bearer) {
|
|
78
|
+
return server_1.NextResponse.json({
|
|
79
|
+
success: false,
|
|
80
|
+
error: 'missing_recovery_token',
|
|
81
|
+
message: 'Your recovery session could not be found. Start the recovery again from the beginning.',
|
|
82
|
+
}, { status: 401 });
|
|
83
|
+
}
|
|
84
|
+
const clientKey = await recoveryClientKey(spec.where);
|
|
85
|
+
let upstream;
|
|
86
|
+
try {
|
|
87
|
+
upstream = await fetch(`${env_1.ENV_CONFIG.IDP_URL}${spec.upstreamPath}`, {
|
|
88
|
+
method: 'POST',
|
|
89
|
+
headers: {
|
|
90
|
+
'Content-Type': 'application/json',
|
|
91
|
+
Accept: 'application/json',
|
|
92
|
+
Authorization: `Bearer ${bearer}`,
|
|
93
|
+
...(clientKey ? { 'X-Client-Id': clientKey } : {}),
|
|
94
|
+
},
|
|
95
|
+
body: JSON.stringify({ ...payload, ...(clientKey ? { client_id: clientKey } : {}) }),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return server_1.NextResponse.json({ success: false, error: spec.failureCode }, { status: 502 });
|
|
100
|
+
}
|
|
101
|
+
const data = await upstream.json().catch(() => ({}));
|
|
102
|
+
if (!upstream.ok)
|
|
103
|
+
return server_1.NextResponse.json((0, safe_error_response_1.idpErrorEnvelope)(data, spec.fallbackMessage), { status: upstream.status });
|
|
104
|
+
return server_1.NextResponse.json(data, { status: 200 });
|
|
105
|
+
}
|
|
@@ -4,6 +4,7 @@ exports.POST = POST;
|
|
|
4
4
|
const server_1 = require("next/server");
|
|
5
5
|
const idp_client_config_1 = require("../../../lib/idp-client-config");
|
|
6
6
|
const client_key_1 = require("../../../lib/client-key");
|
|
7
|
+
const safe_error_response_1 = require("../../../lib/safe-error-response");
|
|
7
8
|
function getIdpUrl() {
|
|
8
9
|
const url = process.env.IDP_URL;
|
|
9
10
|
if (!url)
|
|
@@ -51,6 +52,9 @@ async function POST(req) {
|
|
|
51
52
|
body: JSON.stringify({ email, ...(clientKey ? { client_id: clientKey } : {}) }),
|
|
52
53
|
});
|
|
53
54
|
const data = await upstream.json().catch(() => ({}));
|
|
55
|
+
// PAY-1438: anonymous route - on failure only the whitelisted envelope, never the IDP body.
|
|
56
|
+
if (!upstream.ok)
|
|
57
|
+
return server_1.NextResponse.json((0, safe_error_response_1.idpErrorEnvelope)(data, 'Failed to initiate recovery'), { status: upstream.status });
|
|
54
58
|
return server_1.NextResponse.json(data, { status: upstream.status });
|
|
55
59
|
}
|
|
56
60
|
catch (e) {
|
|
@@ -1,2 +1,12 @@
|
|
|
1
|
-
import { NextRequest
|
|
2
|
-
|
|
1
|
+
import { NextRequest } from 'next/server';
|
|
2
|
+
/**
|
|
3
|
+
* Account recovery - send-code.
|
|
4
|
+
*
|
|
5
|
+
* PAY-1588: the recovery session token minted by `initiate` is the caller's only credential (they are locked out; there is
|
|
6
|
+
* no session), so this forwards the inbound `Authorization: Bearer <token>` to the IdP and fails loud (401
|
|
7
|
+
* `missing_recovery_token`) when it is absent. The whole reasoning, and why the 2026-08-02 idpFetchJSON rewrite killed
|
|
8
|
+
* this flow, is in ./forward.ts. send-code and verify-code share that ONE forwarder so they cannot drift apart again.
|
|
9
|
+
*
|
|
10
|
+
* @package @payez/next-mvp
|
|
11
|
+
*/
|
|
12
|
+
export declare function POST(req: NextRequest): Promise<import("next/server").NextResponse<unknown>>;
|
|
@@ -1,95 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.POST = POST;
|
|
4
|
-
const
|
|
5
|
-
const idp_fetch_1 = require("../../../lib/idp-fetch");
|
|
6
|
-
const env_1 = require("../../../config/env");
|
|
7
|
-
const idp_client_config_1 = require("../../../lib/idp-client-config");
|
|
8
|
-
const client_key_1 = require("../../../lib/client-key");
|
|
4
|
+
const forward_1 = require("./forward");
|
|
9
5
|
/**
|
|
10
|
-
* Account recovery
|
|
6
|
+
* Account recovery - send-code.
|
|
11
7
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* const token = auth.startsWith('Bearer ') ? auth.slice(7) : '';
|
|
17
|
-
* ... headers: { 'Authorization': `Bearer ${token}` }
|
|
18
|
-
*
|
|
19
|
-
* Browsers send a session COOKIE and no Authorization header, so `token` was
|
|
20
|
-
* ALWAYS the empty string and this always called the IDP with a literal
|
|
21
|
-
* "Bearer " and nothing after it.
|
|
22
|
-
*
|
|
23
|
-
* THAT IS WORSE THAN THE profile.ts VERSION OF THIS BUG, in two ways:
|
|
24
|
-
*
|
|
25
|
-
* 1. profile.ts at least 401'd when the header was missing. This one has no
|
|
26
|
-
* check at all — `|| ''` swallows the absence — so it proceeds to call
|
|
27
|
-
* upstream with an empty credential and returns the IDP's rejection as an
|
|
28
|
-
* opaque failure. The user sees recovery "not working", not "not
|
|
29
|
-
* authenticated", and the log shows an upstream error rather than an auth
|
|
30
|
-
* one. The defect hides itself.
|
|
31
|
-
* 2. It is BOTH HALVES of account recovery — send-code and verify-code — so
|
|
32
|
-
* the entire recovery path is unreachable, not merely degraded. This is
|
|
33
|
-
* the flow a locked-out user reaches for, i.e. it fails exactly when it is
|
|
34
|
-
* the only thing left that could help them.
|
|
35
|
-
*
|
|
36
|
-
* Fixed the same way as profile.ts and masked-info.ts: idpFetchJSON resolves
|
|
37
|
-
* the caller's token from the server-side session, so the cookie works and the
|
|
38
|
-
* token is never handed to browser JavaScript.
|
|
8
|
+
* PAY-1588: the recovery session token minted by `initiate` is the caller's only credential (they are locked out; there is
|
|
9
|
+
* no session), so this forwards the inbound `Authorization: Bearer <token>` to the IdP and fails loud (401
|
|
10
|
+
* `missing_recovery_token`) when it is absent. The whole reasoning, and why the 2026-08-02 idpFetchJSON rewrite killed
|
|
11
|
+
* this flow, is in ./forward.ts. send-code and verify-code share that ONE forwarder so they cannot drift apart again.
|
|
39
12
|
*
|
|
40
13
|
* @package @payez/next-mvp
|
|
41
14
|
*/
|
|
42
|
-
/**
|
|
43
|
-
* RECOVERY CLIENT KEY — cards 268012 (S12) and 268410.
|
|
44
|
-
*
|
|
45
|
-
* These four proxies populated NONE of PayEzClientIdResolver's six slots, so
|
|
46
|
-
* RequirePayEzUnAuthorized 404'd the request BEFORE the action ran and no
|
|
47
|
-
* recovery token was ever minted. That is why S12 failed at step 1.
|
|
48
|
-
*
|
|
49
|
-
* NOT `ENV_CONFIG.CLIENT_ID`, which is what card 268012 originally prescribed.
|
|
50
|
-
* That variable is the ENVIRONMENT-SPECIFIC NUMERIC id, and the IDP accepts a
|
|
51
|
-
* numeric with NO existence check (RequirePayEzUnAuthorizedAttribute.cs:31-33)
|
|
52
|
-
* while it VALIDATES a slug (:41-47). Hardcoding the numeric here would have
|
|
53
|
-
* added four more sites to the 268410 family and made a stale id answer for
|
|
54
|
-
* the WRONG TENANT on a password-reset path, silently. Slug-preferring, with
|
|
55
|
-
* the numeric fallback logged, is the same resolver the other nine sites use.
|
|
56
|
-
*
|
|
57
|
-
* SERVER VALUE WINS: client_id is applied AFTER the caller's payload spread.
|
|
58
|
-
* These are unauthenticated recovery endpoints, so a browser-supplied
|
|
59
|
-
* client_id would be a tenant-targeting lever. The caller does not get to
|
|
60
|
-
* choose which tenant their password reset is addressed to.
|
|
61
|
-
*/
|
|
62
|
-
async function recoveryClientKey() {
|
|
63
|
-
try {
|
|
64
|
-
const config = await (0, idp_client_config_1.getIDPClientConfig)();
|
|
65
|
-
return (0, client_key_1.resolveClientKey)(config, 'account/recovery/send-code').key;
|
|
66
|
-
}
|
|
67
|
-
catch (e) {
|
|
68
|
-
console.error('[account/recovery/send-code] could not resolve IDP client config for client_id:', e);
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
15
|
async function POST(req) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return server_1.NextResponse.json({ success: false, error: 'invalid_request', message: 'A JSON body is required' }, { status: 400 });
|
|
79
|
-
}
|
|
80
|
-
const clientKey = await recoveryClientKey();
|
|
81
|
-
const result = await (0, idp_fetch_1.idpFetchJSON)(req, `${env_1.ENV_CONFIG.IDP_URL}/api/Account/recovery/send-code`, {
|
|
82
|
-
method: 'POST',
|
|
83
|
-
headers: { 'Content-Type': 'application/json', ...(clientKey ? { 'X-Client-Id': clientKey } : {}) },
|
|
84
|
-
body: JSON.stringify({ ...payload, ...(clientKey ? { client_id: clientKey } : {}) }),
|
|
16
|
+
return (0, forward_1.forwardRecoveryCall)(req, {
|
|
17
|
+
upstreamPath: '/api/Account/recovery/send-code',
|
|
18
|
+
where: 'account/recovery/send-code',
|
|
19
|
+
failureCode: 'send_code_failed',
|
|
20
|
+
fallbackMessage: 'Failed to send the verification code',
|
|
85
21
|
});
|
|
86
|
-
if (!result.ok) {
|
|
87
|
-
return server_1.NextResponse.json({
|
|
88
|
-
success: false,
|
|
89
|
-
error: 'send_code_failed',
|
|
90
|
-
message: result.json?.message || 'Upstream error',
|
|
91
|
-
meta: { status: result.status, attemptedRefresh: result.attemptedRefresh },
|
|
92
|
-
}, { status: result.status });
|
|
93
|
-
}
|
|
94
|
-
return server_1.NextResponse.json(result.json, { status: 200 });
|
|
95
22
|
}
|
|
@@ -1,2 +1,11 @@
|
|
|
1
|
-
import { NextRequest
|
|
2
|
-
|
|
1
|
+
import { NextRequest } from 'next/server';
|
|
2
|
+
/**
|
|
3
|
+
* Account recovery - verify-code.
|
|
4
|
+
*
|
|
5
|
+
* PAY-1588: forwards the inbound recovery `Authorization: Bearer <token>` to the IdP and fails loud (401
|
|
6
|
+
* `missing_recovery_token`) when it is absent. See ./forward.ts for the full reasoning; send-code and verify-code share
|
|
7
|
+
* that ONE forwarder so they cannot drift apart again.
|
|
8
|
+
*
|
|
9
|
+
* @package @payez/next-mvp
|
|
10
|
+
*/
|
|
11
|
+
export declare function POST(req: NextRequest): Promise<import("next/server").NextResponse<unknown>>;
|
|
@@ -1,95 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.POST = POST;
|
|
4
|
-
const
|
|
5
|
-
const idp_fetch_1 = require("../../../lib/idp-fetch");
|
|
6
|
-
const env_1 = require("../../../config/env");
|
|
7
|
-
const idp_client_config_1 = require("../../../lib/idp-client-config");
|
|
8
|
-
const client_key_1 = require("../../../lib/client-key");
|
|
4
|
+
const forward_1 = require("./forward");
|
|
9
5
|
/**
|
|
10
|
-
* Account recovery
|
|
6
|
+
* Account recovery - verify-code.
|
|
11
7
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* const auth = req.headers.get('authorization') || '';
|
|
16
|
-
* const token = auth.startsWith('Bearer ') ? auth.slice(7) : '';
|
|
17
|
-
* ... headers: { 'Authorization': `Bearer ${token}` }
|
|
18
|
-
*
|
|
19
|
-
* Browsers send a session COOKIE and no Authorization header, so `token` was
|
|
20
|
-
* ALWAYS the empty string and this always called the IDP with a literal
|
|
21
|
-
* "Bearer " and nothing after it.
|
|
22
|
-
*
|
|
23
|
-
* THAT IS WORSE THAN THE profile.ts VERSION OF THIS BUG, in two ways:
|
|
24
|
-
*
|
|
25
|
-
* 1. profile.ts at least 401'd when the header was missing. This one has no
|
|
26
|
-
* check at all — `|| ''` swallows the absence — so it proceeds to call
|
|
27
|
-
* upstream with an empty credential and returns the IDP's rejection as an
|
|
28
|
-
* opaque failure. The user sees recovery "not working", not "not
|
|
29
|
-
* authenticated", and the log shows an upstream error rather than an auth
|
|
30
|
-
* one. The defect hides itself.
|
|
31
|
-
* 2. It is BOTH HALVES of account recovery — send-code and verify-code — so
|
|
32
|
-
* the entire recovery path is unreachable, not merely degraded. This is
|
|
33
|
-
* the flow a locked-out user reaches for, i.e. it fails exactly when it is
|
|
34
|
-
* the only thing left that could help them.
|
|
35
|
-
*
|
|
36
|
-
* Fixed the same way as profile.ts and masked-info.ts: idpFetchJSON resolves
|
|
37
|
-
* the caller's token from the server-side session, so the cookie works and the
|
|
38
|
-
* token is never handed to browser JavaScript.
|
|
8
|
+
* PAY-1588: forwards the inbound recovery `Authorization: Bearer <token>` to the IdP and fails loud (401
|
|
9
|
+
* `missing_recovery_token`) when it is absent. See ./forward.ts for the full reasoning; send-code and verify-code share
|
|
10
|
+
* that ONE forwarder so they cannot drift apart again.
|
|
39
11
|
*
|
|
40
12
|
* @package @payez/next-mvp
|
|
41
13
|
*/
|
|
42
|
-
/**
|
|
43
|
-
* RECOVERY CLIENT KEY — cards 268012 (S12) and 268410.
|
|
44
|
-
*
|
|
45
|
-
* These four proxies populated NONE of PayEzClientIdResolver's six slots, so
|
|
46
|
-
* RequirePayEzUnAuthorized 404'd the request BEFORE the action ran and no
|
|
47
|
-
* recovery token was ever minted. That is why S12 failed at step 1.
|
|
48
|
-
*
|
|
49
|
-
* NOT `ENV_CONFIG.CLIENT_ID`, which is what card 268012 originally prescribed.
|
|
50
|
-
* That variable is the ENVIRONMENT-SPECIFIC NUMERIC id, and the IDP accepts a
|
|
51
|
-
* numeric with NO existence check (RequirePayEzUnAuthorizedAttribute.cs:31-33)
|
|
52
|
-
* while it VALIDATES a slug (:41-47). Hardcoding the numeric here would have
|
|
53
|
-
* added four more sites to the 268410 family and made a stale id answer for
|
|
54
|
-
* the WRONG TENANT on a password-reset path, silently. Slug-preferring, with
|
|
55
|
-
* the numeric fallback logged, is the same resolver the other nine sites use.
|
|
56
|
-
*
|
|
57
|
-
* SERVER VALUE WINS: client_id is applied AFTER the caller's payload spread.
|
|
58
|
-
* These are unauthenticated recovery endpoints, so a browser-supplied
|
|
59
|
-
* client_id would be a tenant-targeting lever. The caller does not get to
|
|
60
|
-
* choose which tenant their password reset is addressed to.
|
|
61
|
-
*/
|
|
62
|
-
async function recoveryClientKey() {
|
|
63
|
-
try {
|
|
64
|
-
const config = await (0, idp_client_config_1.getIDPClientConfig)();
|
|
65
|
-
return (0, client_key_1.resolveClientKey)(config, 'account/recovery/verify-code').key;
|
|
66
|
-
}
|
|
67
|
-
catch (e) {
|
|
68
|
-
console.error('[account/recovery/verify-code] could not resolve IDP client config for client_id:', e);
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
14
|
async function POST(req) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return server_1.NextResponse.json({ success: false, error: 'invalid_request', message: 'A JSON body is required' }, { status: 400 });
|
|
79
|
-
}
|
|
80
|
-
const clientKey = await recoveryClientKey();
|
|
81
|
-
const result = await (0, idp_fetch_1.idpFetchJSON)(req, `${env_1.ENV_CONFIG.IDP_URL}/api/Account/recovery/verify-code`, {
|
|
82
|
-
method: 'POST',
|
|
83
|
-
headers: { 'Content-Type': 'application/json', ...(clientKey ? { 'X-Client-Id': clientKey } : {}) },
|
|
84
|
-
body: JSON.stringify({ ...payload, ...(clientKey ? { client_id: clientKey } : {}) }),
|
|
15
|
+
return (0, forward_1.forwardRecoveryCall)(req, {
|
|
16
|
+
upstreamPath: '/api/Account/recovery/verify-code',
|
|
17
|
+
where: 'account/recovery/verify-code',
|
|
18
|
+
failureCode: 'verify_code_failed',
|
|
19
|
+
fallbackMessage: 'Failed to verify the code',
|
|
85
20
|
});
|
|
86
|
-
if (!result.ok) {
|
|
87
|
-
return server_1.NextResponse.json({
|
|
88
|
-
success: false,
|
|
89
|
-
error: 'verify_code_failed',
|
|
90
|
-
message: result.json?.message || 'Upstream error',
|
|
91
|
-
meta: { status: result.status, attemptedRefresh: result.attemptedRefresh },
|
|
92
|
-
}, { status: result.status });
|
|
93
|
-
}
|
|
94
|
-
return server_1.NextResponse.json(result.json, { status: 200 });
|
|
95
21
|
}
|
|
@@ -4,6 +4,7 @@ exports.POST = POST;
|
|
|
4
4
|
const server_1 = require("next/server");
|
|
5
5
|
const idp_client_config_1 = require("../../lib/idp-client-config");
|
|
6
6
|
const client_key_1 = require("../../lib/client-key");
|
|
7
|
+
const safe_error_response_1 = require("../../lib/safe-error-response");
|
|
7
8
|
function getIdpUrl() {
|
|
8
9
|
const url = process.env.IDP_URL;
|
|
9
10
|
if (!url)
|
|
@@ -43,7 +44,23 @@ async function recoveryClientKey() {
|
|
|
43
44
|
async function POST(req) {
|
|
44
45
|
const IDP_URL = getIdpUrl();
|
|
45
46
|
try {
|
|
46
|
-
const
|
|
47
|
+
const raw = await req.json().catch(() => null);
|
|
48
|
+
const body = (raw && typeof raw === 'object' ? raw : {});
|
|
49
|
+
// PAY-1588: the IdP's ResetPasswordRequest binds exactly email/token/new_password/confirm_password. The client
|
|
50
|
+
// used to send password_reset_token / password / password_confirmation, which bind to nothing (400 at the IdP).
|
|
51
|
+
// Legacy names are accepted as aliases and mapped; only the four IdP fields are forwarded (whitelist).
|
|
52
|
+
const str = (v) => (typeof v === 'string' && v.length > 0 ? v : undefined);
|
|
53
|
+
const fields = {
|
|
54
|
+
email: str(body.email),
|
|
55
|
+
token: str(body.token) ?? str(body.password_reset_token),
|
|
56
|
+
new_password: str(body.new_password) ?? str(body.password),
|
|
57
|
+
confirm_password: str(body.confirm_password) ?? str(body.password_confirmation),
|
|
58
|
+
};
|
|
59
|
+
const missing = Object.entries(fields).filter(([, v]) => v === undefined).map(([k]) => k);
|
|
60
|
+
if (missing.length) {
|
|
61
|
+
return server_1.NextResponse.json({ success: false, error: 'invalid_request', message: 'Missing required fields: ' + missing.join(', ') }, { status: 400 });
|
|
62
|
+
}
|
|
63
|
+
const payload = fields;
|
|
47
64
|
const clientKey = await recoveryClientKey();
|
|
48
65
|
const upstream = await fetch(`${IDP_URL}/api/Account/reset-password`, {
|
|
49
66
|
method: 'POST',
|
|
@@ -51,6 +68,12 @@ async function POST(req) {
|
|
|
51
68
|
body: JSON.stringify({ ...payload, ...(clientKey ? { client_id: clientKey } : {}) }),
|
|
52
69
|
});
|
|
53
70
|
const data = await upstream.json().catch(() => ({}));
|
|
71
|
+
// PAY-1438: anonymous route - on failure only the whitelisted envelope (utils/api reads `message`).
|
|
72
|
+
if (!upstream.ok) {
|
|
73
|
+
// PAY-1591: a password-policy refusal also carries the KNOWN rule codes so the page can say which rules failed.
|
|
74
|
+
const rules = (0, safe_error_response_1.projectPasswordPolicyRules)(data);
|
|
75
|
+
return server_1.NextResponse.json({ ...(0, safe_error_response_1.idpErrorEnvelope)(data, 'Failed to reset password'), ...(rules !== undefined ? { password_policy: { rules } } : {}), ...((0, safe_error_response_1.projectResetTokenInvalid)(data) ? { reset_token_invalid: true } : {}) }, { status: upstream.status });
|
|
76
|
+
}
|
|
54
77
|
return server_1.NextResponse.json(data, { status: upstream.status });
|
|
55
78
|
}
|
|
56
79
|
catch {
|
|
@@ -7,18 +7,4 @@
|
|
|
7
7
|
* @package @payez/next-mvp
|
|
8
8
|
*/
|
|
9
9
|
import { NextRequest, NextResponse } from 'next/server';
|
|
10
|
-
export declare function POST(req: NextRequest): Promise<NextResponse<
|
|
11
|
-
success: boolean;
|
|
12
|
-
error: string;
|
|
13
|
-
code: string;
|
|
14
|
-
}> | NextResponse<{
|
|
15
|
-
success: boolean;
|
|
16
|
-
error: any;
|
|
17
|
-
code: any;
|
|
18
|
-
meta: {
|
|
19
|
-
attemptedRefresh: boolean;
|
|
20
|
-
};
|
|
21
|
-
}> | NextResponse<{
|
|
22
|
-
success: boolean;
|
|
23
|
-
message: string;
|
|
24
|
-
}>>;
|
|
10
|
+
export declare function POST(req: NextRequest): Promise<NextResponse<unknown>>;
|
|
@@ -12,6 +12,7 @@ exports.POST = POST;
|
|
|
12
12
|
const server_1 = require("next/server");
|
|
13
13
|
const idp_fetch_1 = require("../../lib/idp-fetch");
|
|
14
14
|
const env_1 = require("../../config/env");
|
|
15
|
+
const two_factor_verify_failure_1 = require("../../lib/two-factor-verify-failure");
|
|
15
16
|
async function POST(req) {
|
|
16
17
|
try {
|
|
17
18
|
// Parse request body
|
|
@@ -37,12 +38,8 @@ async function POST(req) {
|
|
|
37
38
|
body: idpBody,
|
|
38
39
|
});
|
|
39
40
|
if (!result.ok) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
error: result.json?.message || `Failed to send ${method} code`,
|
|
43
|
-
code: result.json?.code || 'IDP_ERROR',
|
|
44
|
-
meta: { attemptedRefresh: result.attemptedRefresh },
|
|
45
|
-
}, { status: result.status });
|
|
41
|
+
// PAY-1443: never relay the IDP's message/code - fixed sentence + stable code, status kept.
|
|
42
|
+
return (0, two_factor_verify_failure_1.twoFactorSendCodeFailure)(result, method);
|
|
46
43
|
}
|
|
47
44
|
return server_1.NextResponse.json({
|
|
48
45
|
success: true,
|
|
@@ -32,7 +32,12 @@ async function POST(req) {
|
|
|
32
32
|
body: JSON.stringify({ phone_number: phoneNumber }),
|
|
33
33
|
});
|
|
34
34
|
if (!result.ok) {
|
|
35
|
-
|
|
35
|
+
// PAY-1424: shape only, never the IDP body (profile endpoint - PII).
|
|
36
|
+
console.error('[UPDATE_PHONE] IDP error:', {
|
|
37
|
+
status: result.status,
|
|
38
|
+
bodyKeys: result.json && typeof result.json === 'object' ? Object.keys(result.json) : undefined,
|
|
39
|
+
errorKeys: result.json?.error && typeof result.json?.error === 'object' ? Object.keys(result.json?.error) : undefined,
|
|
40
|
+
});
|
|
36
41
|
return server_1.NextResponse.json({
|
|
37
42
|
success: false,
|
|
38
43
|
error: {
|
|
@@ -48,9 +48,11 @@ async function POST(req) {
|
|
|
48
48
|
});
|
|
49
49
|
const responseData = await idpResponse.json().catch(() => ({}));
|
|
50
50
|
if (!idpResponse.ok) {
|
|
51
|
+
// PAY-1424: shape only, never the envelope contents (this endpoint carries a password).
|
|
51
52
|
console.error('[VALIDATE_PASSWORD] IDP error:', {
|
|
52
53
|
status: idpResponse.status,
|
|
53
|
-
|
|
54
|
+
bodyKeys: responseData && typeof responseData === 'object' ? Object.keys(responseData) : undefined,
|
|
55
|
+
errorKeys: responseData?.error && typeof responseData?.error === 'object' ? Object.keys(responseData?.error) : undefined,
|
|
54
56
|
});
|
|
55
57
|
return server_1.NextResponse.json({
|
|
56
58
|
is_valid: false,
|
|
@@ -7,20 +7,4 @@
|
|
|
7
7
|
* @package @payez/next-mvp
|
|
8
8
|
*/
|
|
9
9
|
import { NextRequest, NextResponse } from 'next/server';
|
|
10
|
-
export declare function POST(req: NextRequest): Promise<NextResponse<
|
|
11
|
-
success: boolean;
|
|
12
|
-
error: string;
|
|
13
|
-
code: string;
|
|
14
|
-
}> | NextResponse<{
|
|
15
|
-
success: boolean;
|
|
16
|
-
error: any;
|
|
17
|
-
code: any;
|
|
18
|
-
meta: {
|
|
19
|
-
attemptedRefresh: boolean;
|
|
20
|
-
};
|
|
21
|
-
}> | NextResponse<{
|
|
22
|
-
success: boolean;
|
|
23
|
-
verificationSuccessful: boolean;
|
|
24
|
-
twoFactorSessionVerified: boolean;
|
|
25
|
-
message: any;
|
|
26
|
-
}>>;
|
|
10
|
+
export declare function POST(req: NextRequest): Promise<NextResponse<unknown>>;
|