@payez/next-mvp 4.4.5 → 4.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -3
- package/dist/api/auth-handler.js +5 -3
- package/dist/api-handlers/account/change-password.js +33 -8
- package/dist/api-handlers/account/masked-info.js +2 -1
- package/dist/api-handlers/account/recovery/forward.d.ts +14 -0
- package/dist/api-handlers/account/recovery/forward.js +105 -0
- package/dist/api-handlers/account/recovery/initiate.js +4 -0
- package/dist/api-handlers/account/recovery/send-code.d.ts +12 -2
- package/dist/api-handlers/account/recovery/send-code.js +11 -84
- package/dist/api-handlers/account/recovery/verify-code.d.ts +11 -2
- package/dist/api-handlers/account/recovery/verify-code.js +10 -84
- package/dist/api-handlers/account/reset-password.js +24 -1
- package/dist/api-handlers/account/send-code.d.ts +1 -15
- package/dist/api-handlers/account/send-code.js +3 -6
- package/dist/api-handlers/account/update-phone.js +6 -1
- package/dist/api-handlers/account/validate-password.js +3 -1
- package/dist/api-handlers/account/verify-email.d.ts +1 -17
- package/dist/api-handlers/account/verify-email.js +4 -6
- package/dist/api-handlers/account/verify-sms.d.ts +1 -17
- package/dist/api-handlers/account/verify-sms.js +4 -6
- package/dist/api-handlers/admin/analytics.js +12 -4
- package/dist/api-handlers/admin/audit.js +12 -4
- package/dist/api-handlers/admin/redis-sessions.js +16 -8
- package/dist/api-handlers/admin/sessions.js +14 -6
- package/dist/api-handlers/admin/site-logs.js +39 -16
- package/dist/api-handlers/admin/stats.js +14 -4
- package/dist/api-handlers/admin/users.js +14 -6
- package/dist/api-handlers/admin/vibe-data.js +19 -11
- package/dist/api-handlers/auth/jwks.js +2 -1
- package/dist/api-handlers/auth/login.js +13 -4
- package/dist/api-handlers/auth/passkey-signin.js +6 -0
- package/dist/api-handlers/auth/refresh.js +38 -11
- package/dist/api-handlers/auth/signout.js +2 -1
- package/dist/api-handlers/auth/status.js +4 -1
- package/dist/api-handlers/auth/update-session.d.ts +25 -4
- package/dist/api-handlers/auth/update-session.js +38 -11
- package/dist/api-handlers/auth/validate.js +4 -2
- package/dist/api-handlers/auth/verify-code.js +3 -2
- package/dist/api-handlers/mfa/backup-code-verify.d.ts +1 -18
- package/dist/api-handlers/mfa/backup-code-verify.js +4 -6
- package/dist/api-handlers/mfa/backup-codes.js +2 -1
- package/dist/api-handlers/mfa/methods.js +2 -1
- package/dist/api-handlers/mfa/totp-confirm.js +2 -1
- package/dist/api-handlers/mfa/totp-enroll.js +2 -1
- package/dist/api-handlers/mfa/totp-verify.d.ts +1 -19
- package/dist/api-handlers/mfa/totp-verify.js +4 -6
- package/dist/api-handlers/passkeys/list.js +2 -1
- package/dist/api-handlers/passkeys/manage.js +2 -1
- package/dist/api-handlers/passkeys/register-options.js +2 -1
- package/dist/api-handlers/passkeys/register-verify.js +2 -1
- package/dist/api-handlers/session/refresh-viability.d.ts +4 -2
- package/dist/api-handlers/session/refresh-viability.js +10 -7
- package/dist/api-handlers/session/viability.js +4 -3
- package/dist/api-handlers/test/force-expire.js +5 -3
- package/dist/auth/auth-decision.js +2 -1
- package/dist/auth/better-auth.d.ts +58 -14
- package/dist/auth/better-auth.js +125 -54
- package/dist/auth/utils/idp-client.js +2 -1
- package/dist/client/better-auth-client.d.ts +101 -956
- package/dist/components/admin/DataBrowserTab.js +4 -1
- package/dist/components/admin/SessionsTab.js +9 -3
- package/dist/components/recovery/RequestedStep.d.ts +11 -0
- package/dist/components/recovery/RequestedStep.js +14 -0
- package/dist/components/recovery/SetPasswordStep.js +10 -1
- package/dist/config/env.d.ts +0 -2
- package/dist/config/env.js +0 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +8 -1
- package/dist/lib/app-slug.d.ts +21 -0
- package/dist/lib/app-slug.js +51 -0
- package/dist/lib/ensure-fresh-access-token.d.ts +26 -0
- package/dist/lib/ensure-fresh-access-token.js +105 -4
- package/dist/lib/idp-client-config.d.ts +20 -0
- package/dist/lib/idp-client-config.js +73 -45
- package/dist/lib/idp-fetch.js +1 -1
- package/dist/lib/never-in-response.d.ts +30 -0
- package/dist/lib/never-in-response.js +71 -0
- package/dist/lib/password-rules.d.ts +22 -0
- package/dist/lib/password-rules.js +43 -0
- package/dist/lib/safe-error-response.d.ts +67 -0
- package/dist/lib/safe-error-response.js +130 -0
- package/dist/lib/session-cookie-shape.d.ts +22 -19
- package/dist/lib/session-cookie-shape.js +14 -18
- package/dist/lib/session-store.d.ts +8 -1
- package/dist/lib/session-store.js +170 -24
- package/dist/lib/startup-init.d.ts +6 -0
- package/dist/lib/startup-init.js +30 -4
- package/dist/lib/token-lifecycle.js +2 -1
- package/dist/lib/token-log-id.d.ts +18 -0
- package/dist/lib/token-log-id.js +84 -0
- package/dist/lib/two-factor-verify-failure.d.ts +78 -0
- package/dist/lib/two-factor-verify-failure.js +220 -0
- package/dist/middleware/create-middleware.d.ts +4 -1
- package/dist/middleware/create-middleware.js +79 -8
- package/dist/pages/login/page.js +5 -0
- package/dist/pages/recovery/page.js +14 -4
- package/dist/pages/test-env/JwtInspectPage.js +7 -149
- package/dist/pages/test-env/RefreshTokenPage.js +4 -1
- package/dist/pages/test-env/redact-credentials.d.ts +6 -0
- package/dist/pages/test-env/redact-credentials.js +101 -0
- package/dist/pages/verify-code/page.js +6 -3
- package/dist/routes/auth/logout.d.ts +2 -1
- package/dist/routes/auth/logout.js +4 -2
- package/dist/routes/auth/session.d.ts +0 -3
- package/dist/routes/auth/session.js +16 -10
- package/dist/routes/auth/settings.js +4 -1
- package/dist/routes/auth/viability.d.ts +3 -2
- package/dist/routes/auth/viability.js +11 -7
- package/dist/server/auth.d.ts +36 -294
- package/dist/server/auth.js +32 -31
- package/dist/server/decode-session.js +15 -26
- package/dist/server/verified-session-token.d.ts +13 -0
- package/dist/server/verified-session-token.js +103 -0
- package/dist/stores/authStore.js +14 -3
- package/dist/types/recovery.d.ts +1 -1
- package/dist/utils/api.d.ts +18 -0
- package/dist/utils/api.js +51 -9
- package/dist/vibe/client.d.ts +132 -16
- package/dist/vibe/client.js +288 -82
- package/dist/vibe/document-unwrap.d.ts +50 -0
- package/dist/vibe/document-unwrap.js +78 -0
- package/dist/vibe/errors.d.ts +17 -0
- package/dist/vibe/errors.js +21 -1
- package/dist/vibe/generic.d.ts +2 -49
- package/dist/vibe/generic.js +17 -78
- package/dist/vibe/index.d.ts +1 -1
- package/dist/vibe/index.js +2 -1
- package/dist/vibe/sessions.js +2 -1
- package/package.json +13 -2
- package/scripts/verify-release-provenance.mjs +410 -0
- package/src/api/auth-handler.ts +5 -3
- package/src/api-handlers/account/change-password.ts +36 -8
- package/src/api-handlers/account/masked-info.ts +2 -1
- package/src/api-handlers/account/recovery/forward.ts +117 -0
- package/src/api-handlers/account/recovery/initiate.ts +3 -0
- package/src/api-handlers/account/recovery/send-code.ts +13 -99
- package/src/api-handlers/account/recovery/verify-code.ts +12 -99
- package/src/api-handlers/account/reset-password.ts +30 -1
- package/src/api-handlers/account/send-code.ts +3 -9
- package/src/api-handlers/account/update-phone.ts +6 -1
- package/src/api-handlers/account/validate-password.ts +3 -1
- package/src/api-handlers/account/verify-email.ts +4 -9
- package/src/api-handlers/account/verify-sms.ts +4 -9
- package/src/api-handlers/admin/analytics.ts +13 -5
- package/src/api-handlers/admin/audit.ts +13 -5
- package/src/api-handlers/admin/redis-sessions.ts +16 -8
- package/src/api-handlers/admin/sessions.ts +15 -7
- package/src/api-handlers/admin/site-logs.ts +39 -16
- package/src/api-handlers/admin/stats.ts +15 -5
- package/src/api-handlers/admin/users.ts +15 -7
- package/src/api-handlers/admin/vibe-data.ts +20 -12
- package/src/api-handlers/auth/jwks.ts +2 -1
- package/src/api-handlers/auth/login.ts +13 -4
- package/src/api-handlers/auth/passkey-signin.ts +4 -0
- package/src/api-handlers/auth/refresh.ts +38 -11
- package/src/api-handlers/auth/signout.ts +2 -1
- package/src/api-handlers/auth/status.ts +4 -1
- package/src/api-handlers/auth/update-session.ts +45 -11
- package/src/api-handlers/auth/validate.ts +4 -2
- package/src/api-handlers/auth/verify-code.ts +3 -2
- package/src/api-handlers/mfa/backup-code-verify.ts +4 -9
- package/src/api-handlers/mfa/backup-codes.ts +2 -1
- package/src/api-handlers/mfa/methods.ts +2 -1
- package/src/api-handlers/mfa/totp-confirm.ts +2 -1
- package/src/api-handlers/mfa/totp-enroll.ts +2 -1
- package/src/api-handlers/mfa/totp-verify.ts +4 -9
- package/src/api-handlers/passkeys/list.ts +2 -1
- package/src/api-handlers/passkeys/manage.ts +2 -1
- package/src/api-handlers/passkeys/register-options.ts +2 -1
- package/src/api-handlers/passkeys/register-verify.ts +2 -1
- package/src/api-handlers/session/refresh-viability.ts +10 -7
- package/src/api-handlers/session/viability.ts +4 -3
- package/src/api-handlers/test/force-expire.ts +5 -3
- package/src/auth/auth-decision.ts +2 -1
- package/src/auth/better-auth.ts +128 -61
- package/src/auth/utils/idp-client.ts +2 -1
- package/src/components/admin/DataBrowserTab.tsx +4 -1
- package/src/components/admin/SessionsTab.tsx +10 -3
- package/src/components/recovery/RequestedStep.tsx +30 -0
- package/src/components/recovery/SetPasswordStep.tsx +24 -3
- package/src/config/env.ts +0 -2
- package/src/hooks/useViabilitySession.ts +2 -1
- package/src/index.ts +4 -0
- package/src/lib/app-slug.ts +50 -0
- package/src/lib/ensure-fresh-access-token.ts +133 -4
- package/src/lib/idp-client-config.ts +98 -45
- package/src/lib/idp-fetch.ts +1 -1
- package/src/lib/never-in-response.ts +64 -0
- package/src/lib/password-rules.ts +50 -0
- package/src/lib/safe-error-response.ts +126 -0
- package/src/lib/session-cookie-shape.ts +23 -22
- package/src/lib/session-store.ts +167 -24
- package/src/lib/startup-init.ts +29 -4
- package/src/lib/token-lifecycle.ts +2 -1
- package/src/lib/token-log-id.ts +69 -0
- package/src/lib/two-factor-verify-failure.ts +248 -0
- package/src/middleware/create-middleware.ts +97 -9
- package/src/pages/login/page.tsx +4 -0
- package/src/pages/recovery/page.tsx +21 -4
- package/src/pages/test-env/JwtInspectPage.tsx +11 -233
- package/src/pages/test-env/RefreshTokenPage.tsx +9 -4
- package/src/pages/test-env/redact-credentials.ts +96 -0
- package/src/pages/verify-code/page.tsx +6 -3
- package/src/routes/auth/logout.ts +4 -2
- package/src/routes/auth/session.ts +16 -10
- package/src/routes/auth/settings.ts +5 -1
- package/src/routes/auth/viability.ts +11 -7
- package/src/server/auth.ts +33 -35
- package/src/server/decode-session.ts +16 -26
- package/src/server/verified-session-token.ts +103 -0
- package/src/stores/authStore.ts +16 -5
- package/src/types/recovery.ts +1 -1
- package/src/utils/api.ts +47 -8
- package/src/vibe/client.ts +378 -91
- package/src/vibe/document-unwrap.ts +103 -0
- package/src/vibe/errors.ts +24 -0
- package/src/vibe/generic.ts +8 -100
- package/src/vibe/index.ts +1 -0
- package/src/vibe/sessions.ts +2 -1
|
@@ -13,6 +13,7 @@ import { getSession } from '../../server/auth';
|
|
|
13
13
|
import { getStartupIDPConfig } from '../../lib/startup-init';
|
|
14
14
|
import { resolveClientKey } from '../../lib/client-key';
|
|
15
15
|
import { ADMIN_ROLES, hasAnyRole } from '../../lib/roles';
|
|
16
|
+
import { randomUUID } from 'crypto';
|
|
16
17
|
|
|
17
18
|
interface VibeRequestOptions {
|
|
18
19
|
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
@@ -57,7 +58,10 @@ async function checkAdminRole(request: NextRequest): Promise<{ isAdmin: boolean;
|
|
|
57
58
|
async function vibeServiceRequest<T = unknown>(
|
|
58
59
|
endpoint: string,
|
|
59
60
|
options: VibeRequestOptions
|
|
60
|
-
): Promise<{ ok: boolean; status: number; data: T | null; error?: string }> {
|
|
61
|
+
): Promise<{ ok: boolean; status: number; data: T | null; error?: string; correlationId?: string }> {
|
|
62
|
+
// PAY-1429: `error` can reach the HTTP caller, so it never carries upstream Vibe text or
|
|
63
|
+
// exception text. Failures return a fixed message + correlationId; the same id is on the
|
|
64
|
+
// server log line, which carries status + bodyLength only (PAY-1424).
|
|
61
65
|
const idpUrl = process.env.NEXT_PUBLIC_IDP_URL || process.env.IDP_URL;
|
|
62
66
|
const clientId = process.env.VIBE_CLIENT_ID;
|
|
63
67
|
const signingKey = process.env.VIBE_HMAC_KEY;
|
|
@@ -105,13 +109,17 @@ async function vibeServiceRequest<T = unknown>(
|
|
|
105
109
|
|
|
106
110
|
if (!res.ok) {
|
|
107
111
|
const errorText = await res.text();
|
|
108
|
-
|
|
112
|
+
const correlationId = randomUUID();
|
|
113
|
+
console.error(`[admin/sessions] Vibe request failed: ${options.method} ${endpoint} - ${res.status}`, { correlationId, bodyLength: errorText.length });
|
|
114
|
+
return { ok: false, status: res.status, data: null, error: `Vibe request failed (HTTP ${res.status})`, correlationId };
|
|
109
115
|
}
|
|
110
116
|
|
|
111
117
|
const body = await res.json();
|
|
112
118
|
return { ok: true, status: res.status, data: body };
|
|
113
119
|
} catch (error) {
|
|
114
|
-
|
|
120
|
+
const correlationId = randomUUID();
|
|
121
|
+
console.error(`[admin/sessions] Vibe request exception: ${options.method} ${endpoint}`, { correlationId, error });
|
|
122
|
+
return { ok: false, status: 0, data: null, error: 'Vibe request could not be completed', correlationId };
|
|
115
123
|
}
|
|
116
124
|
}
|
|
117
125
|
|
|
@@ -171,7 +179,7 @@ export function createSessionsHandler(config: AdminSessionsHandlerConfig) {
|
|
|
171
179
|
);
|
|
172
180
|
|
|
173
181
|
if (!result.ok) {
|
|
174
|
-
return NextResponse.json({ error: result.error }, { status: result.status || 500 });
|
|
182
|
+
return NextResponse.json({ error: result.error, code: 'UPSTREAM_ERROR', correlationId: result.correlationId }, { status: result.status || 500 });
|
|
175
183
|
}
|
|
176
184
|
|
|
177
185
|
// Extract sessions from response
|
|
@@ -214,7 +222,7 @@ export function createSessionsHandler(config: AdminSessionsHandlerConfig) {
|
|
|
214
222
|
);
|
|
215
223
|
|
|
216
224
|
if (!result.ok) {
|
|
217
|
-
return NextResponse.json({ error: result.error }, { status: result.status || 500 });
|
|
225
|
+
return NextResponse.json({ error: result.error, code: 'UPSTREAM_ERROR', correlationId: result.correlationId }, { status: result.status || 500 });
|
|
218
226
|
}
|
|
219
227
|
|
|
220
228
|
const sessions = result.data?.data || result.data?.documents || [];
|
|
@@ -262,7 +270,7 @@ export function createSessionsHandler(config: AdminSessionsHandlerConfig) {
|
|
|
262
270
|
);
|
|
263
271
|
|
|
264
272
|
if (!result.ok) {
|
|
265
|
-
return NextResponse.json({ error: result.error }, { status: result.status || 500 });
|
|
273
|
+
return NextResponse.json({ error: result.error, code: 'UPSTREAM_ERROR', correlationId: result.correlationId }, { status: result.status || 500 });
|
|
266
274
|
}
|
|
267
275
|
|
|
268
276
|
return NextResponse.json({ success: true });
|
|
@@ -287,7 +295,7 @@ export function createSessionsHandler(config: AdminSessionsHandlerConfig) {
|
|
|
287
295
|
);
|
|
288
296
|
|
|
289
297
|
if (!queryResult.ok) {
|
|
290
|
-
return NextResponse.json({ error: queryResult.error }, { status: queryResult.status || 500 });
|
|
298
|
+
return NextResponse.json({ error: queryResult.error, code: 'UPSTREAM_ERROR', correlationId: queryResult.correlationId }, { status: queryResult.status || 500 });
|
|
291
299
|
}
|
|
292
300
|
|
|
293
301
|
const sessions = queryResult.data?.data || queryResult.data?.documents || [];
|
|
@@ -16,6 +16,7 @@ import { NextRequest, NextResponse } from 'next/server';
|
|
|
16
16
|
import { getSession } from '../../server/auth';
|
|
17
17
|
import { getRedis } from '../../lib/redis';
|
|
18
18
|
import { ADMIN_ROLES, hasAnyRole } from '../../lib/roles';
|
|
19
|
+
import { randomUUID } from 'crypto';
|
|
19
20
|
const REDIS_SITE_LOG_KEY = 'vibe:site-logs:pending';
|
|
20
21
|
const REDIS_LOG_TTL = 7 * 24 * 60 * 60; // 1 week
|
|
21
22
|
|
|
@@ -106,9 +107,13 @@ export function createSiteLogsHandler(config: SiteLogsHandlerConfig) {
|
|
|
106
107
|
|
|
107
108
|
if (!response.ok) {
|
|
108
109
|
const errorText = await response.text();
|
|
109
|
-
|
|
110
|
+
// PAY-1424: never the raw upstream body in the log sink.
|
|
111
|
+
// PAY-1425: nor in the response - a fixed message + code, and a correlationId that
|
|
112
|
+
// matches this log line.
|
|
113
|
+
const correlationId = randomUUID();
|
|
114
|
+
console.error('[admin/site-logs] Vibe error:', { correlationId, status: response.status, bodyLength: errorText.length });
|
|
110
115
|
return NextResponse.json(
|
|
111
|
-
{ error: 'Failed to fetch site logs',
|
|
116
|
+
{ error: 'Failed to fetch site logs', code: 'UPSTREAM_ERROR', correlationId },
|
|
112
117
|
{ status: response.status }
|
|
113
118
|
);
|
|
114
119
|
}
|
|
@@ -116,9 +121,11 @@ export function createSiteLogsHandler(config: SiteLogsHandlerConfig) {
|
|
|
116
121
|
const data = await response.json();
|
|
117
122
|
return NextResponse.json(data);
|
|
118
123
|
} catch (error: any) {
|
|
119
|
-
|
|
124
|
+
// PAY-1425: exception text (hosts, URLs, driver messages) stays server-side.
|
|
125
|
+
const correlationId = randomUUID();
|
|
126
|
+
console.error('[admin/site-logs] Error:', { correlationId, error });
|
|
120
127
|
return NextResponse.json(
|
|
121
|
-
{ error:
|
|
128
|
+
{ error: 'Internal error', code: 'INTERNAL_ERROR', correlationId },
|
|
122
129
|
{ status: 500 }
|
|
123
130
|
);
|
|
124
131
|
}
|
|
@@ -192,9 +199,11 @@ export function createSiteLogsHandler(config: SiteLogsHandlerConfig) {
|
|
|
192
199
|
note: 'Log will appear after drain service processes it (every 5 seconds)',
|
|
193
200
|
}, { status: 201 });
|
|
194
201
|
} catch (error: any) {
|
|
195
|
-
|
|
202
|
+
// PAY-1425: exception text (hosts, URLs, driver messages) stays server-side.
|
|
203
|
+
const correlationId = randomUUID();
|
|
204
|
+
console.error('[admin/site-logs] POST Error:', { correlationId, error });
|
|
196
205
|
return NextResponse.json(
|
|
197
|
-
{ error:
|
|
206
|
+
{ error: 'Internal error', code: 'INTERNAL_ERROR', correlationId },
|
|
198
207
|
{ status: 500 }
|
|
199
208
|
);
|
|
200
209
|
}
|
|
@@ -232,9 +241,13 @@ export function createSiteLogsStatsHandler(config: SiteLogsHandlerConfig) {
|
|
|
232
241
|
|
|
233
242
|
if (!response.ok) {
|
|
234
243
|
const errorText = await response.text();
|
|
235
|
-
|
|
244
|
+
// PAY-1424: never the raw upstream body in the log sink.
|
|
245
|
+
// PAY-1425: nor in the response - a fixed message + code, and a correlationId that
|
|
246
|
+
// matches this log line.
|
|
247
|
+
const correlationId = randomUUID();
|
|
248
|
+
console.error('[admin/site-logs/stats] Vibe error:', { correlationId, status: response.status, bodyLength: errorText.length });
|
|
236
249
|
return NextResponse.json(
|
|
237
|
-
{ error: 'Failed to fetch stats',
|
|
250
|
+
{ error: 'Failed to fetch stats', code: 'UPSTREAM_ERROR', correlationId },
|
|
238
251
|
{ status: response.status }
|
|
239
252
|
);
|
|
240
253
|
}
|
|
@@ -243,9 +256,11 @@ export function createSiteLogsStatsHandler(config: SiteLogsHandlerConfig) {
|
|
|
243
256
|
// Ensure response matches expected format: { total, unique_users, by_level, by_category }
|
|
244
257
|
return NextResponse.json(data);
|
|
245
258
|
} catch (error: any) {
|
|
246
|
-
|
|
259
|
+
// PAY-1425: exception text (hosts, URLs, driver messages) stays server-side.
|
|
260
|
+
const correlationId = randomUUID();
|
|
261
|
+
console.error('[admin/site-logs/stats] Error:', { correlationId, error });
|
|
247
262
|
return NextResponse.json(
|
|
248
|
-
{ error:
|
|
263
|
+
{ error: 'Internal error', code: 'INTERNAL_ERROR', correlationId },
|
|
249
264
|
{ status: 500 }
|
|
250
265
|
);
|
|
251
266
|
}
|
|
@@ -282,9 +297,13 @@ export function createSiteLogsDrainHandler(config: SiteLogsHandlerConfig) {
|
|
|
282
297
|
|
|
283
298
|
if (!response.ok) {
|
|
284
299
|
const errorText = await response.text();
|
|
285
|
-
|
|
300
|
+
// PAY-1424: never the raw upstream body in the log sink.
|
|
301
|
+
// PAY-1425: nor in the response - a fixed message + code, and a correlationId that
|
|
302
|
+
// matches this log line.
|
|
303
|
+
const correlationId = randomUUID();
|
|
304
|
+
console.error('[admin/site-logs/drain] Vibe error:', { correlationId, status: response.status, bodyLength: errorText.length });
|
|
286
305
|
return NextResponse.json(
|
|
287
|
-
{ error: 'Failed to drain site logs',
|
|
306
|
+
{ error: 'Failed to drain site logs', code: 'UPSTREAM_ERROR', correlationId },
|
|
288
307
|
{ status: response.status }
|
|
289
308
|
);
|
|
290
309
|
}
|
|
@@ -292,9 +311,11 @@ export function createSiteLogsDrainHandler(config: SiteLogsHandlerConfig) {
|
|
|
292
311
|
const data = await response.json();
|
|
293
312
|
return NextResponse.json(data);
|
|
294
313
|
} catch (error: any) {
|
|
295
|
-
|
|
314
|
+
// PAY-1425: exception text (hosts, URLs, driver messages) stays server-side.
|
|
315
|
+
const correlationId = randomUUID();
|
|
316
|
+
console.error('[admin/site-logs/drain] Error:', { correlationId, error });
|
|
296
317
|
return NextResponse.json(
|
|
297
|
-
{ error:
|
|
318
|
+
{ error: 'Internal error', code: 'INTERNAL_ERROR', correlationId },
|
|
298
319
|
{ status: 500 }
|
|
299
320
|
);
|
|
300
321
|
}
|
|
@@ -354,9 +375,11 @@ export function createSiteLogsQueueHandler(config: SiteLogsHandlerConfig) {
|
|
|
354
375
|
status: queueLength === 0 ? 'empty' : 'pending',
|
|
355
376
|
});
|
|
356
377
|
} catch (error: any) {
|
|
357
|
-
|
|
378
|
+
// PAY-1425: exception text (hosts, URLs, driver messages) stays server-side.
|
|
379
|
+
const correlationId = randomUUID();
|
|
380
|
+
console.error('[admin/site-logs/queue] Error:', { correlationId, error });
|
|
358
381
|
return NextResponse.json(
|
|
359
|
-
{ error:
|
|
382
|
+
{ error: 'Internal error', code: 'INTERNAL_ERROR', correlationId },
|
|
360
383
|
{ status: 500 }
|
|
361
384
|
);
|
|
362
385
|
}
|
|
@@ -14,6 +14,7 @@ import { getStartupIDPConfig } from '../../lib/startup-init';
|
|
|
14
14
|
import { resolveClientKey } from '../../lib/client-key';
|
|
15
15
|
import { getRedis } from '../../lib/redis';
|
|
16
16
|
import { ADMIN_ROLES } from '../../lib/roles';
|
|
17
|
+
import { randomUUID } from 'crypto';
|
|
17
18
|
|
|
18
19
|
interface VibeRequestOptions {
|
|
19
20
|
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
@@ -46,7 +47,10 @@ async function checkAdminRole(request: NextRequest): Promise<{ isAdmin: boolean;
|
|
|
46
47
|
async function vibeServiceRequest<T = unknown>(
|
|
47
48
|
endpoint: string,
|
|
48
49
|
options: VibeRequestOptions
|
|
49
|
-
): Promise<{ ok: boolean; status: number; data: T | null; error?: string }> {
|
|
50
|
+
): Promise<{ ok: boolean; status: number; data: T | null; error?: string; correlationId?: string }> {
|
|
51
|
+
// PAY-1429: `error` can reach the HTTP caller, so it never carries upstream Vibe text or
|
|
52
|
+
// exception text. Failures return a fixed message + correlationId; the same id is on the
|
|
53
|
+
// server log line, which carries status + bodyLength only (PAY-1424).
|
|
50
54
|
const idpUrl = process.env.NEXT_PUBLIC_IDP_URL || process.env.IDP_URL;
|
|
51
55
|
const clientId = process.env.VIBE_CLIENT_ID;
|
|
52
56
|
const signingKey = process.env.VIBE_HMAC_KEY;
|
|
@@ -90,13 +94,17 @@ async function vibeServiceRequest<T = unknown>(
|
|
|
90
94
|
if (res.status === 204) return { ok: true, status: 204, data: null };
|
|
91
95
|
if (!res.ok) {
|
|
92
96
|
const errorText = await res.text();
|
|
93
|
-
|
|
97
|
+
const correlationId = randomUUID();
|
|
98
|
+
console.error(`[admin/stats] Vibe request failed: ${options.method} ${endpoint} - ${res.status}`, { correlationId, bodyLength: errorText.length });
|
|
99
|
+
return { ok: false, status: res.status, data: null, error: `Vibe request failed (HTTP ${res.status})`, correlationId };
|
|
94
100
|
}
|
|
95
101
|
|
|
96
102
|
const body = await res.json();
|
|
97
103
|
return { ok: true, status: res.status, data: body };
|
|
98
104
|
} catch (error) {
|
|
99
|
-
|
|
105
|
+
const correlationId = randomUUID();
|
|
106
|
+
console.error(`[admin/stats] Vibe request exception: ${options.method} ${endpoint}`, { correlationId, error });
|
|
107
|
+
return { ok: false, status: 0, data: null, error: 'Vibe request could not be completed', correlationId };
|
|
100
108
|
}
|
|
101
109
|
}
|
|
102
110
|
|
|
@@ -239,9 +247,11 @@ export function createStatsHandler(config: AdminStatsHandlerConfig) {
|
|
|
239
247
|
recentActivity,
|
|
240
248
|
});
|
|
241
249
|
} catch (error: any) {
|
|
242
|
-
|
|
250
|
+
// PAY-1429: exception text (hosts, URLs, driver messages) stays server-side.
|
|
251
|
+
const correlationId = randomUUID();
|
|
252
|
+
console.error('[admin/stats] Error:', { correlationId, error });
|
|
243
253
|
return NextResponse.json(
|
|
244
|
-
{ error:
|
|
254
|
+
{ error: 'Internal error', code: 'INTERNAL_ERROR', correlationId },
|
|
245
255
|
{ status: 500 }
|
|
246
256
|
);
|
|
247
257
|
}
|
|
@@ -12,6 +12,7 @@ import { getSession } from '../../server/auth';
|
|
|
12
12
|
import { getStartupIDPConfig } from '../../lib/startup-init';
|
|
13
13
|
import { resolveClientKey } from '../../lib/client-key';
|
|
14
14
|
import { ADMIN_ROLES, hasAnyRole } from '../../lib/roles';
|
|
15
|
+
import { randomUUID } from 'crypto';
|
|
15
16
|
|
|
16
17
|
interface VibeRequestOptions {
|
|
17
18
|
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
@@ -44,7 +45,10 @@ async function checkAdminRole(request: NextRequest): Promise<{ isAdmin: boolean;
|
|
|
44
45
|
async function vibeServiceRequest<T = unknown>(
|
|
45
46
|
endpoint: string,
|
|
46
47
|
options: VibeRequestOptions
|
|
47
|
-
): Promise<{ ok: boolean; status: number; data: T | null; error?: string }> {
|
|
48
|
+
): Promise<{ ok: boolean; status: number; data: T | null; error?: string; correlationId?: string }> {
|
|
49
|
+
// PAY-1429: `error` can reach the HTTP caller, so it never carries upstream Vibe text or
|
|
50
|
+
// exception text. Failures return a fixed message + correlationId; the same id is on the
|
|
51
|
+
// server log line, which carries status + bodyLength only (PAY-1424).
|
|
48
52
|
const idpUrl = process.env.NEXT_PUBLIC_IDP_URL || process.env.IDP_URL;
|
|
49
53
|
const clientId = process.env.VIBE_CLIENT_ID;
|
|
50
54
|
const signingKey = process.env.VIBE_HMAC_KEY;
|
|
@@ -89,13 +93,17 @@ async function vibeServiceRequest<T = unknown>(
|
|
|
89
93
|
if (res.status === 204) return { ok: true, status: 204, data: null };
|
|
90
94
|
if (!res.ok) {
|
|
91
95
|
const errorText = await res.text();
|
|
92
|
-
|
|
96
|
+
const correlationId = randomUUID();
|
|
97
|
+
console.error(`[admin/users] Vibe request failed: ${options.method} ${endpoint} - ${res.status}`, { correlationId, bodyLength: errorText.length });
|
|
98
|
+
return { ok: false, status: res.status, data: null, error: `Vibe request failed (HTTP ${res.status})`, correlationId };
|
|
93
99
|
}
|
|
94
100
|
|
|
95
101
|
const body = await res.json();
|
|
96
102
|
return { ok: true, status: res.status, data: body };
|
|
97
103
|
} catch (error) {
|
|
98
|
-
|
|
104
|
+
const correlationId = randomUUID();
|
|
105
|
+
console.error(`[admin/users] Vibe request exception: ${options.method} ${endpoint}`, { correlationId, error });
|
|
106
|
+
return { ok: false, status: 0, data: null, error: 'Vibe request could not be completed', correlationId };
|
|
99
107
|
}
|
|
100
108
|
}
|
|
101
109
|
|
|
@@ -151,7 +159,7 @@ export function createUsersHandler(config: AdminUsersHandlerConfig) {
|
|
|
151
159
|
);
|
|
152
160
|
|
|
153
161
|
if (!result.ok) {
|
|
154
|
-
return NextResponse.json({ error: result.error }, { status: result.status || 500 });
|
|
162
|
+
return NextResponse.json({ error: result.error, code: 'UPSTREAM_ERROR', correlationId: result.correlationId }, { status: result.status || 500 });
|
|
155
163
|
}
|
|
156
164
|
|
|
157
165
|
const rawUsers = result.data?.data || result.data?.documents || [];
|
|
@@ -187,7 +195,7 @@ export function createUsersHandler(config: AdminUsersHandlerConfig) {
|
|
|
187
195
|
);
|
|
188
196
|
|
|
189
197
|
if (!result.ok) {
|
|
190
|
-
return NextResponse.json({ error: result.error }, { status: result.status || 500 });
|
|
198
|
+
return NextResponse.json({ error: result.error, code: 'UPSTREAM_ERROR', correlationId: result.correlationId }, { status: result.status || 500 });
|
|
191
199
|
}
|
|
192
200
|
|
|
193
201
|
const users = result.data?.data || result.data?.documents || [];
|
|
@@ -218,7 +226,7 @@ export function createUsersHandler(config: AdminUsersHandlerConfig) {
|
|
|
218
226
|
);
|
|
219
227
|
|
|
220
228
|
if (!result.ok) {
|
|
221
|
-
return NextResponse.json({ error: result.error }, { status: result.status || 500 });
|
|
229
|
+
return NextResponse.json({ error: result.error, code: 'UPSTREAM_ERROR', correlationId: result.correlationId }, { status: result.status || 500 });
|
|
222
230
|
}
|
|
223
231
|
|
|
224
232
|
return NextResponse.json({ success: true });
|
|
@@ -231,7 +239,7 @@ export function createUsersHandler(config: AdminUsersHandlerConfig) {
|
|
|
231
239
|
);
|
|
232
240
|
|
|
233
241
|
if (!result.ok) {
|
|
234
|
-
return NextResponse.json({ error: result.error }, { status: result.status || 500 });
|
|
242
|
+
return NextResponse.json({ error: result.error, code: 'UPSTREAM_ERROR', correlationId: result.correlationId }, { status: result.status || 500 });
|
|
235
243
|
}
|
|
236
244
|
|
|
237
245
|
return NextResponse.json({ success: true });
|
|
@@ -13,6 +13,7 @@ import { getSession } from '../../server/auth';
|
|
|
13
13
|
import { getStartupIDPConfig } from '../../lib/startup-init';
|
|
14
14
|
import { resolveClientKey } from '../../lib/client-key';
|
|
15
15
|
import { ADMIN_ROLES, hasAnyRole } from '../../lib/roles';
|
|
16
|
+
import { randomUUID } from 'crypto';
|
|
16
17
|
|
|
17
18
|
interface VibeRequestOptions {
|
|
18
19
|
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
@@ -57,7 +58,11 @@ async function checkAdminRole(request: NextRequest): Promise<{ isAdmin: boolean;
|
|
|
57
58
|
async function vibeServiceRequest<T = unknown>(
|
|
58
59
|
endpoint: string,
|
|
59
60
|
options: VibeRequestOptions
|
|
60
|
-
): Promise<{ ok: boolean; status: number; data: T | null; error?: string }> {
|
|
61
|
+
): Promise<{ ok: boolean; status: number; data: T | null; error?: string; correlationId?: string }> {
|
|
62
|
+
// PAY-1425: `error` is returned to the HTTP caller verbatim by every handler below, so it
|
|
63
|
+
// must NEVER carry upstream text (raw Vibe/VibeSQL error bodies can hold SQL fragments,
|
|
64
|
+
// schema names or connection detail) or exception text. Failures return a fixed message
|
|
65
|
+
// plus a correlationId; the same id is on the server log line for operators.
|
|
61
66
|
const idpUrl = process.env.NEXT_PUBLIC_IDP_URL || process.env.IDP_URL;
|
|
62
67
|
const clientId = process.env.VIBE_CLIENT_ID;
|
|
63
68
|
const signingKey = process.env.VIBE_HMAC_KEY;
|
|
@@ -108,15 +113,18 @@ async function vibeServiceRequest<T = unknown>(
|
|
|
108
113
|
|
|
109
114
|
if (!res.ok) {
|
|
110
115
|
const errorText = await res.text();
|
|
111
|
-
|
|
112
|
-
|
|
116
|
+
// PAY-1424: never the raw upstream body in the log sink.
|
|
117
|
+
const correlationId = randomUUID();
|
|
118
|
+
console.error(`[Admin Vibe] Request failed: ${options.method} ${endpoint} - ${res.status}`, { correlationId, bodyLength: errorText.length });
|
|
119
|
+
return { ok: false, status: res.status, data: null, error: `Vibe request failed (HTTP ${res.status})`, correlationId };
|
|
113
120
|
}
|
|
114
121
|
|
|
115
122
|
const body = await res.json();
|
|
116
123
|
return { ok: true, status: res.status, data: body };
|
|
117
124
|
} catch (error) {
|
|
118
|
-
|
|
119
|
-
|
|
125
|
+
const correlationId = randomUUID();
|
|
126
|
+
console.error(`[Admin Vibe] Request exception: ${options.method} ${endpoint}`, { correlationId, error });
|
|
127
|
+
return { ok: false, status: 0, data: null, error: 'Vibe request could not be completed', correlationId };
|
|
120
128
|
}
|
|
121
129
|
}
|
|
122
130
|
|
|
@@ -140,7 +148,7 @@ export function createGetCollectionsHandler(config: AdminVibeHandlerConfig) {
|
|
|
140
148
|
|
|
141
149
|
if (!result.ok) {
|
|
142
150
|
return NextResponse.json(
|
|
143
|
-
{ success: false, error: { code: 'FETCH_ERROR', message: result.error } },
|
|
151
|
+
{ success: false, error: { code: 'FETCH_ERROR', message: result.error, correlationId: result.correlationId } },
|
|
144
152
|
{ status: result.status || 500 }
|
|
145
153
|
);
|
|
146
154
|
}
|
|
@@ -166,7 +174,7 @@ export function createGetTablesHandler(config: AdminVibeHandlerConfig) {
|
|
|
166
174
|
|
|
167
175
|
if (!result.ok) {
|
|
168
176
|
return NextResponse.json(
|
|
169
|
-
{ success: false, error: { code: 'FETCH_ERROR', message: result.error } },
|
|
177
|
+
{ success: false, error: { code: 'FETCH_ERROR', message: result.error, correlationId: result.correlationId } },
|
|
170
178
|
{ status: result.status || 500 }
|
|
171
179
|
);
|
|
172
180
|
}
|
|
@@ -200,7 +208,7 @@ export function createGetTableDataHandler(config: AdminVibeHandlerConfig) {
|
|
|
200
208
|
|
|
201
209
|
if (!result.ok) {
|
|
202
210
|
return NextResponse.json(
|
|
203
|
-
{ success: false, error: { code: 'FETCH_ERROR', message: result.error } },
|
|
211
|
+
{ success: false, error: { code: 'FETCH_ERROR', message: result.error, correlationId: result.correlationId } },
|
|
204
212
|
{ status: result.status || 500 }
|
|
205
213
|
);
|
|
206
214
|
}
|
|
@@ -228,7 +236,7 @@ export function createGetRecordHandler(config: AdminVibeHandlerConfig) {
|
|
|
228
236
|
if (!result.ok) {
|
|
229
237
|
const status = result.status === 404 ? 404 : result.status || 500;
|
|
230
238
|
return NextResponse.json(
|
|
231
|
-
{ success: false, error: { code: result.status === 404 ? 'NOT_FOUND' : 'FETCH_ERROR', message: result.error } },
|
|
239
|
+
{ success: false, error: { code: result.status === 404 ? 'NOT_FOUND' : 'FETCH_ERROR', message: result.error, correlationId: result.correlationId } },
|
|
232
240
|
{ status }
|
|
233
241
|
);
|
|
234
242
|
}
|
|
@@ -256,7 +264,7 @@ export function createUpdateRecordHandler(config: AdminVibeHandlerConfig) {
|
|
|
256
264
|
|
|
257
265
|
if (!result.ok) {
|
|
258
266
|
return NextResponse.json(
|
|
259
|
-
{ success: false, error: { code: 'UPDATE_ERROR', message: result.error } },
|
|
267
|
+
{ success: false, error: { code: 'UPDATE_ERROR', message: result.error, correlationId: result.correlationId } },
|
|
260
268
|
{ status: result.status || 500 }
|
|
261
269
|
);
|
|
262
270
|
}
|
|
@@ -283,7 +291,7 @@ export function createDeleteRecordHandler(config: AdminVibeHandlerConfig) {
|
|
|
283
291
|
|
|
284
292
|
if (!result.ok) {
|
|
285
293
|
return NextResponse.json(
|
|
286
|
-
{ success: false, error: { code: 'DELETE_ERROR', message: result.error } },
|
|
294
|
+
{ success: false, error: { code: 'DELETE_ERROR', message: result.error, correlationId: result.correlationId } },
|
|
287
295
|
{ status: result.status || 500 }
|
|
288
296
|
);
|
|
289
297
|
}
|
|
@@ -315,7 +323,7 @@ export function createQueryHandler(config: AdminVibeHandlerConfig) {
|
|
|
315
323
|
|
|
316
324
|
if (!result.ok) {
|
|
317
325
|
return NextResponse.json(
|
|
318
|
-
{ success: false, error: { code: 'QUERY_ERROR', message: result.error } },
|
|
326
|
+
{ success: false, error: { code: 'QUERY_ERROR', message: result.error, correlationId: result.correlationId } },
|
|
319
327
|
{ status: result.status || 500 }
|
|
320
328
|
);
|
|
321
329
|
}
|
|
@@ -14,7 +14,8 @@ export async function GET(_req: NextRequest) {
|
|
|
14
14
|
const json = JSON.parse(bodyText);
|
|
15
15
|
return NextResponse.json(json, { status: res.status });
|
|
16
16
|
} catch (e) {
|
|
17
|
-
|
|
17
|
+
// PAY-1427: no body slice - a non-JSON upstream is an error/proxy page of unknown content.
|
|
18
|
+
logger.error('[AUTH_JWKS] Upstream returned non-JSON', { status: res.status, bodyLength: bodyText.length, contentType: res.headers.get('content-type') });
|
|
18
19
|
return NextResponse.json({ error: 'Invalid JWKS response' }, { status: 502 });
|
|
19
20
|
}
|
|
20
21
|
} catch (error) {
|
|
@@ -162,9 +162,12 @@ export function createLoginHandler(config: LoginConfig) {
|
|
|
162
162
|
try {
|
|
163
163
|
data = JSON.parse(rawText);
|
|
164
164
|
} catch (jsonError) {
|
|
165
|
+
// PAY-1424: never the raw body - a non-JSON IDP/proxy error page can echo request
|
|
166
|
+
// context (credentials) back. Status, length and content-type are enough to triage.
|
|
165
167
|
console.error('[LOGIN] IDP returned non-JSON response', {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
+
status: backendResponse.status,
|
|
169
|
+
bodyLength: rawText.length,
|
|
170
|
+
contentType: backendResponse.headers.get('content-type'),
|
|
168
171
|
});
|
|
169
172
|
return NextResponse.json(
|
|
170
173
|
{ error: 'Invalid response from authentication service' },
|
|
@@ -187,16 +190,22 @@ export function createLoginHandler(config: LoginConfig) {
|
|
|
187
190
|
}
|
|
188
191
|
|
|
189
192
|
// Pass through error from IDP
|
|
193
|
+
// PAY-1424: shape only, never the envelope contents.
|
|
190
194
|
console.error('[LOGIN] IDP service error', {
|
|
191
195
|
status: backendResponse.status,
|
|
192
|
-
|
|
196
|
+
bodyKeys: data && typeof data === 'object' ? Object.keys(data) : undefined,
|
|
197
|
+
errorKeys: data?.error && typeof data?.error === 'object' ? Object.keys(data?.error) : undefined,
|
|
193
198
|
});
|
|
194
199
|
return addSecurityHeaders(NextResponse.json(data, { status: backendResponse.status }));
|
|
195
200
|
}
|
|
196
201
|
|
|
197
202
|
if (!data.success) {
|
|
198
203
|
// IDP returned a canonical error envelope with HTTP 200
|
|
199
|
-
|
|
204
|
+
// PAY-1424: shape only, never the envelope contents.
|
|
205
|
+
console.error('[LOGIN] IDP authentication failure', {
|
|
206
|
+
bodyKeys: data && typeof data === 'object' ? Object.keys(data) : undefined,
|
|
207
|
+
errorKeys: data?.error && typeof data?.error === 'object' ? Object.keys(data?.error) : undefined,
|
|
208
|
+
});
|
|
200
209
|
return addSecurityHeaders(NextResponse.json(data, { status: 200 }));
|
|
201
210
|
}
|
|
202
211
|
|
|
@@ -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 { assertAbsoluteIdpUrl } from '../../lib/idp-url-guard';
|
|
6
|
+
import { idpErrorEnvelope } from '../../lib/safe-error-response';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Passkey SIGN-IN ceremony (ANONYMOUS — no session exists yet):
|
|
@@ -54,6 +55,8 @@ async function anonCall(req: NextRequest, path: string) {
|
|
|
54
55
|
|
|
55
56
|
export async function optionsPOST(req: NextRequest) {
|
|
56
57
|
const { res, json } = await anonCall(req, '/api/ExternalAuth/passwordless/passkey/options');
|
|
58
|
+
// PAY-1438: anonymous route - on failure only the whitelisted envelope, never the IDP body.
|
|
59
|
+
if (!res.ok) return NextResponse.json(idpErrorEnvelope(json, 'Passkey sign-in failed'), { status: res.status });
|
|
57
60
|
return NextResponse.json(json ?? { success: false, message: 'Empty upstream response' }, { status: res.status });
|
|
58
61
|
}
|
|
59
62
|
|
|
@@ -108,5 +111,6 @@ export async function verifyPOST(req: NextRequest) {
|
|
|
108
111
|
cache: 'no-store',
|
|
109
112
|
});
|
|
110
113
|
const json = await res.json().catch(() => null);
|
|
114
|
+
if (!res.ok) return NextResponse.json(idpErrorEnvelope(json, 'Passkey sign-in failed'), { status: res.status });
|
|
111
115
|
return NextResponse.json(json ?? { success: false, message: 'Empty upstream response' }, { status: res.status });
|
|
112
116
|
}
|
|
@@ -40,6 +40,7 @@ import { getSession as getBetterAuthSession } from '../../server/auth';
|
|
|
40
40
|
import { getSession, updateSession, acquireRefreshLock, releaseRefreshLock, checkRefreshLock } from '../../lib/session-store';
|
|
41
41
|
import { computeTokenExpiries } from '../../lib/token-expiry';
|
|
42
42
|
import { extractKidFromToken } from '../../auth/utils/token-utils';
|
|
43
|
+
import { tokenLogId } from '../../lib/token-log-id';
|
|
43
44
|
|
|
44
45
|
interface RefreshConfig {
|
|
45
46
|
idpBaseUrl: string;
|
|
@@ -168,7 +169,7 @@ export function createRefreshHandler(config: RefreshConfig) {
|
|
|
168
169
|
console.log('║ REFRESH TOKEN ATTEMPT - PRE-FLIGHT ║');
|
|
169
170
|
console.log('╠══════════════════════════════════════════════════════════════════════════════╣');
|
|
170
171
|
console.log(`║ User: ${(userId || 'unknown').substring(0, 50).padEnd(50)} ║`);
|
|
171
|
-
console.log(`║ Session: ${sessionToken
|
|
172
|
+
console.log(`║ Session: ${tokenLogId(sessionToken)} ║`);
|
|
172
173
|
console.log('╠──────────────────────────────────────────────────────────────────────────────╣');
|
|
173
174
|
console.log(`║ Access Token Expires In: ${String(accessTokenExpiresIn).padEnd(10)} seconds ║`);
|
|
174
175
|
console.log(`║ Refresh Token Age: ${String(refreshTokenAge).padEnd(10)} seconds ║`);
|
|
@@ -520,12 +521,19 @@ export function createRefreshHandler(config: RefreshConfig) {
|
|
|
520
521
|
console.error(`║ ${whyItFailed.substring(0, 76).padEnd(76)} ║`);
|
|
521
522
|
console.error('╠──────────────────────────────────────────────────────────────────────────────╣');
|
|
522
523
|
console.error(`║ User: ${(userId || 'unknown').substring(0, 60).padEnd(60)}║`);
|
|
523
|
-
console.error(`║ Session: ${sessionToken
|
|
524
|
+
console.error(`║ Session: ${tokenLogId(sessionToken)} ║`);
|
|
524
525
|
console.error(`║ Resolution: ${(resolution || 'Check IDP logs').substring(0, 60).padEnd(60)}║`);
|
|
525
526
|
console.error('╚══════════════════════════════════════════════════════════════════════════════╝');
|
|
526
527
|
|
|
527
|
-
//
|
|
528
|
-
|
|
528
|
+
// NEVER the raw envelope (DotNetPert-Scout, PR 139 review / mail 58691): this used to print
|
|
529
|
+
// a stringified dump of responseData (first 1000 chars) - the same whole IDP body the :618
|
|
530
|
+
// "unsuccessful" branch stopped logging. The box above already carries status, code and
|
|
531
|
+
// flags; add envelope SHAPE only (key names), never values.
|
|
532
|
+
console.error('[AUTH_REFRESH] IDP error response shape', {
|
|
533
|
+
bodyKeys: responseData && typeof responseData === 'object' ? Object.keys(responseData) : undefined,
|
|
534
|
+
errorKeys: idpError && typeof idpError === 'object' ? Object.keys(idpError) : undefined,
|
|
535
|
+
errorHasMessage: !!idpError.message,
|
|
536
|
+
});
|
|
529
537
|
|
|
530
538
|
// If IDP signals to discard token, clear it from Redis
|
|
531
539
|
if (discardToken) {
|
|
@@ -541,7 +549,7 @@ export function createRefreshHandler(config: RefreshConfig) {
|
|
|
541
549
|
reason: 'IDP_DISCARD_TOKEN',
|
|
542
550
|
errorCode,
|
|
543
551
|
userId,
|
|
544
|
-
sessionToken: sessionToken
|
|
552
|
+
sessionToken: tokenLogId(sessionToken),
|
|
545
553
|
hadRefreshToken: !!currentSession.idpRefreshToken,
|
|
546
554
|
stack: new Error().stack
|
|
547
555
|
});
|
|
@@ -585,7 +593,16 @@ export function createRefreshHandler(config: RefreshConfig) {
|
|
|
585
593
|
Object.prototype.hasOwnProperty.call(responseData, 'meta');
|
|
586
594
|
|
|
587
595
|
if (!isCompliant) {
|
|
588
|
-
|
|
596
|
+
// NEVER the raw body (DotNetPert-Scout, mail 58678): a non-compliant response has an
|
|
597
|
+
// UNKNOWN shape by definition, so there is no basis to assume it is free of tokens or
|
|
598
|
+
// other IDP-side secrets. Log shape metadata only - same "kind, never value" reasoning
|
|
599
|
+
// as the session-store.ts / refresh.ts REFRESH_TOKEN_REMOVAL fixes in PR 138.
|
|
600
|
+
console.error('[AUTH_REFRESH] Upstream non-compliant IDP response', {
|
|
601
|
+
bodyType: typeof responseData,
|
|
602
|
+
bodyKeys: responseData && typeof responseData === 'object' ? Object.keys(responseData) : undefined,
|
|
603
|
+
hasSuccessKey: !!(responseData && typeof responseData === 'object' && Object.prototype.hasOwnProperty.call(responseData, 'success')),
|
|
604
|
+
hasMetaKey: !!(responseData && typeof responseData === 'object' && Object.prototype.hasOwnProperty.call(responseData, 'meta')),
|
|
605
|
+
});
|
|
589
606
|
return NextResponse.json({
|
|
590
607
|
error: 'Upstream non-compliance',
|
|
591
608
|
code: 'UPSTREAM_SERVICE_ERROR',
|
|
@@ -601,10 +618,17 @@ export function createRefreshHandler(config: RefreshConfig) {
|
|
|
601
618
|
const discardToken = idpError.discard_token === true;
|
|
602
619
|
const retryable = idpError.retryable === true;
|
|
603
620
|
|
|
621
|
+
// NEVER the raw envelope (DotNetPert-Scout, mail 58678): `responseData` here is the
|
|
622
|
+
// WHOLE IDP response body, and its `error` sub-object's contents are not something this
|
|
623
|
+
// handler controls or has audited - printing it wholesale would ship whatever the IDP
|
|
624
|
+
// decided to put there straight to the log sink. Log the fields THIS handler already
|
|
625
|
+
// extracts and acts on (retryable, plus envelope-shape flags for diagnosis) instead.
|
|
604
626
|
console.warn('[AUTH_REFRESH] IDP refresh unsuccessful', {
|
|
605
627
|
code: errorCode,
|
|
606
628
|
discardToken,
|
|
607
|
-
|
|
629
|
+
retryable,
|
|
630
|
+
errorHasMessage: !!idpError.message,
|
|
631
|
+
errorKeys: Object.keys(idpError),
|
|
608
632
|
});
|
|
609
633
|
|
|
610
634
|
if (discardToken) {
|
|
@@ -612,7 +636,7 @@ export function createRefreshHandler(config: RefreshConfig) {
|
|
|
612
636
|
reason: 'IDP_SUCCESS_FALSE_DISCARD',
|
|
613
637
|
errorCode,
|
|
614
638
|
userId,
|
|
615
|
-
sessionToken: sessionToken
|
|
639
|
+
sessionToken: tokenLogId(sessionToken),
|
|
616
640
|
hadRefreshToken: !!currentSession.idpRefreshToken,
|
|
617
641
|
stack: new Error().stack
|
|
618
642
|
});
|
|
@@ -698,9 +722,12 @@ export function createRefreshHandler(config: RefreshConfig) {
|
|
|
698
722
|
console.error('[AUTH_REFRESH] ⚠️ REFRESH_TOKEN_REMOVAL: No new refresh token in IDP response', {
|
|
699
723
|
reason: 'NO_NEW_REFRESH_TOKEN_IN_RESPONSE',
|
|
700
724
|
userId,
|
|
701
|
-
sessionToken: sessionToken
|
|
725
|
+
sessionToken: tokenLogId(sessionToken),
|
|
702
726
|
hadRefreshToken: true,
|
|
703
|
-
|
|
727
|
+
// Kind only, never the value (credential). This branch runs when refreshToken is not a
|
|
728
|
+
// non-empty string, so the old raw value was almost always empty - but a token-shaped
|
|
729
|
+
// non-string would have been printed. refreshTokenType below already carries the kind.
|
|
730
|
+
refreshTokenFromResponsePresent: refreshToken !== undefined && refreshToken !== null && refreshToken !== '',
|
|
704
731
|
refreshTokenType: typeof refreshToken,
|
|
705
732
|
payloadKeys: Object.keys(payload || {}),
|
|
706
733
|
stack: new Error().stack
|
|
@@ -752,7 +779,7 @@ export function createRefreshHandler(config: RefreshConfig) {
|
|
|
752
779
|
expiresAt: new Date(accessTokenExpires).toISOString(),
|
|
753
780
|
userId,
|
|
754
781
|
hasNewRefreshToken: hasNewRefresh,
|
|
755
|
-
|
|
782
|
+
hasAccessToken: !!accessToken // PAY-1427: was tokenPreview (first 20 chars of the new access token)
|
|
756
783
|
});
|
|
757
784
|
|
|
758
785
|
} catch (error) {
|