@payez/next-mvp 4.11.0 → 4.12.0
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 +13 -0
- package/dist/api-handlers/account/recovery/forward.js +2 -1
- package/dist/api-handlers/account/recovery/initiate.js +2 -1
- package/dist/api-handlers/account/reset-password.js +2 -1
- package/dist/api-handlers/account/send-code.js +2 -1
- package/dist/api-handlers/account/update-phone.js +2 -1
- package/dist/api-handlers/account/validate-password.js +2 -1
- package/dist/api-handlers/account/verify-email.js +2 -1
- package/dist/api-handlers/account/verify-sms.js +2 -1
- package/dist/api-handlers/anon/preferences.js +3 -2
- package/dist/api-handlers/auth/credential-login-email-code.js +3 -2
- package/dist/api-handlers/auth/credential-login.js +6 -5
- package/dist/api-handlers/auth/refresh.js +2 -1
- package/dist/api-handlers/auth/signout.js +43 -6
- package/dist/api-handlers/mfa/backup-code-verify.js +2 -1
- package/dist/api-handlers/mfa/totp-verify.js +2 -1
- package/dist/api-handlers/session/refresh-viability.js +3 -2
- package/dist/api-handlers/session/viability.js +2 -1
- package/dist/auth/better-auth.js +7 -6
- package/dist/auth/idp-oauth.js +4 -2
- package/dist/auth/utils/idp-client.js +5 -4
- package/dist/auth/utils/token-utils.js +4 -3
- package/dist/config/logger.js +2 -1
- package/dist/config/logging-config.js +4 -3
- package/dist/config/vibe-log-transport.js +2 -1
- package/dist/lib/ensure-fresh-access-token.js +2 -1
- package/dist/lib/geolocation.js +5 -22
- package/dist/lib/idp-client-config.js +4 -3
- package/dist/lib/idp-fetch.d.ts +11 -0
- package/dist/lib/idp-fetch.js +3 -1
- package/dist/lib/idp-logout.d.ts +10 -0
- package/dist/lib/idp-logout.js +73 -1
- package/dist/lib/jwt-decode-client.js +2 -1
- package/dist/lib/jwt-decode.js +2 -1
- package/dist/lib/log-safe.d.ts +19 -0
- package/dist/lib/log-safe.js +32 -0
- package/dist/lib/redis.js +2 -1
- package/dist/lib/safe-error-response.d.ts +5 -1
- package/dist/lib/safe-error-response.js +7 -2
- package/dist/lib/session-store.js +2 -1
- package/dist/lib/site-logger.d.ts +2 -8
- package/dist/lib/site-logger.js +11 -5
- package/dist/lib/standardized-client-api.js +3 -2
- package/dist/lib/token-lifecycle.js +3 -2
- package/dist/middleware/create-middleware.js +2 -1
- package/dist/middleware/rbac-check.d.ts +0 -13
- package/dist/middleware/rbac-check.js +6 -5
- package/dist/routes/auth/logout.js +6 -2
- package/dist/routes/auth/settings.js +2 -1
- package/dist/server/auth-guard.js +4 -3
- package/dist/server/auth.js +5 -9
- package/dist/server/decode-session.js +3 -2
- package/dist/server/with-auth.js +2 -1
- package/package.json +1 -1
- package/src/api-handlers/account/recovery/forward.ts +2 -1
- package/src/api-handlers/account/recovery/initiate.ts +2 -1
- package/src/api-handlers/account/reset-password.ts +2 -1
- package/src/api-handlers/account/send-code.ts +2 -1
- package/src/api-handlers/account/update-phone.ts +2 -1
- package/src/api-handlers/account/validate-password.ts +2 -1
- package/src/api-handlers/account/verify-email.ts +2 -1
- package/src/api-handlers/account/verify-sms.ts +2 -1
- package/src/api-handlers/anon/preferences.ts +3 -2
- package/src/api-handlers/auth/credential-login-email-code.ts +3 -2
- package/src/api-handlers/auth/credential-login.ts +6 -5
- package/src/api-handlers/auth/refresh.ts +2 -1
- package/src/api-handlers/auth/signout.ts +54 -7
- package/src/api-handlers/mfa/backup-code-verify.ts +2 -1
- package/src/api-handlers/mfa/totp-verify.ts +2 -1
- package/src/api-handlers/session/refresh-viability.ts +3 -2
- package/src/api-handlers/session/viability.ts +2 -1
- package/src/auth/better-auth.ts +7 -6
- package/src/auth/idp-oauth.ts +7 -2
- package/src/auth/utils/idp-client.ts +5 -4
- package/src/auth/utils/token-utils.ts +4 -3
- package/src/config/logger.ts +2 -1
- package/src/config/logging-config.ts +4 -3
- package/src/config/vibe-log-transport.ts +2 -1
- package/src/lib/ensure-fresh-access-token.ts +2 -1
- package/src/lib/geolocation.ts +5 -4
- package/src/lib/idp-client-config.ts +4 -3
- package/src/lib/idp-fetch.ts +11 -1
- package/src/lib/idp-logout.ts +36 -1
- package/src/lib/jwt-decode-client.ts +3 -1
- package/src/lib/jwt-decode.ts +2 -1
- package/src/lib/log-safe.ts +30 -0
- package/src/lib/redis.ts +2 -1
- package/src/lib/safe-error-response.ts +7 -2
- package/src/lib/session-store.ts +2 -1
- package/src/lib/site-logger.ts +13 -7
- package/src/lib/standardized-client-api.ts +3 -2
- package/src/lib/token-lifecycle.ts +3 -2
- package/src/middleware/create-middleware.ts +2 -1
- package/src/middleware/rbac-check.ts +2 -1
- package/src/routes/auth/logout.ts +9 -2
- package/src/routes/auth/settings.ts +2 -1
- package/src/server/auth-guard.ts +4 -3
- package/src/server/auth.ts +5 -10
- package/src/server/decode-session.ts +3 -2
- package/src/server/with-auth.ts +2 -1
|
@@ -31,6 +31,7 @@ const app_slug_1 = require("../lib/app-slug");
|
|
|
31
31
|
const rbac_check_1 = require("./rbac-check");
|
|
32
32
|
const session_cookie_clear_1 = require("../auth/session-cookie-clear");
|
|
33
33
|
const session_reset_link_1 = require("../auth/session-reset-link");
|
|
34
|
+
const log_safe_1 = require("../lib/log-safe");
|
|
34
35
|
// =============================================================================
|
|
35
36
|
// DEFAULT IMPLEMENTATIONS
|
|
36
37
|
// =============================================================================
|
|
@@ -748,7 +749,7 @@ async function handleAllow(request, pathname, sessionPointer, sessionStatus, rba
|
|
|
748
749
|
}
|
|
749
750
|
}
|
|
750
751
|
catch (error) {
|
|
751
|
-
console.error('[MIDDLEWARE] RBAC error:', error);
|
|
752
|
+
console.error('[MIDDLEWARE] RBAC error:', (0, log_safe_1.errForLog)(error));
|
|
752
753
|
// In development, fail open
|
|
753
754
|
if (process.env.NODE_ENV !== 'production') {
|
|
754
755
|
console.warn('[MIDDLEWARE] RBAC: Allowing in development despite error');
|
|
@@ -1,16 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Page RBAC Check Module
|
|
3
|
-
*
|
|
4
|
-
* Checks page-level permissions via Vibe API through the IDP Proxy.
|
|
5
|
-
* Uses in-memory cache to reduce API calls.
|
|
6
|
-
* Fails closed (DENY) on errors or timeout.
|
|
7
|
-
*
|
|
8
|
-
* All requests route through the IDP Vibe Proxy ({IDP_URL}/api/vibe/proxy)
|
|
9
|
-
* which injects proper HMAC credentials for the Vibe API.
|
|
10
|
-
*
|
|
11
|
-
* @version 2.0.0
|
|
12
|
-
* @since page-rbac-2026-01
|
|
13
|
-
*/
|
|
14
1
|
export interface RBACResult {
|
|
15
2
|
allowed: boolean;
|
|
16
3
|
requires_2fa?: boolean;
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.clearRBACCache = clearRBACCache;
|
|
4
|
+
exports.checkPagePermission = checkPagePermission;
|
|
5
|
+
exports.isRBACEnabled = isRBACEnabled;
|
|
6
|
+
const log_safe_1 = require("../lib/log-safe");
|
|
2
7
|
/**
|
|
3
8
|
* Page RBAC Check Module
|
|
4
9
|
*
|
|
@@ -12,10 +17,6 @@
|
|
|
12
17
|
* @version 2.0.0
|
|
13
18
|
* @since page-rbac-2026-01
|
|
14
19
|
*/
|
|
15
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
-
exports.clearRBACCache = clearRBACCache;
|
|
17
|
-
exports.checkPagePermission = checkPagePermission;
|
|
18
|
-
exports.isRBACEnabled = isRBACEnabled;
|
|
19
20
|
// ============================================================================
|
|
20
21
|
// WEB CRYPTO HELPERS (Edge Runtime compatible)
|
|
21
22
|
// ============================================================================
|
|
@@ -203,7 +204,7 @@ async function checkPagePermission(path, userRoles, clientId, userClaims) {
|
|
|
203
204
|
redirect: '/error?code=rbac_timeout',
|
|
204
205
|
};
|
|
205
206
|
}
|
|
206
|
-
console.error('[RBAC] Proxy error:', error);
|
|
207
|
+
console.error('[RBAC] Proxy error:', (0, log_safe_1.errForLog)(error));
|
|
207
208
|
return {
|
|
208
209
|
allowed: false,
|
|
209
210
|
reason: 'rbac_service_unavailable',
|
|
@@ -24,6 +24,7 @@ const idp_logout_1 = require("../../lib/idp-logout");
|
|
|
24
24
|
const app_slug_1 = require("../../lib/app-slug");
|
|
25
25
|
const site_logger_1 = require("../../lib/site-logger");
|
|
26
26
|
const safe_error_response_1 = require("../../lib/safe-error-response");
|
|
27
|
+
const log_safe_1 = require("../../lib/log-safe");
|
|
27
28
|
/**
|
|
28
29
|
* 4.8.9: answered (status 500) when a sign-out did not finish. 4.8.10 (BAPert 76297): TWO sentences, one per cause, each true for its
|
|
29
30
|
* cause, neither promising a retry (the local session is gone, so there is nothing to retry from). The browser's cookies and the local
|
|
@@ -66,7 +67,10 @@ async function POST(req) {
|
|
|
66
67
|
}
|
|
67
68
|
catch (error) {
|
|
68
69
|
idpRevokeFailed = true;
|
|
69
|
-
|
|
70
|
+
// "STILL LIVE" only when the IdP showed it (a forced refresh succeeded); otherwise say what is known: it was not confirmed.
|
|
71
|
+
console.error(error?.grantLive === true
|
|
72
|
+
? '[LOGOUT_ROUTE] IdP revoke failed - the IdP refresh token is STILL LIVE:'
|
|
73
|
+
: '[LOGOUT_ROUTE] IdP revoke could not be confirmed (the grant may already be dead):', (0, log_safe_1.errForLog)(error));
|
|
70
74
|
}
|
|
71
75
|
}
|
|
72
76
|
// Delete session from EVERY store — canonical + BA secondary (181922).
|
|
@@ -78,7 +82,7 @@ async function POST(req) {
|
|
|
78
82
|
}
|
|
79
83
|
catch (error) {
|
|
80
84
|
localDeleteFailed = true;
|
|
81
|
-
console.error('[LOGOUT_ROUTE] Failed to delete session - the server session is STILL LIVE:', error);
|
|
85
|
+
console.error('[LOGOUT_ROUTE] Failed to delete session - the server session is STILL LIVE:', (0, log_safe_1.errForLog)(error));
|
|
82
86
|
}
|
|
83
87
|
}
|
|
84
88
|
// Log logout event (fire-and-forget)
|
|
@@ -32,6 +32,7 @@ exports.GET = GET;
|
|
|
32
32
|
const server_1 = require("next/server");
|
|
33
33
|
const idp_client_config_1 = require("../../lib/idp-client-config");
|
|
34
34
|
const never_in_response_1 = require("../../lib/never-in-response");
|
|
35
|
+
const log_safe_1 = require("../../lib/log-safe");
|
|
35
36
|
/**
|
|
36
37
|
* Fail closed and LOUD. 503 + success:false + a code, and CRUCIALLY no policy
|
|
37
38
|
* fields at all - not defaults, not partials. A consumer cannot mistake an
|
|
@@ -53,7 +54,7 @@ async function GET() {
|
|
|
53
54
|
}
|
|
54
55
|
catch (error) {
|
|
55
56
|
// Keep the loud line - it is what finally unmasked this in production.
|
|
56
|
-
console.error('[AUTH_SETTINGS] Failed to get settings:', error);
|
|
57
|
+
console.error('[AUTH_SETTINGS] Failed to get settings:', (0, log_safe_1.errForLog)(error));
|
|
57
58
|
// The `degraded: true` shape that used to live here (240447) is gone
|
|
58
59
|
// deliberately. It still answered `success: true` with a full invented
|
|
59
60
|
// policy and merely LABELLED the guess - which BAPert v32 rules out:
|
|
@@ -14,6 +14,7 @@ const navigation_1 = require("next/navigation");
|
|
|
14
14
|
const headers_1 = require("next/headers");
|
|
15
15
|
const decode_session_1 = require("./decode-session");
|
|
16
16
|
const idp_client_config_1 = require("../lib/idp-client-config");
|
|
17
|
+
const log_safe_1 = require("../lib/log-safe");
|
|
17
18
|
// =============================================================================
|
|
18
19
|
// CONSTANTS
|
|
19
20
|
// =============================================================================
|
|
@@ -53,7 +54,7 @@ async function authGuard(options) {
|
|
|
53
54
|
}
|
|
54
55
|
catch (error) {
|
|
55
56
|
// Redis unreachable or startup failure → fail closed
|
|
56
|
-
console.error('[AUTH-GUARD] Session decode failed (service error):',
|
|
57
|
+
console.error('[AUTH-GUARD] Session decode failed (service error):', (0, log_safe_1.errForLog)(error));
|
|
57
58
|
(0, navigation_1.redirect)(serviceUnavailableUrl);
|
|
58
59
|
}
|
|
59
60
|
// No session at all → redirect to login
|
|
@@ -83,7 +84,7 @@ async function authGuard(options) {
|
|
|
83
84
|
}
|
|
84
85
|
catch (error) {
|
|
85
86
|
// If we can't check 2FA config, fail closed
|
|
86
|
-
console.error('[AUTH-GUARD] 2FA config check failed:',
|
|
87
|
+
console.error('[AUTH-GUARD] 2FA config check failed:', (0, log_safe_1.errForLog)(error));
|
|
87
88
|
(0, navigation_1.redirect)(serviceUnavailableUrl);
|
|
88
89
|
}
|
|
89
90
|
if (requires2FA) {
|
|
@@ -144,7 +145,7 @@ async function authGuard(options) {
|
|
|
144
145
|
if (error && typeof error === 'object' && 'digest' in error) {
|
|
145
146
|
throw error;
|
|
146
147
|
}
|
|
147
|
-
console.error(`[AUTH-GUARD] Custom check "${check.name}" error:`,
|
|
148
|
+
console.error(`[AUTH-GUARD] Custom check "${check.name}" error:`, (0, log_safe_1.errForLog)(error));
|
|
148
149
|
(0, navigation_1.redirect)(serviceUnavailableUrl);
|
|
149
150
|
}
|
|
150
151
|
}
|
package/dist/server/auth.js
CHANGED
|
@@ -50,6 +50,7 @@ require("server-only");
|
|
|
50
50
|
const better_auth_1 = require("../auth/better-auth");
|
|
51
51
|
const session_store_1 = require("../lib/session-store");
|
|
52
52
|
const token_log_id_1 = require("../lib/token-log-id");
|
|
53
|
+
const log_safe_1 = require("../lib/log-safe");
|
|
53
54
|
const verified_session_token_1 = require("./verified-session-token");
|
|
54
55
|
function buildSessionDataFromAuthSession(session) {
|
|
55
56
|
const user = session?.user;
|
|
@@ -147,18 +148,13 @@ async function getAuthInstance() {
|
|
|
147
148
|
* `{error:'NO_SESSION'}`, and getSession() swallowed every Redis error into a bare `catch {}`. These helpers name
|
|
148
149
|
* the origin and make a swallowed read audible. Hard rules: every helper is wrapped so it can NEVER throw or change
|
|
149
150
|
* a return value; nothing here ever logs a token, a cookie VALUE or a Redis value - only a tokenLogId() fingerprint,
|
|
150
|
-
* cookie NAMES, booleans, and an error
|
|
151
|
+
* cookie NAMES, booleans, and an error NAME with its closed code/status (errForLog; never the message).
|
|
151
152
|
* -------------------------------------------------------------------------------------------------------------- */
|
|
152
|
-
/**
|
|
153
|
-
|
|
154
|
-
const raw = v instanceof Error ? v.message : String(v ?? '');
|
|
155
|
-
return raw.replace(/[A-Za-z0-9_\-.+/=]{24,}/g, '[redacted]').slice(0, 80);
|
|
156
|
-
}
|
|
157
|
-
/** `catch {}` sites on the session read path: still swallowed, now audible. */
|
|
153
|
+
/** `catch {}` sites on the session read path: still swallowed, now audible. The line names the error and its closed code/status only: the
|
|
154
|
+
* message is dropped (BAPert 76553: a redacted-and-truncated message still leaked host names, ports and quoted input). */
|
|
158
155
|
function logSessionReadSwallowed(path, err) {
|
|
159
156
|
try {
|
|
160
|
-
|
|
161
|
-
console.warn(`[server-auth] SESSION_READ_SWALLOWED path=${path} err=${name} msg=${scrubForLog(err)}`);
|
|
157
|
+
console.warn(`[server-auth] SESSION_READ_SWALLOWED path=${path} ${(0, log_safe_1.errForLog)(err)}`);
|
|
162
158
|
}
|
|
163
159
|
catch {
|
|
164
160
|
/* a diagnostic must never change the outcome */
|
|
@@ -49,6 +49,7 @@ const startup_init_1 = require("../lib/startup-init");
|
|
|
49
49
|
const session_cookie_shape_1 = require("../lib/session-cookie-shape");
|
|
50
50
|
const verified_session_token_1 = require("./verified-session-token");
|
|
51
51
|
const token_log_id_1 = require("../lib/token-log-id");
|
|
52
|
+
const log_safe_1 = require("../lib/log-safe");
|
|
52
53
|
/**
|
|
53
54
|
* Try Better Auth's server-side session API.
|
|
54
55
|
* Returns a DecodedSession if Better Auth has an active session, null otherwise.
|
|
@@ -215,7 +216,7 @@ async function tryBetterAuthSession(requestCookies) {
|
|
|
215
216
|
return { sessionData, jwtPayload };
|
|
216
217
|
}
|
|
217
218
|
catch (error) {
|
|
218
|
-
console.warn('[DECODE-SESSION] Better Auth session check failed:',
|
|
219
|
+
console.warn('[DECODE-SESSION] Better Auth session check failed:', (0, log_safe_1.errForLog)(error));
|
|
219
220
|
return null;
|
|
220
221
|
}
|
|
221
222
|
}
|
|
@@ -304,7 +305,7 @@ async function decodeSession(requestCookies) {
|
|
|
304
305
|
};
|
|
305
306
|
}
|
|
306
307
|
catch (error) {
|
|
307
|
-
console.error('[DECODE-SESSION] Error:',
|
|
308
|
+
console.error('[DECODE-SESSION] Error:', (0, log_safe_1.errForLog)(error));
|
|
308
309
|
return null;
|
|
309
310
|
}
|
|
310
311
|
}
|
package/dist/server/with-auth.js
CHANGED
|
@@ -18,6 +18,7 @@ exports.withAuth = withAuth;
|
|
|
18
18
|
require("server-only");
|
|
19
19
|
const server_1 = require("next/server");
|
|
20
20
|
const decode_session_1 = require("./decode-session");
|
|
21
|
+
const log_safe_1 = require("../lib/log-safe");
|
|
21
22
|
// =============================================================================
|
|
22
23
|
// MAIN
|
|
23
24
|
// =============================================================================
|
|
@@ -52,7 +53,7 @@ function withAuth(handler, options) {
|
|
|
52
53
|
return handler(req, auth);
|
|
53
54
|
}
|
|
54
55
|
catch (error) {
|
|
55
|
-
console.error('[WITH-AUTH] Error:',
|
|
56
|
+
console.error('[WITH-AUTH] Error:', (0, log_safe_1.errForLog)(error));
|
|
56
57
|
return server_1.NextResponse.json({ error: 'Internal Server Error', message: 'Auth check failed' }, { status: 500 });
|
|
57
58
|
}
|
|
58
59
|
};
|
package/package.json
CHANGED
|
@@ -3,6 +3,7 @@ import { ENV_CONFIG } from '../../../config/env';
|
|
|
3
3
|
import { getIDPClientConfig } from '../../../lib/idp-client-config';
|
|
4
4
|
import { resolveClientKey } from '../../../lib/client-key';
|
|
5
5
|
import { idpErrorEnvelope } from '../../../lib/safe-error-response';
|
|
6
|
+
import { errForLog } from '../../../lib/log-safe';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Account recovery - the ONE forwarder behind /recovery/send-code and /recovery/verify-code (PAY-1588).
|
|
@@ -57,7 +58,7 @@ async function recoveryClientKey(where: string): Promise<string | null> {
|
|
|
57
58
|
const config = await getIDPClientConfig();
|
|
58
59
|
return resolveClientKey(config, where).key;
|
|
59
60
|
} catch (e) {
|
|
60
|
-
console.error(`[${where}] could not resolve IDP client config for client_id:`, e);
|
|
61
|
+
console.error(`[${where}] could not resolve IDP client config for client_id:`, errForLog(e));
|
|
61
62
|
return null;
|
|
62
63
|
}
|
|
63
64
|
}
|
|
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server';
|
|
|
2
2
|
import { getIDPClientConfig } from '../../../lib/idp-client-config';
|
|
3
3
|
import { resolveClientKey } from '../../../lib/client-key';
|
|
4
4
|
import { idpErrorEnvelope } from '../../../lib/safe-error-response';
|
|
5
|
+
import { errForLog } from '../../../lib/log-safe';
|
|
5
6
|
|
|
6
7
|
function getIdpUrl(): string {
|
|
7
8
|
const url = process.env.IDP_URL;
|
|
@@ -34,7 +35,7 @@ async function recoveryClientKey(): Promise<string | null> {
|
|
|
34
35
|
const config = await getIDPClientConfig();
|
|
35
36
|
return resolveClientKey(config, 'account/recovery/initiate').key;
|
|
36
37
|
} catch (e) {
|
|
37
|
-
console.error('[account/recovery/initiate] could not resolve IDP client config for client_id:', e);
|
|
38
|
+
console.error('[account/recovery/initiate] could not resolve IDP client config for client_id:', errForLog(e));
|
|
38
39
|
return null;
|
|
39
40
|
}
|
|
40
41
|
}
|
|
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server';
|
|
|
2
2
|
import { getIDPClientConfig } from '../../lib/idp-client-config';
|
|
3
3
|
import { resolveClientKey } from '../../lib/client-key';
|
|
4
4
|
import { idpErrorEnvelope, projectPasswordPolicyRules, projectResetTokenInvalid, projectPasswordBreached } from '../../lib/safe-error-response';
|
|
5
|
+
import { errForLog } from '../../lib/log-safe';
|
|
5
6
|
|
|
6
7
|
function getIdpUrl(): string {
|
|
7
8
|
const url = process.env.IDP_URL;
|
|
@@ -34,7 +35,7 @@ async function recoveryClientKey(): Promise<string | null> {
|
|
|
34
35
|
const config = await getIDPClientConfig();
|
|
35
36
|
return resolveClientKey(config, 'account/reset-password').key;
|
|
36
37
|
} catch (e) {
|
|
37
|
-
console.error('[account/reset-password] could not resolve IDP client config for client_id:', e);
|
|
38
|
+
console.error('[account/reset-password] could not resolve IDP client config for client_id:', errForLog(e));
|
|
38
39
|
return null;
|
|
39
40
|
}
|
|
40
41
|
}
|
|
@@ -11,6 +11,7 @@ import { NextRequest, NextResponse } from 'next/server';
|
|
|
11
11
|
import { idpFetchJSON } from '../../lib/idp-fetch';
|
|
12
12
|
import { ENV_CONFIG } from '../../config/env';
|
|
13
13
|
import { twoFactorSendCodeFailure } from '../../lib/two-factor-verify-failure';
|
|
14
|
+
import { errForLog } from '../../lib/log-safe';
|
|
14
15
|
|
|
15
16
|
export async function POST(req: NextRequest) {
|
|
16
17
|
try {
|
|
@@ -57,7 +58,7 @@ export async function POST(req: NextRequest) {
|
|
|
57
58
|
{ status: 200 }
|
|
58
59
|
);
|
|
59
60
|
} catch (error) {
|
|
60
|
-
console.error('[SEND_CODE] Error:', error);
|
|
61
|
+
console.error('[SEND_CODE] Error:', errForLog(error));
|
|
61
62
|
return NextResponse.json(
|
|
62
63
|
{
|
|
63
64
|
success: false,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
2
|
import { idpFetchJSON } from '../../lib/idp-fetch';
|
|
3
3
|
import { ENV_CONFIG } from '../../config/env';
|
|
4
|
+
import { errForLog } from '../../lib/log-safe';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Update Phone Number API Handler
|
|
@@ -75,7 +76,7 @@ export async function POST(req: NextRequest) {
|
|
|
75
76
|
data: responseData,
|
|
76
77
|
});
|
|
77
78
|
} catch (error) {
|
|
78
|
-
console.error('[UPDATE_PHONE] Error:', error);
|
|
79
|
+
console.error('[UPDATE_PHONE] Error:', errForLog(error));
|
|
79
80
|
return NextResponse.json(
|
|
80
81
|
{ success: false, error: { code: 'INTERNAL_ERROR', message: 'Failed to update phone number' } },
|
|
81
82
|
{ status: 500 }
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { errForLog } from '../../lib/log-safe';
|
|
2
3
|
|
|
3
4
|
interface ValidatePasswordRequest {
|
|
4
5
|
password: string;
|
|
@@ -123,7 +124,7 @@ export async function POST(req: NextRequest) {
|
|
|
123
124
|
headers: { 'Cache-Control': 'no-store' },
|
|
124
125
|
});
|
|
125
126
|
} catch (error) {
|
|
126
|
-
console.error('[VALIDATE_PASSWORD] Error:', error);
|
|
127
|
+
console.error('[VALIDATE_PASSWORD] Error:', errForLog(error));
|
|
127
128
|
return NextResponse.json(
|
|
128
129
|
{
|
|
129
130
|
is_valid: false,
|
|
@@ -14,6 +14,7 @@ import { getTokenTestAware } from '../../lib/test-aware-get-token';
|
|
|
14
14
|
import { transitionTo2FASession } from '../../lib/session-store';
|
|
15
15
|
import { jwtDecode } from '../../lib/jwt-decode';
|
|
16
16
|
import { twoFactorVerifyFailure } from '../../lib/two-factor-verify-failure';
|
|
17
|
+
import { errForLog } from '../../lib/log-safe';
|
|
17
18
|
|
|
18
19
|
export async function POST(req: NextRequest) {
|
|
19
20
|
try {
|
|
@@ -121,7 +122,7 @@ export async function POST(req: NextRequest) {
|
|
|
121
122
|
message: unwrappedData.message || 'Email code verified successfully'
|
|
122
123
|
}, { status: 200 });
|
|
123
124
|
} catch (error) {
|
|
124
|
-
console.error('[VERIFY_EMAIL] Error:', error);
|
|
125
|
+
console.error('[VERIFY_EMAIL] Error:', errForLog(error));
|
|
125
126
|
return NextResponse.json(
|
|
126
127
|
{
|
|
127
128
|
success: false,
|
|
@@ -14,6 +14,7 @@ import { getTokenTestAware } from '../../lib/test-aware-get-token';
|
|
|
14
14
|
import { transitionTo2FASession } from '../../lib/session-store';
|
|
15
15
|
import { jwtDecode } from '../../lib/jwt-decode';
|
|
16
16
|
import { twoFactorVerifyFailure } from '../../lib/two-factor-verify-failure';
|
|
17
|
+
import { errForLog } from '../../lib/log-safe';
|
|
17
18
|
|
|
18
19
|
export async function POST(req: NextRequest) {
|
|
19
20
|
try {
|
|
@@ -121,7 +122,7 @@ export async function POST(req: NextRequest) {
|
|
|
121
122
|
message: unwrappedData.message || 'SMS code verified successfully'
|
|
122
123
|
}, { status: 200 });
|
|
123
124
|
} catch (error) {
|
|
124
|
-
console.error('[VERIFY_SMS] Error:', error);
|
|
125
|
+
console.error('[VERIFY_SMS] Error:', errForLog(error));
|
|
125
126
|
return NextResponse.json(
|
|
126
127
|
{
|
|
127
128
|
success: false,
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
ANON_COOKIE_NAME,
|
|
16
16
|
AnonSessionPreferences,
|
|
17
17
|
} from '../../lib/anon-session';
|
|
18
|
+
import { errForLog } from '../../lib/log-safe';
|
|
18
19
|
|
|
19
20
|
const COOKIE_MAX_AGE = 90 * 24 * 60 * 60; // 90 days in seconds
|
|
20
21
|
|
|
@@ -52,7 +53,7 @@ export async function GET(request: NextRequest) {
|
|
|
52
53
|
|
|
53
54
|
return response;
|
|
54
55
|
} catch (error) {
|
|
55
|
-
console.error('[ANON-PREFERENCES] GET error:', error);
|
|
56
|
+
console.error('[ANON-PREFERENCES] GET error:', errForLog(error));
|
|
56
57
|
return NextResponse.json(
|
|
57
58
|
{ success: false, error: 'Failed to get preferences' },
|
|
58
59
|
{ status: 500 }
|
|
@@ -114,7 +115,7 @@ export async function POST(request: NextRequest) {
|
|
|
114
115
|
|
|
115
116
|
return response;
|
|
116
117
|
} catch (error) {
|
|
117
|
-
console.error('[ANON-PREFERENCES] POST error:', error);
|
|
118
|
+
console.error('[ANON-PREFERENCES] POST error:', errForLog(error));
|
|
118
119
|
return NextResponse.json(
|
|
119
120
|
{ success: false, error: 'Failed to update preferences' },
|
|
120
121
|
{ status: 500 }
|
|
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server';
|
|
|
2
2
|
import { getIDPClientConfig } from '../../lib/idp-client-config';
|
|
3
3
|
import { resolveClientKey } from '../../lib/client-key';
|
|
4
4
|
import { bearerFetch } from '../../lib/bearer-fetch';
|
|
5
|
+
import { errForLog } from '../../lib/log-safe';
|
|
5
6
|
|
|
6
7
|
// `export const runtime = 'nodejs'` must be written LITERALLY in the app's route file (Next ignores a re-exported segment config).
|
|
7
8
|
|
|
@@ -43,7 +44,7 @@ export async function POST(req: NextRequest) {
|
|
|
43
44
|
const config = await getIDPClientConfig();
|
|
44
45
|
clientKey = resolveClientKey(config, 'graft/admin-login/email-code').key ?? '';
|
|
45
46
|
} catch (err) {
|
|
46
|
-
console.error('[ADMIN_LOGIN_EMAIL_CODE] IDP client config unavailable:',
|
|
47
|
+
console.error('[ADMIN_LOGIN_EMAIL_CODE] IDP client config unavailable:', errForLog(err));
|
|
47
48
|
return NextResponse.json(
|
|
48
49
|
{ success: false, error: 'Sign-in is temporarily unavailable. This is a problem on our side. Please try again shortly.' },
|
|
49
50
|
{ status: 503 },
|
|
@@ -74,7 +75,7 @@ export async function POST(req: NextRequest) {
|
|
|
74
75
|
/* not JSON - nothing to echo */
|
|
75
76
|
}
|
|
76
77
|
} catch (err) {
|
|
77
|
-
console.error('[ADMIN_LOGIN_EMAIL_CODE] twofa/email/send fetch failed:',
|
|
78
|
+
console.error('[ADMIN_LOGIN_EMAIL_CODE] twofa/email/send fetch failed:', errForLog(err));
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
if (!sendOk) {
|
|
@@ -47,6 +47,7 @@ import { createHash } from 'crypto';
|
|
|
47
47
|
import { DEVICE_ID_HEADER, isValidDeviceId } from '../../lib/device-id';
|
|
48
48
|
import { classifyTwofaRejection, type TwofaLaneResult } from '../../lib/twofa-rejection';
|
|
49
49
|
import { bearerFetch } from '../../lib/bearer-fetch';
|
|
50
|
+
import { errForLog } from '../../lib/log-safe';
|
|
50
51
|
|
|
51
52
|
// `export const runtime = 'nodejs'` must be written LITERALLY in the app's route file (Next ignores a re-exported segment config).
|
|
52
53
|
|
|
@@ -182,7 +183,7 @@ export async function POST(req: NextRequest) {
|
|
|
182
183
|
try {
|
|
183
184
|
config = await getIDPClientConfig();
|
|
184
185
|
} catch (err) {
|
|
185
|
-
console.error('[ADMIN_LOGIN] IDP client config unavailable — returning 503:', err);
|
|
186
|
+
console.error('[ADMIN_LOGIN] IDP client config unavailable — returning 503:', errForLog(err));
|
|
186
187
|
return NextResponse.json(
|
|
187
188
|
{
|
|
188
189
|
success: false,
|
|
@@ -283,7 +284,7 @@ export async function POST(req: NextRequest) {
|
|
|
283
284
|
laneResults.push({ lane, status: idpResponse.status, body: await idpResponse.clone().text().catch(() => ''), retryAfter: idpResponse.headers.get('retry-after') });
|
|
284
285
|
}
|
|
285
286
|
} catch (error) {
|
|
286
|
-
console.error('[ADMIN-LOGIN] twofa verify fetch failed:',
|
|
287
|
+
console.error('[ADMIN-LOGIN] twofa verify fetch failed:', errForLog(error));
|
|
287
288
|
return NextResponse.json({ success: false, error: 'Unable to reach the authentication service' }, { status: 503 });
|
|
288
289
|
}
|
|
289
290
|
if (!idpResponse.ok) {
|
|
@@ -372,7 +373,7 @@ export async function POST(req: NextRequest) {
|
|
|
372
373
|
body: JSON.stringify(idpPayload),
|
|
373
374
|
});
|
|
374
375
|
} catch (error) {
|
|
375
|
-
console.error('[ADMIN-LOGIN] IDP fetch failed:',
|
|
376
|
+
console.error('[ADMIN-LOGIN] IDP fetch failed:', errForLog(error));
|
|
376
377
|
return NextResponse.json({ success: false, error: 'Unable to reach the authentication service' }, { status: 503 });
|
|
377
378
|
}
|
|
378
379
|
|
|
@@ -474,7 +475,7 @@ export async function POST(req: NextRequest) {
|
|
|
474
475
|
console.warn('[ADMIN-LOGIN] mfa/methods unreadable, defaulting to email lane', mResp.status);
|
|
475
476
|
}
|
|
476
477
|
} catch (err) {
|
|
477
|
-
console.warn('[ADMIN-LOGIN] mfa/methods fetch failed, defaulting to email lane:',
|
|
478
|
+
console.warn('[ADMIN-LOGIN] mfa/methods fetch failed, defaulting to email lane:', errForLog(err));
|
|
478
479
|
}
|
|
479
480
|
}
|
|
480
481
|
// PAY-2314: advertise what this user can actually use, derived from the SAME usableFactors that chose the lane
|
|
@@ -549,7 +550,7 @@ export async function POST(req: NextRequest) {
|
|
|
549
550
|
// not JSON - nothing to echo, and never a reason to fail the sign-in
|
|
550
551
|
}
|
|
551
552
|
} catch (err) {
|
|
552
|
-
console.error(`[ADMIN-LOGIN] twofa/${sendLane}/send fetch failed:`,
|
|
553
|
+
console.error(`[ADMIN-LOGIN] twofa/${sendLane}/send fetch failed:`, errForLog(err));
|
|
553
554
|
}
|
|
554
555
|
}
|
|
555
556
|
// 267479: `method:'email'` WAS A HARDCODED LITERAL HERE, and it was a lie for
|
|
@@ -26,6 +26,7 @@ import { getSession as getBetterAuthSession } from '../../server/auth';
|
|
|
26
26
|
import { ensureFreshAccessToken, type EnsureFreshResult } from '../../lib/ensure-fresh-access-token';
|
|
27
27
|
import { UPSTREAM_TIMEOUT_CODE } from '../../lib/refresh-in-doubt';
|
|
28
28
|
import { sanitizeTraceId } from '../../lib/safe-trace-id';
|
|
29
|
+
import { errForLog } from '../../lib/log-safe';
|
|
29
30
|
|
|
30
31
|
interface RefreshConfig {
|
|
31
32
|
idpBaseUrl: string;
|
|
@@ -145,7 +146,7 @@ export function createRefreshHandler(config: RefreshConfig) {
|
|
|
145
146
|
hasRefreshToken: result.hasRefreshToken,
|
|
146
147
|
});
|
|
147
148
|
} catch (err: any) {
|
|
148
|
-
console.error('[AUTH_REFRESH] Unexpected error', { error:
|
|
149
|
+
console.error('[AUTH_REFRESH] Unexpected error', { error: errForLog(err) });
|
|
149
150
|
return NextResponse.json({
|
|
150
151
|
error: 'Unexpected error during refresh',
|
|
151
152
|
code: 'INTERNAL_SERVER_ERROR'
|
|
@@ -11,7 +11,15 @@
|
|
|
11
11
|
|
|
12
12
|
import { NextRequest, NextResponse } from 'next/server';
|
|
13
13
|
import { cookies } from 'next/headers';
|
|
14
|
-
import { deleteSessionEverywhere } from '../../lib/session-store';
|
|
14
|
+
import { deleteSessionEverywhere, getSession as getStoredSession, getBetterAuthSession as getStoredBetterAuthSession } from '../../lib/session-store';
|
|
15
|
+
import { revokeSessionAtIdp } from '../../lib/idp-logout';
|
|
16
|
+
import { errForLog } from '../../lib/log-safe';
|
|
17
|
+
import {
|
|
18
|
+
SESSION_NOT_ENDED_CODE,
|
|
19
|
+
SESSION_NOT_ENDED_IDP_MESSAGE,
|
|
20
|
+
SESSION_NOT_ENDED_LOCAL_MESSAGE,
|
|
21
|
+
type SessionNotEndedCause,
|
|
22
|
+
} from '../../routes/auth/logout';
|
|
15
23
|
import { getSession } from '../../server/auth';
|
|
16
24
|
import {
|
|
17
25
|
getSessionCookieName,
|
|
@@ -147,23 +155,55 @@ export function createSignoutHandler() {
|
|
|
147
155
|
}
|
|
148
156
|
}
|
|
149
157
|
|
|
150
|
-
//
|
|
151
|
-
//
|
|
158
|
+
// GOV-023 (BAPert 76687): ending the session ends the IdP GRANT first, the same IdP-first path as routes/auth/logout. Before this, this
|
|
159
|
+
// handler only deleted the local record, so a consumer who mounted it left the IdP refresh token live until it expired.
|
|
160
|
+
// A failed or unresolved revoke is the same typed failure as a failed local delete (SESSION_NOT_ENDED + cause); the local delete still
|
|
161
|
+
// runs and the cookies are cleared either way.
|
|
162
|
+
let idpRevokeFailed = false;
|
|
163
|
+
let localDeleteFailed = false;
|
|
152
164
|
if (redisSessionToken) {
|
|
165
|
+
// Does the stored session hold an IdP refresh token? (revokeSessionAtIdp reads the canonical row, then the Better Auth record.)
|
|
166
|
+
let heldRefreshToken = false;
|
|
167
|
+
try {
|
|
168
|
+
const stored = (await getStoredSession(redisSessionToken)) ?? (await getStoredBetterAuthSession(redisSessionToken));
|
|
169
|
+
heldRefreshToken = !!stored?.idpRefreshToken;
|
|
170
|
+
} catch {
|
|
171
|
+
// unknown: treated as "none held", so an unreadable store does not turn a clean sign-out into a failure on its own
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
const outcome = await revokeSessionAtIdp(req);
|
|
175
|
+
// 'nothing-to-revoke' while a refresh token IS held means the helper could not resolve this session from the request (this
|
|
176
|
+
// handler also finds the session by decoding the cookie when Better Auth cannot): the grant is NOT ended, so say so.
|
|
177
|
+
if (outcome === 'nothing-to-revoke' && heldRefreshToken) {
|
|
178
|
+
idpRevokeFailed = true;
|
|
179
|
+
console.error('[SIGNOUT] IdP revoke could not resolve the session - the IdP refresh token is STILL LIVE');
|
|
180
|
+
}
|
|
181
|
+
} catch (revokeError) {
|
|
182
|
+
idpRevokeFailed = true;
|
|
183
|
+
console.error(
|
|
184
|
+
(revokeError as { grantLive?: boolean } | null)?.grantLive === true
|
|
185
|
+
? '[SIGNOUT] IdP revoke failed - the IdP refresh token is STILL LIVE:'
|
|
186
|
+
: '[SIGNOUT] IdP revoke could not be confirmed (the grant may already be dead):',
|
|
187
|
+
errForLog(revokeError),
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Delete the session from EVERY store (181922): canonical + BA secondary.
|
|
192
|
+
// Canonical-only deletion let the ba: record resurrect the session.
|
|
153
193
|
try {
|
|
154
194
|
await deleteSessionEverywhere(redisSessionToken);
|
|
155
195
|
console.info('[SIGNOUT] Redis session cleanup successful', {
|
|
156
196
|
sessionToken: tokenLogId(redisSessionToken)
|
|
157
197
|
});
|
|
158
198
|
} catch (sessionError) {
|
|
159
|
-
|
|
160
|
-
console.error('[SIGNOUT] Redis session cleanup failed
|
|
199
|
+
localDeleteFailed = true;
|
|
200
|
+
console.error('[SIGNOUT] Redis session cleanup failed - the server session is STILL LIVE:', errForLog(sessionError));
|
|
161
201
|
}
|
|
162
202
|
} else {
|
|
163
203
|
console.warn('[SIGNOUT] No Redis session token (UUID) extracted from cookie; skipping Redis deletion');
|
|
164
204
|
}
|
|
165
205
|
|
|
166
|
-
// Build response
|
|
206
|
+
// Build response. A sign-out that did not end the session must not say it did (same typed answer as routes/auth/logout).
|
|
167
207
|
const responseData: SignoutResponse = {
|
|
168
208
|
success: true,
|
|
169
209
|
message: sessionToken ? 'Session deleted successfully' : 'No active session found',
|
|
@@ -171,7 +211,14 @@ export function createSignoutHandler() {
|
|
|
171
211
|
chunkCookiesDeleted: chunkCookies.length
|
|
172
212
|
};
|
|
173
213
|
|
|
174
|
-
const response =
|
|
214
|
+
const response = (localDeleteFailed || idpRevokeFailed)
|
|
215
|
+
? NextResponse.json(
|
|
216
|
+
localDeleteFailed
|
|
217
|
+
? { success: false, code: SESSION_NOT_ENDED_CODE, cause: 'local_delete_failed' as SessionNotEndedCause, message: SESSION_NOT_ENDED_LOCAL_MESSAGE }
|
|
218
|
+
: { success: false, code: SESSION_NOT_ENDED_CODE, cause: 'idp_unconfirmed' as SessionNotEndedCause, message: SESSION_NOT_ENDED_IDP_MESSAGE },
|
|
219
|
+
{ status: 500 },
|
|
220
|
+
)
|
|
221
|
+
: NextResponse.json(responseData);
|
|
175
222
|
|
|
176
223
|
// Always clear cookies, regardless of success/failure (using app-slug prefixed names)
|
|
177
224
|
try {
|
|
@@ -60,6 +60,7 @@ import { getTokenTestAware } from '../../lib/test-aware-get-token';
|
|
|
60
60
|
import { transitionTo2FASession } from '../../lib/session-store';
|
|
61
61
|
import { jwtDecode } from '../../lib/jwt-decode';
|
|
62
62
|
import { twoFactorVerifyFailure } from '../../lib/two-factor-verify-failure';
|
|
63
|
+
import { errForLog } from '../../lib/log-safe';
|
|
63
64
|
|
|
64
65
|
export async function POST(req: NextRequest) {
|
|
65
66
|
try {
|
|
@@ -157,7 +158,7 @@ export async function POST(req: NextRequest) {
|
|
|
157
158
|
{ status: 200 }
|
|
158
159
|
);
|
|
159
160
|
} catch (error) {
|
|
160
|
-
console.error('[VERIFY_BACKUP_CODE] Error:', error);
|
|
161
|
+
console.error('[VERIFY_BACKUP_CODE] Error:', errForLog(error));
|
|
161
162
|
return NextResponse.json(
|
|
162
163
|
{
|
|
163
164
|
success: false,
|
|
@@ -35,6 +35,7 @@ import { getTokenTestAware } from '../../lib/test-aware-get-token';
|
|
|
35
35
|
import { transitionTo2FASession } from '../../lib/session-store';
|
|
36
36
|
import { jwtDecode } from '../../lib/jwt-decode';
|
|
37
37
|
import { twoFactorVerifyFailure } from '../../lib/two-factor-verify-failure';
|
|
38
|
+
import { errForLog } from '../../lib/log-safe';
|
|
38
39
|
|
|
39
40
|
export async function POST(req: NextRequest) {
|
|
40
41
|
try {
|
|
@@ -159,7 +160,7 @@ export async function POST(req: NextRequest) {
|
|
|
159
160
|
{ status: 200 }
|
|
160
161
|
);
|
|
161
162
|
} catch (error) {
|
|
162
|
-
console.error('[VERIFY_TOTP] Error:', error);
|
|
163
|
+
console.error('[VERIFY_TOTP] Error:', errForLog(error));
|
|
163
164
|
return NextResponse.json(
|
|
164
165
|
{
|
|
165
166
|
success: false,
|