@payez/next-mvp 4.10.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.
Files changed (123) hide show
  1. package/CHANGELOG.md +44 -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.d.ts +17 -58
  14. package/dist/api-handlers/auth/refresh.js +73 -773
  15. package/dist/api-handlers/auth/signout.js +43 -6
  16. package/dist/api-handlers/mfa/backup-code-verify.js +2 -1
  17. package/dist/api-handlers/mfa/totp-verify.js +2 -1
  18. package/dist/api-handlers/session/refresh-viability.js +3 -2
  19. package/dist/api-handlers/session/viability.js +2 -1
  20. package/dist/auth/better-auth.js +7 -6
  21. package/dist/auth/idp-oauth.js +4 -2
  22. package/dist/auth/utils/idp-client.js +5 -4
  23. package/dist/auth/utils/token-utils.d.ts +1 -1
  24. package/dist/auth/utils/token-utils.js +7 -5
  25. package/dist/config/logger.js +2 -1
  26. package/dist/config/logging-config.js +4 -3
  27. package/dist/config/vibe-log-transport.js +2 -1
  28. package/dist/lib/api-handler.js +4 -3
  29. package/dist/lib/ensure-fresh-access-token.d.ts +24 -1
  30. package/dist/lib/ensure-fresh-access-token.js +195 -28
  31. package/dist/lib/geolocation.js +5 -22
  32. package/dist/lib/idp-client-config.js +4 -3
  33. package/dist/lib/idp-fetch.d.ts +11 -0
  34. package/dist/lib/idp-fetch.js +6 -3
  35. package/dist/lib/idp-logout.d.ts +10 -0
  36. package/dist/lib/idp-logout.js +73 -1
  37. package/dist/lib/jwt-decode-client.js +2 -1
  38. package/dist/lib/jwt-decode.js +2 -1
  39. package/dist/lib/log-safe.d.ts +19 -0
  40. package/dist/lib/log-safe.js +32 -0
  41. package/dist/lib/redis.js +2 -1
  42. package/dist/lib/refresh-in-doubt.d.ts +31 -0
  43. package/dist/lib/refresh-in-doubt.js +81 -0
  44. package/dist/lib/refresh-token-validator.js +2 -1
  45. package/dist/lib/refresh-window.d.ts +16 -0
  46. package/dist/lib/refresh-window.js +19 -0
  47. package/dist/lib/safe-error-response.d.ts +5 -1
  48. package/dist/lib/safe-error-response.js +7 -2
  49. package/dist/lib/safe-trace-id.d.ts +11 -0
  50. package/dist/lib/safe-trace-id.js +23 -0
  51. package/dist/lib/session-store.js +16 -5
  52. package/dist/lib/site-logger.d.ts +2 -8
  53. package/dist/lib/site-logger.js +11 -5
  54. package/dist/lib/standardized-client-api.js +3 -2
  55. package/dist/lib/token-lifecycle.js +6 -4
  56. package/dist/middleware/create-middleware.js +2 -1
  57. package/dist/middleware/rbac-check.d.ts +0 -13
  58. package/dist/middleware/rbac-check.js +6 -5
  59. package/dist/models/SessionModel.d.ts +7 -0
  60. package/dist/routes/auth/logout.js +6 -2
  61. package/dist/routes/auth/refresh.d.ts +1 -15
  62. package/dist/routes/auth/settings.js +2 -1
  63. package/dist/server/auth-guard.js +4 -3
  64. package/dist/server/auth.d.ts +1 -1
  65. package/dist/server/auth.js +5 -9
  66. package/dist/server/decode-session.js +3 -2
  67. package/dist/server/with-auth.js +2 -1
  68. package/dist/stores/authStore.js +3 -2
  69. package/package.json +1 -1
  70. package/scripts/verify-release-provenance.mjs +1 -0
  71. package/src/api-handlers/account/recovery/forward.ts +2 -1
  72. package/src/api-handlers/account/recovery/initiate.ts +2 -1
  73. package/src/api-handlers/account/reset-password.ts +2 -1
  74. package/src/api-handlers/account/send-code.ts +2 -1
  75. package/src/api-handlers/account/update-phone.ts +2 -1
  76. package/src/api-handlers/account/validate-password.ts +2 -1
  77. package/src/api-handlers/account/verify-email.ts +2 -1
  78. package/src/api-handlers/account/verify-sms.ts +2 -1
  79. package/src/api-handlers/anon/preferences.ts +3 -2
  80. package/src/api-handlers/auth/credential-login-email-code.ts +3 -2
  81. package/src/api-handlers/auth/credential-login.ts +6 -5
  82. package/src/api-handlers/auth/refresh.ts +74 -813
  83. package/src/api-handlers/auth/signout.ts +54 -7
  84. package/src/api-handlers/mfa/backup-code-verify.ts +2 -1
  85. package/src/api-handlers/mfa/totp-verify.ts +2 -1
  86. package/src/api-handlers/session/refresh-viability.ts +3 -2
  87. package/src/api-handlers/session/viability.ts +2 -1
  88. package/src/auth/better-auth.ts +7 -6
  89. package/src/auth/idp-oauth.ts +7 -2
  90. package/src/auth/utils/idp-client.ts +5 -4
  91. package/src/auth/utils/token-utils.ts +7 -5
  92. package/src/config/logger.ts +2 -1
  93. package/src/config/logging-config.ts +4 -3
  94. package/src/config/vibe-log-transport.ts +2 -1
  95. package/src/lib/api-handler.ts +4 -3
  96. package/src/lib/ensure-fresh-access-token.ts +214 -29
  97. package/src/lib/geolocation.ts +5 -4
  98. package/src/lib/idp-client-config.ts +4 -3
  99. package/src/lib/idp-fetch.ts +14 -3
  100. package/src/lib/idp-logout.ts +36 -1
  101. package/src/lib/jwt-decode-client.ts +3 -1
  102. package/src/lib/jwt-decode.ts +2 -1
  103. package/src/lib/log-safe.ts +30 -0
  104. package/src/lib/redis.ts +2 -1
  105. package/src/lib/refresh-in-doubt.ts +79 -0
  106. package/src/lib/refresh-token-validator.ts +2 -1
  107. package/src/lib/refresh-window.ts +16 -0
  108. package/src/lib/safe-error-response.ts +7 -2
  109. package/src/lib/safe-trace-id.ts +21 -0
  110. package/src/lib/session-store.ts +17 -4
  111. package/src/lib/site-logger.ts +13 -7
  112. package/src/lib/standardized-client-api.ts +3 -2
  113. package/src/lib/token-lifecycle.ts +6 -4
  114. package/src/middleware/create-middleware.ts +2 -1
  115. package/src/middleware/rbac-check.ts +2 -1
  116. package/src/models/SessionModel.ts +7 -0
  117. package/src/routes/auth/logout.ts +9 -2
  118. package/src/routes/auth/settings.ts +2 -1
  119. package/src/server/auth-guard.ts +4 -3
  120. package/src/server/auth.ts +6 -11
  121. package/src/server/decode-session.ts +3 -2
  122. package/src/server/with-auth.ts +2 -1
  123. package/src/stores/authStore.ts +3 -2
