@payez/next-mvp 4.11.0 → 4.12.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 (100) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/api-handlers/account/recovery/forward.js +2 -1
  3. package/dist/api-handlers/account/recovery/initiate.js +2 -1
  4. package/dist/api-handlers/account/reset-password.js +2 -1
  5. package/dist/api-handlers/account/send-code.js +2 -1
  6. package/dist/api-handlers/account/update-phone.js +2 -1
  7. package/dist/api-handlers/account/validate-password.js +2 -1
  8. package/dist/api-handlers/account/verify-email.js +2 -1
  9. package/dist/api-handlers/account/verify-sms.js +2 -1
  10. package/dist/api-handlers/anon/preferences.js +3 -2
  11. package/dist/api-handlers/auth/credential-login-email-code.js +3 -2
  12. package/dist/api-handlers/auth/credential-login.js +6 -5
  13. package/dist/api-handlers/auth/refresh.js +2 -1
  14. package/dist/api-handlers/auth/signout.js +43 -6
  15. package/dist/api-handlers/mfa/backup-code-verify.js +2 -1
  16. package/dist/api-handlers/mfa/totp-verify.js +2 -1
  17. package/dist/api-handlers/session/refresh-viability.js +3 -2
  18. package/dist/api-handlers/session/viability.js +2 -1
  19. package/dist/auth/better-auth.js +7 -6
  20. package/dist/auth/idp-oauth.js +4 -2
  21. package/dist/auth/utils/idp-client.js +5 -4
  22. package/dist/auth/utils/token-utils.js +4 -3
  23. package/dist/config/logger.js +2 -1
  24. package/dist/config/logging-config.js +4 -3
  25. package/dist/config/vibe-log-transport.js +2 -1
  26. package/dist/lib/ensure-fresh-access-token.js +17 -11
  27. package/dist/lib/geolocation.js +5 -22
  28. package/dist/lib/idp-client-config.js +4 -3
  29. package/dist/lib/idp-fetch.d.ts +11 -0
  30. package/dist/lib/idp-fetch.js +3 -1
  31. package/dist/lib/idp-logout.d.ts +10 -0
  32. package/dist/lib/idp-logout.js +73 -1
  33. package/dist/lib/jwt-decode-client.js +2 -1
  34. package/dist/lib/jwt-decode.js +2 -1
  35. package/dist/lib/log-safe.d.ts +19 -0
  36. package/dist/lib/log-safe.js +32 -0
  37. package/dist/lib/redis.js +2 -1
  38. package/dist/lib/safe-error-response.d.ts +5 -1
  39. package/dist/lib/safe-error-response.js +7 -2
  40. package/dist/lib/session-store.js +2 -1
  41. package/dist/lib/site-logger.d.ts +2 -8
  42. package/dist/lib/site-logger.js +11 -5
  43. package/dist/lib/standardized-client-api.js +3 -2
  44. package/dist/lib/token-lifecycle.js +3 -2
  45. package/dist/middleware/create-middleware.js +2 -1
  46. package/dist/middleware/rbac-check.d.ts +0 -13
  47. package/dist/middleware/rbac-check.js +6 -5
  48. package/dist/routes/auth/logout.js +6 -2
  49. package/dist/routes/auth/settings.js +2 -1
  50. package/dist/server/auth-guard.js +4 -3
  51. package/dist/server/auth.js +5 -9
  52. package/dist/server/decode-session.js +3 -2
  53. package/dist/server/with-auth.js +2 -1
  54. package/package.json +1 -1
  55. package/src/api-handlers/account/recovery/forward.ts +2 -1
  56. package/src/api-handlers/account/recovery/initiate.ts +2 -1
  57. package/src/api-handlers/account/reset-password.ts +2 -1
  58. package/src/api-handlers/account/send-code.ts +2 -1
  59. package/src/api-handlers/account/update-phone.ts +2 -1
  60. package/src/api-handlers/account/validate-password.ts +2 -1
  61. package/src/api-handlers/account/verify-email.ts +2 -1
  62. package/src/api-handlers/account/verify-sms.ts +2 -1
  63. package/src/api-handlers/anon/preferences.ts +3 -2
  64. package/src/api-handlers/auth/credential-login-email-code.ts +3 -2
  65. package/src/api-handlers/auth/credential-login.ts +6 -5
  66. package/src/api-handlers/auth/refresh.ts +2 -1
  67. package/src/api-handlers/auth/signout.ts +54 -7
  68. package/src/api-handlers/mfa/backup-code-verify.ts +2 -1
  69. package/src/api-handlers/mfa/totp-verify.ts +2 -1
  70. package/src/api-handlers/session/refresh-viability.ts +3 -2
  71. package/src/api-handlers/session/viability.ts +2 -1
  72. package/src/auth/better-auth.ts +7 -6
  73. package/src/auth/idp-oauth.ts +7 -2
  74. package/src/auth/utils/idp-client.ts +5 -4
  75. package/src/auth/utils/token-utils.ts +4 -3
  76. package/src/config/logger.ts +2 -1
  77. package/src/config/logging-config.ts +4 -3
  78. package/src/config/vibe-log-transport.ts +2 -1
  79. package/src/lib/ensure-fresh-access-token.ts +17 -11
  80. package/src/lib/geolocation.ts +5 -4
  81. package/src/lib/idp-client-config.ts +4 -3
  82. package/src/lib/idp-fetch.ts +11 -1
  83. package/src/lib/idp-logout.ts +36 -1
  84. package/src/lib/jwt-decode-client.ts +3 -1
  85. package/src/lib/jwt-decode.ts +2 -1
  86. package/src/lib/log-safe.ts +30 -0
  87. package/src/lib/redis.ts +2 -1
  88. package/src/lib/safe-error-response.ts +7 -2
  89. package/src/lib/session-store.ts +2 -1
  90. package/src/lib/site-logger.ts +13 -7
  91. package/src/lib/standardized-client-api.ts +3 -2
  92. package/src/lib/token-lifecycle.ts +3 -2
  93. package/src/middleware/create-middleware.ts +2 -1
  94. package/src/middleware/rbac-check.ts +2 -1
  95. package/src/routes/auth/logout.ts +9 -2
  96. package/src/routes/auth/settings.ts +2 -1
  97. package/src/server/auth-guard.ts +4 -3
  98. package/src/server/auth.ts +5 -10
  99. package/src/server/decode-session.ts +3 -2
  100. package/src/server/with-auth.ts +2 -1