@@ -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)
@@ -14,18 +14,4 @@
14
14
  * @since auth-ready-v2
15
15
  */
16
16
  import { NextRequest } from 'next/server';
17
- export declare function POST(req: NextRequest): Promise<import("next/server").NextResponse<{
18
- error: string;
19
- code: string;
20
- }> | import("next/server").NextResponse<{
21
- error: any;
22
- code: any;
23
- discardToken: boolean;
24
- retryable: boolean;
25
- resolution: any;
26
- status: number;
27
- }> | import("next/server").NextResponse<{
28
- refreshed: boolean;
29
- accessTokenExpires: number;
30
- hasRefreshToken: boolean;
31
- }>>;
17
+ export declare function POST(req: NextRequest): Promise<import("next/server").NextResponse<unknown>>;
@@ -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
  }
@@ -153,7 +153,7 @@ export interface FreshIdpTokenConfig {
153
153
  idpBaseUrl: string;
154
154
  clientId: string;
155
155
  refreshEndpoint?: string;
156
- /** Refresh if the access token is within this many ms of expiry. Default 60_000. */
156
+ /** Refresh if the access token is within this many ms of expiry. Default REFRESH_WINDOW_MS (5 min, lib/refresh-window). */
157
157
  safetyWindowMs?: number;
158
158
  }
159
159
  /**
@@ -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
  };
@@ -56,6 +56,7 @@ const signalr_1 = require("@microsoft/signalr");
56
56
  const signalrActivityService_1 = require("../services/signalrActivityService");
57
57
  const app_slug_1 = require("../lib/app-slug");
58
58
  const token_log_id_1 = require("../lib/token-log-id");
59
+ const refresh_window_1 = require("../lib/refresh-window");
59
60
  // ===============================
60
61
  // CACHE CONFIGURATION
61
62
  // ===============================
@@ -520,8 +521,8 @@ exports.useAuthStore = (0, zustand_1.create)()((0, middleware_1.devtools)((set,
520
521
  const tokenExpiry = decoded.exp * 1000;
521
522
  const now = Date.now();
522
523
  const timeUntilExpiry = tokenExpiry - now;
523
- // Token should be fresh (not expiring in next 5 minutes)
524
- if (timeUntilExpiry < (5 * 60 * 1000)) {
524
+ // Token should be fresh (not expiring within the one refresh window)
525
+ if (timeUntilExpiry < refresh_window_1.REFRESH_WINDOW_MS) {
525
526
  throw new Error(`Fresh token still expires soon: ${timeUntilExpiry}ms`);
526
527
  }
527
528
  logger_1.authLogger.info('[AuthStore] Session rehydration successful', {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@payez/next-mvp",
3
- "version": "4.10.0",
3
+ "version": "4.12.0",
4
4
  "sideEffects": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -103,6 +103,7 @@ const CLASSES = [
103
103
  { cls: 'VERIFIED', test: (p) => p.startsWith('src/') },
104
104
  { cls: 'VERIFIED', test: (p) => p.startsWith('scripts/') },
105
105
  { cls: 'VERIFIED', test: (p) => p === 'POST-INSTALL.md' },
106
+ { cls: 'VERIFIED', test: (p) => p === 'CHANGELOG.md' },
106
107
  // npm always ships README regardless of "files"; it is committed, so verify it.
107
108
  { cls: 'VERIFIED', test: (p) => p === 'README.md' },
108
109
  { cls: 'VERIFIED', test: (p) => p === 'LICENSE' || p === 'LICENCE' },
@@ -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