@@ -48,6 +48,7 @@ exports.extractApiItems = extractApiItems;
48
48
  // NO MORE GUESSING data.data.data.data - EVER AGAIN!
49
49
  // ========================================================================================
50
50
  const better_auth_client_1 = require("../client/better-auth-client");
51
+ const log_safe_1 = require("./log-safe");
51
52
  /** Unwrap Better Auth getSession() to a flat session-like object for backward compat */
52
53
  async function getSession() {
53
54
  const { data } = await better_auth_client_1.authClient.getSession();
@@ -172,7 +173,7 @@ function scheduleLoginRedirect(isImmediate = false) {
172
173
  window.location.href = `/account-auth/login?returnUrl=${returnUrl}`;
173
174
  }
174
175
  catch (error) {
175
- console.error('❌ Error during login redirect:', error);
176
+ console.error('❌ Error during login redirect:', (0, log_safe_1.errForLog)(error));
176
177
  // Final fallback
177
178
  try {
178
179
  window.location.href = '/account-auth/login';
@@ -640,7 +641,7 @@ class StandardizedClientApiService {
640
641
  throw error;
641
642
  }
642
643
  // Wrap unknown errors as network errors
643
- console.error('❌ API request failed:', error);
644
+ console.error('❌ API request failed:', (0, log_safe_1.errForLog)(error));
644
645
  throw new ApiNetworkError(error instanceof Error ? error.message : 'Network error', 0, endpoint);
645
646
  }
646
647
  }
@@ -29,6 +29,7 @@ const redis_1 = require("./redis");
29
29
  const app_slug_1 = require("./app-slug");
30
30
  const token_log_id_1 = require("./token-log-id");
31
31
  const refresh_window_1 = require("./refresh-window");
32
+ const log_safe_1 = require("./log-safe");
32
33
  // "needs refresh" threshold: THE one freshness window (lib/refresh-window), the same value the refresh route, ensureFreshAccessToken and idp-fetch use.
33
34
  const REFRESH_THRESHOLD_MS = refresh_window_1.REFRESH_WINDOW_MS;
34
35
  // Concurrent refresh handling configuration
@@ -358,7 +359,7 @@ async function ensureFreshToken(request) {
358
359
  }
359
360
  catch (e) {
360
361
  // If we can't decode, proceed with normal logic
361
- console.warn('[TOKEN_LIFECYCLE] Could not validate JWT exp claim:', e);
362
+ console.warn('[TOKEN_LIFECYCLE] Could not validate JWT exp claim:', (0, log_safe_1.errForLog)(e));
362
363
  }
363
364
  }
364
365
  console.log('[TOKEN_LIFECYCLE] ensureFreshToken check:', {
@@ -429,7 +430,7 @@ async function ensureFreshToken(request) {
429
430
  };
430
431
  }
431
432
  catch (error) {
432
- console.error('[TOKEN_LIFECYCLE] Error:', error);
433
+ console.error('[TOKEN_LIFECYCLE] Error:', (0, log_safe_1.errForLog)(error));
433
434
  return {
434
435
  success: false,
435
436
  error: 'NO_SESSION',
@@ -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
- console.error('[LOGOUT_ROUTE] IdP revoke failed - the IdP refresh token is STILL LIVE:', error);
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):', error instanceof Error ? error.message : String(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:', error instanceof Error ? error.message : String(error));
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:`, error instanceof Error ? error.message : String(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
  }
@@ -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 name/message with any long opaque run redacted.
151
+ * cookie NAMES, booleans, and an error NAME with its closed code/status (errForLog; never the message).
151
152
  * -------------------------------------------------------------------------------------------------------------- */
152
- /** An error name/message, first 80 chars, with any 24+ char opaque run (a token, a key) replaced. */
153
- function scrubForLog(v) {
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
- const name = scrubForLog(err instanceof Error ? err.name : typeof err);
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:', error instanceof Error ? error.message : String(error));
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:', error instanceof Error ? error.message : String(error));
308
+ console.error('[DECODE-SESSION] Error:', (0, log_safe_1.errForLog)(error));
308
309
  return null;
309
310
  }
310
311
  }
@@ -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:', error instanceof Error ? error.message : String(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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@payez/next-mvp",
3
- "version": "4.11.0",
3
+ "version": "4.12.1",
4
4
  "sideEffects": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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:', err instanceof Error ? err.message : String(err));
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:', err instanceof Error ? err.message : String(err));
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:', error instanceof Error ? error.message : String(error));
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:', error instanceof Error ? error.message : String(error));
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:', err instanceof Error ? err.message : String(err));
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:`, err instanceof Error ? err.message : String(err));
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: err?.message || String(err) });
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'