@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
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveVerifiedSessionCookie = resolveVerifiedSessionCookie;
|
|
4
|
+
exports.resolveVerifiedSessionToken = resolveVerifiedSessionToken;
|
|
5
|
+
exports.resolveVerifiedSessionTokenFromRequest = resolveVerifiedSessionTokenFromRequest;
|
|
6
|
+
/**
|
|
7
|
+
* PAY-1476 / card 372275 (R941 s1b) - THE ONE SHARED VERIFIED RESOLVER for a session token taken from a cookie.
|
|
8
|
+
*
|
|
9
|
+
* THE DEFECT (4.4.5, measured at the deployed portal by QAPert-NightHawk, mail 59030):
|
|
10
|
+
* Module A server/decode-session.ts the ba-hmac branch took `{token}.{anything}` and used segment 0 unread.
|
|
11
|
+
* Module B server/auth.ts Path B took the token from ANY recognised shape and never verified anything;
|
|
12
|
+
* a forged JWS resolved there too.
|
|
13
|
+
* Both then treated "a Redis record exists" as the validation. So a leaked session token string was a bearer
|
|
14
|
+
* credential with no signature needed, including on the portal's admin API gate (api-admin-gate -> decodeSession).
|
|
15
|
+
*
|
|
16
|
+
* THE RULE THIS MODULE ENFORCES: a cookie yields a session token ONLY after its signature checks out against
|
|
17
|
+
* the auth secret. Every caller that turns a cookie into a token for the session store awaits this function.
|
|
18
|
+
* lib/session-cookie-shape.ts stays classification-only and secret-free (it cannot verify, by construction).
|
|
19
|
+
*
|
|
20
|
+
* ba-hmac `{token}.{sig}` as Better Auth mints it (better-call signCookieValue): sig =
|
|
21
|
+
* base64(HMAC-SHA256(secret, token)), standard padding, 44 characters. Compared with
|
|
22
|
+
* crypto.timingSafeEqual over equal-length buffers.
|
|
23
|
+
* jws legacy NextAuth-era cookie: jose jwtVerify with the same secret, then the sessionToken
|
|
24
|
+
* (or redisSessionId) claim.
|
|
25
|
+
* anything else, a missing secret, or a failed check -> null. Never throws.
|
|
26
|
+
*
|
|
27
|
+
* THE SECRET is the auth secret from getIDPClientConfig(), which resolves env-first (BETTER_AUTH_SECRET,
|
|
28
|
+
* NEXTAUTH_SECRET, then IdP config; PAY-1460). The cookie HMAC key IS the auth secret, so the resolver and the
|
|
29
|
+
* config loader cannot disagree about which secret signs sessions (BAPert 59034).
|
|
30
|
+
*/
|
|
31
|
+
const crypto_1 = require("crypto");
|
|
32
|
+
const jose_1 = require("jose");
|
|
33
|
+
const idp_client_config_1 = require("../lib/idp-client-config");
|
|
34
|
+
const session_cookie_shape_1 = require("../lib/session-cookie-shape");
|
|
35
|
+
/** Better Auth's signature: base64 of a 32-byte HMAC-SHA256, padded = 44 chars ending in '='. */
|
|
36
|
+
const BA_SIGNATURE_LENGTH = 44;
|
|
37
|
+
async function authSecret() {
|
|
38
|
+
try {
|
|
39
|
+
const config = await (0, idp_client_config_1.getIDPClientConfig)();
|
|
40
|
+
return config?.authSecret ? String(config.authSecret) : null;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function verifyBaHmac(rawValue, secret) {
|
|
47
|
+
const dot = rawValue.lastIndexOf('.');
|
|
48
|
+
if (dot < 1)
|
|
49
|
+
return null;
|
|
50
|
+
const token = rawValue.slice(0, dot);
|
|
51
|
+
let signature = rawValue.slice(dot + 1);
|
|
52
|
+
if (signature.includes('%')) {
|
|
53
|
+
try {
|
|
54
|
+
signature = decodeURIComponent(signature);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (signature.length !== BA_SIGNATURE_LENGTH || !signature.endsWith('='))
|
|
61
|
+
return null;
|
|
62
|
+
const expected = (0, crypto_1.createHmac)('sha256', secret).update(token, 'utf8').digest('base64');
|
|
63
|
+
const a = Buffer.from(signature, 'utf8');
|
|
64
|
+
const b = Buffer.from(expected, 'utf8');
|
|
65
|
+
if (a.length !== b.length)
|
|
66
|
+
return null;
|
|
67
|
+
return (0, crypto_1.timingSafeEqual)(a, b) ? token : null;
|
|
68
|
+
}
|
|
69
|
+
async function verifyJws(rawValue, secret) {
|
|
70
|
+
try {
|
|
71
|
+
const { payload } = await (0, jose_1.jwtVerify)(rawValue, new TextEncoder().encode(secret));
|
|
72
|
+
const claim = payload.sessionToken ?? payload.redisSessionId;
|
|
73
|
+
return typeof claim === 'string' && claim ? { token: claim, shape: 'jws', payload } : null;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** The verified session cookie (token, shape, JWS payload), or null unless its signature verifies. */
|
|
80
|
+
async function resolveVerifiedSessionCookie(rawValue) {
|
|
81
|
+
if (!rawValue)
|
|
82
|
+
return null;
|
|
83
|
+
const shaped = (0, session_cookie_shape_1.extractSessionTokenFromCookieValue)(rawValue);
|
|
84
|
+
if (!shaped)
|
|
85
|
+
return null;
|
|
86
|
+
const secret = await authSecret();
|
|
87
|
+
if (!secret)
|
|
88
|
+
return null;
|
|
89
|
+
if (shaped.shape === 'ba-hmac') {
|
|
90
|
+
const token = verifyBaHmac(rawValue, secret);
|
|
91
|
+
return token ? { token, shape: 'ba-hmac' } : null;
|
|
92
|
+
}
|
|
93
|
+
return verifyJws(rawValue, secret);
|
|
94
|
+
}
|
|
95
|
+
/** The session token carried by a raw session-cookie value, or null unless its signature verifies. */
|
|
96
|
+
async function resolveVerifiedSessionToken(rawValue) {
|
|
97
|
+
const verified = await resolveVerifiedSessionCookie(rawValue);
|
|
98
|
+
return verified ? verified.token : null;
|
|
99
|
+
}
|
|
100
|
+
/** Same, reading the session cookie (every name it is minted under, chunks joined) from a Request. */
|
|
101
|
+
async function resolveVerifiedSessionTokenFromRequest(request) {
|
|
102
|
+
return resolveVerifiedSessionToken((0, session_cookie_shape_1.extractSessionCookieValue)(request));
|
|
103
|
+
}
|
package/dist/stores/authStore.js
CHANGED
|
@@ -55,6 +55,7 @@ const logger_1 = require("../config/logger");
|
|
|
55
55
|
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
|
+
const token_log_id_1 = require("../lib/token-log-id");
|
|
58
59
|
// ===============================
|
|
59
60
|
// CACHE CONFIGURATION
|
|
60
61
|
// ===============================
|
|
@@ -303,17 +304,26 @@ exports.useAuthStore = (0, zustand_1.create)()((0, middleware_1.devtools)((set,
|
|
|
303
304
|
},
|
|
304
305
|
forceLogoutAndRedirect: async (reason) => {
|
|
305
306
|
const state = get();
|
|
307
|
+
// Same truncation convention as the server-side session logs (session-store.ts,
|
|
308
|
+
// refresh.ts): a session token is a bearer credential, and this line goes to the
|
|
309
|
+
// browser console (and whatever aggregates authLogger client-side) - full value
|
|
310
|
+
// was leftover debug logging (DotNetPert-Scout, mail 58678).
|
|
311
|
+
// PAY-1505: the ternary that used to stand here fell back to the RAW token on its false arm.
|
|
312
|
+
// Safe only by the unenforced property that the arm is reached when the token is falsy -
|
|
313
|
+
// exactly the shape the source guard exists to refuse. tokenLogId already answers 'none'
|
|
314
|
+
// for an absent token, so the fallback is not needed.
|
|
315
|
+
const sessionTokenPrefix = (0, token_log_id_1.tokenLogId)(state.session?.sessionToken);
|
|
306
316
|
// AGGRESSIVE LOGGING TO TRACK EXECUTION
|
|
307
317
|
console.error('🚨 FORCE LOGOUT INITIATED 🚨', {
|
|
308
318
|
reason,
|
|
309
319
|
userId: state.user?.id,
|
|
310
|
-
sessionToken:
|
|
320
|
+
sessionToken: sessionTokenPrefix,
|
|
311
321
|
timestamp: new Date().toISOString()
|
|
312
322
|
});
|
|
313
323
|
logger_1.authLogger.error('[AuthStore] Force logout initiated', {
|
|
314
324
|
reason,
|
|
315
325
|
userId: state.user?.id,
|
|
316
|
-
sessionToken:
|
|
326
|
+
sessionToken: sessionTokenPrefix
|
|
317
327
|
});
|
|
318
328
|
try {
|
|
319
329
|
// TODO: Step 1: Mark session as force-invalidated in Redis (temp disabled for client build)
|
|
@@ -1503,7 +1513,8 @@ exports.useAuthStore = (0, zustand_1.create)()((0, middleware_1.devtools)((set,
|
|
|
1503
1513
|
const state = get();
|
|
1504
1514
|
// If this event is about the current user, update their state
|
|
1505
1515
|
if (data.userId === state.user?.id && state.session) {
|
|
1506
|
-
|
|
1516
|
+
// PAY-1427: the event is user state; log its shape, not its contents.
|
|
1517
|
+
logger_1.authLogger.info('[AuthStore] Updating current user state from SignalR:', { keys: Object.keys(data || {}) });
|
|
1507
1518
|
// ... user state update logic would go here
|
|
1508
1519
|
}
|
|
1509
1520
|
},
|
package/dist/types/recovery.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type RecoveryStep = 'initiate' | 'select-method' | 'verify-code' | 'set-password' | 'complete';
|
|
1
|
+
export type RecoveryStep = 'initiate' | 'select-method' | 'verify-code' | 'set-password' | 'requested' | 'complete';
|
|
2
2
|
export interface RecoverySession {
|
|
3
3
|
recoveryToken: string;
|
|
4
4
|
email: string;
|
package/dist/utils/api.d.ts
CHANGED
|
@@ -25,6 +25,24 @@ export interface ApiResponse<T> {
|
|
|
25
25
|
message?: string;
|
|
26
26
|
}
|
|
27
27
|
export declare function apiFetch<T>(url: string, options?: ApiOptions): Promise<ApiResponse<T>>;
|
|
28
|
+
/**
|
|
29
|
+
* PAY-1591: what the user reads when the LAST recovery step fails. Four outcomes, told apart only by codes the IdP really sends
|
|
30
|
+
* (see resetFailureText): a password-policy refusal names the failed rules; an explicit expired/used token says so and links back
|
|
31
|
+
* to recovery; any other 400/422 gets a neutral 'check the requirements and try again' (the user's recovery session is kept); and
|
|
32
|
+
* a server or transport failure gets a retry sentence. None of them is the old fixed "Failed to reset password".
|
|
33
|
+
*/
|
|
34
|
+
export declare const RECOVERY_EXPIRED_TEXT = "This reset link has expired or was already used. Start password recovery again.";
|
|
35
|
+
export declare const RESET_NEUTRAL_TEXT = "We couldn't set that password. Check it meets the requirements shown and try again.";
|
|
36
|
+
/**
|
|
37
|
+
* Three tiers for a 400/422 from reset (rigpert 60822, QAPert 60820), decided ONLY on codes the IdP actually sends:
|
|
38
|
+
* policy details.code password_policy (projected by the handler as password_policy) -> the rule sentences
|
|
39
|
+
* tokenInvalid an EXPLICIT expired/used signal (token_expired / token_used / Identity InvalidToken, projected as
|
|
40
|
+
* reset_token_invalid) -> 'link expired, start again'
|
|
41
|
+
* anything else -> NEUTRAL, which must NOT tell the user their link is dead: the served IdP sends a weak-password refusal with
|
|
42
|
+
* no code, and 'expired' there loops them after they spent their emailed code. The recovery session is kept, so
|
|
43
|
+
* they can retry.
|
|
44
|
+
*/
|
|
45
|
+
export declare function resetFailureText(status: number, rules?: string[], policy?: boolean, tokenInvalid?: boolean): string;
|
|
28
46
|
export declare class AccountApi {
|
|
29
47
|
/**
|
|
30
48
|
* Get masked user info (email, phone, authenticator status)
|
package/dist/utils/api.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.accountApi = exports.AccountApi = exports.ApiError = void 0;
|
|
3
|
+
exports.accountApi = exports.AccountApi = exports.RESET_NEUTRAL_TEXT = exports.RECOVERY_EXPIRED_TEXT = exports.ApiError = void 0;
|
|
4
4
|
exports.apiFetch = apiFetch;
|
|
5
|
+
exports.resetFailureText = resetFailureText;
|
|
5
6
|
const standardized_client_api_1 = require("../lib/standardized-client-api");
|
|
7
|
+
const password_rules_1 = require("../lib/password-rules");
|
|
6
8
|
class ApiError extends Error {
|
|
7
9
|
type;
|
|
8
10
|
title;
|
|
@@ -74,6 +76,34 @@ async function apiFetch(url, options = {}) {
|
|
|
74
76
|
}
|
|
75
77
|
// DELETED: apiFetchAuth - Use clientApi from @/lib/client-api instead for proper token refresh handling
|
|
76
78
|
// Account API for masked info and 2FA
|
|
79
|
+
/**
|
|
80
|
+
* PAY-1591: what the user reads when the LAST recovery step fails. Four outcomes, told apart only by codes the IdP really sends
|
|
81
|
+
* (see resetFailureText): a password-policy refusal names the failed rules; an explicit expired/used token says so and links back
|
|
82
|
+
* to recovery; any other 400/422 gets a neutral 'check the requirements and try again' (the user's recovery session is kept); and
|
|
83
|
+
* a server or transport failure gets a retry sentence. None of them is the old fixed "Failed to reset password".
|
|
84
|
+
*/
|
|
85
|
+
exports.RECOVERY_EXPIRED_TEXT = 'This reset link has expired or was already used. Start password recovery again.';
|
|
86
|
+
exports.RESET_NEUTRAL_TEXT = "We couldn't set that password. Check it meets the requirements shown and try again.";
|
|
87
|
+
/**
|
|
88
|
+
* Three tiers for a 400/422 from reset (rigpert 60822, QAPert 60820), decided ONLY on codes the IdP actually sends:
|
|
89
|
+
* policy details.code password_policy (projected by the handler as password_policy) -> the rule sentences
|
|
90
|
+
* tokenInvalid an EXPLICIT expired/used signal (token_expired / token_used / Identity InvalidToken, projected as
|
|
91
|
+
* reset_token_invalid) -> 'link expired, start again'
|
|
92
|
+
* anything else -> NEUTRAL, which must NOT tell the user their link is dead: the served IdP sends a weak-password refusal with
|
|
93
|
+
* no code, and 'expired' there loops them after they spent their emailed code. The recovery session is kept, so
|
|
94
|
+
* they can retry.
|
|
95
|
+
*/
|
|
96
|
+
function resetFailureText(status, rules = [], policy = false, tokenInvalid = false) {
|
|
97
|
+
if (status === 400 || status === 422) {
|
|
98
|
+
if (tokenInvalid && !policy)
|
|
99
|
+
return exports.RECOVERY_EXPIRED_TEXT;
|
|
100
|
+
if (!policy)
|
|
101
|
+
return exports.RESET_NEUTRAL_TEXT;
|
|
102
|
+
const base = "That password wasn't accepted.";
|
|
103
|
+
return rules.length ? `${base} ${(0, password_rules_1.passwordRuleSentences)(rules)}` : `${base} Try a different one; the rules are shown above.`;
|
|
104
|
+
}
|
|
105
|
+
return "We couldn't reset your password just now. Please try again.";
|
|
106
|
+
}
|
|
77
107
|
class AccountApi {
|
|
78
108
|
/**
|
|
79
109
|
* Get masked user info (email, phone, authenticator status)
|
|
@@ -87,16 +117,15 @@ class AccountApi {
|
|
|
87
117
|
// Authenticated request - proxy through Next API to IDP
|
|
88
118
|
// Empty body: user info comes from JWT token
|
|
89
119
|
const result = await standardized_client_api_1.standardizedApi.post('/api/account/masked-info', {}, accessToken);
|
|
120
|
+
// PAY-1427: masked email/phone are still user data - log the response shape, never its values.
|
|
90
121
|
console.log('[DEBUG] getMaskedInfo result:', {
|
|
91
122
|
success: (0, standardized_client_api_1.isApiSuccess)(result),
|
|
92
123
|
hasData: (0, standardized_client_api_1.isApiSuccess)(result) && !!result.data,
|
|
93
|
-
rawData: (0, standardized_client_api_1.isApiSuccess)(result) ? result.data : null,
|
|
94
124
|
dataFields: (0, standardized_client_api_1.isApiSuccess)(result) && result.data ? Object.keys(result.data) : []
|
|
95
125
|
});
|
|
96
|
-
console.log('[DEBUG] getMaskedInfo raw data structure:', JSON.stringify((0, standardized_client_api_1.isApiSuccess)(result) ? result.data : result, null, 2));
|
|
97
126
|
if (!(0, standardized_client_api_1.isApiSuccess)(result)) {
|
|
98
127
|
const errorMessage = (0, standardized_client_api_1.isApiError)(result) ? result.message : 'Failed to get masked info';
|
|
99
|
-
console.error('[DEBUG] getMaskedInfo failed:', result);
|
|
128
|
+
console.error('[DEBUG] getMaskedInfo failed:', { keys: result && typeof result === 'object' ? Object.keys(result) : typeof result });
|
|
100
129
|
throw new ApiError(errorMessage, result);
|
|
101
130
|
}
|
|
102
131
|
// Use only snake_case fields from backend response
|
|
@@ -116,6 +145,9 @@ class AccountApi {
|
|
|
116
145
|
headers: { 'Content-Type': 'application/json' },
|
|
117
146
|
body: JSON.stringify({ email }),
|
|
118
147
|
});
|
|
148
|
+
// PAY-1588: a throttled caller (429) must look exactly like an unknown one - no session, no distinguishing error.
|
|
149
|
+
if (res.status === 429)
|
|
150
|
+
return { success: false, data: {} };
|
|
119
151
|
if (!res.ok)
|
|
120
152
|
throw new Error('Failed to initiate recovery');
|
|
121
153
|
return res.json();
|
|
@@ -154,13 +186,23 @@ class AccountApi {
|
|
|
154
186
|
headers: { 'Content-Type': 'application/json' },
|
|
155
187
|
body: JSON.stringify({
|
|
156
188
|
email,
|
|
157
|
-
|
|
189
|
+
token: resetToken,
|
|
158
190
|
new_password: newPassword,
|
|
159
191
|
confirm_password: confirmPassword,
|
|
160
192
|
}),
|
|
161
193
|
});
|
|
162
|
-
if (!res.ok)
|
|
163
|
-
|
|
194
|
+
if (!res.ok) {
|
|
195
|
+
const body = await res.json().catch(() => null);
|
|
196
|
+
const policy = !!body?.password_policy;
|
|
197
|
+
const rules = (0, password_rules_1.knownPasswordRules)(body?.password_policy?.rules);
|
|
198
|
+
const tokenInvalid = body?.reset_token_invalid === true;
|
|
199
|
+
const err = new Error(resetFailureText(res.status, rules, policy, tokenInvalid));
|
|
200
|
+
if (rules.length)
|
|
201
|
+
err.rules = rules;
|
|
202
|
+
if ((res.status === 400 || res.status === 422) && tokenInvalid && !policy)
|
|
203
|
+
err.code = 'RECOVERY_EXPIRED';
|
|
204
|
+
throw err;
|
|
205
|
+
}
|
|
164
206
|
return res.json();
|
|
165
207
|
}
|
|
166
208
|
async resetPassword(data) {
|
|
@@ -171,8 +213,8 @@ class AccountApi {
|
|
|
171
213
|
body: JSON.stringify({
|
|
172
214
|
email: data.email,
|
|
173
215
|
token: data.token,
|
|
174
|
-
|
|
175
|
-
|
|
216
|
+
new_password: data.password,
|
|
217
|
+
confirm_password: data.password_confirmation
|
|
176
218
|
})
|
|
177
219
|
});
|
|
178
220
|
const result = await res.json().catch(() => ({}));
|
package/dist/vibe/client.d.ts
CHANGED
|
@@ -49,7 +49,13 @@ export interface VibeRetryConfig {
|
|
|
49
49
|
retryOnRateLimit?: boolean;
|
|
50
50
|
}
|
|
51
51
|
export interface VibeClientConfig {
|
|
52
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* Base URL for the Vibe API (defaults to VIBE_API_URL env var).
|
|
54
|
+
*
|
|
55
|
+
* PAY-1767: proxy mode does NOT need this. Every request is sent to the IDP proxy as an endpoint
|
|
56
|
+
* PATH (`/v1/collections/...`), which is what the proxy validates and signs - it never builds a
|
|
57
|
+
* Vibe URL from this value. It is retained only for compatibility with consumers that still read it.
|
|
58
|
+
*/
|
|
53
59
|
baseUrl?: string;
|
|
54
60
|
/** Vibe Client ID (defaults to VIBE_CLIENT_ID env var) */
|
|
55
61
|
clientId?: string;
|
|
@@ -64,6 +70,15 @@ export interface VibeClientConfig {
|
|
|
64
70
|
* When set, requests are routed through the IDP proxy instead of directly to Vibe.
|
|
65
71
|
*/
|
|
66
72
|
appSigningKey?: string;
|
|
73
|
+
/**
|
|
74
|
+
* PAY-1767: which COLLECTION the table delegates address (defaults to VIBE_COLLECTION, then 'vibe_app').
|
|
75
|
+
*
|
|
76
|
+
* The Vibe API route is `/v1/collections/{collection}/tables/{table}`. This client used to hardcode
|
|
77
|
+
* `/api/v1/vibe_app/{table}` - a collection baked into the path with no route behind it. Pin the
|
|
78
|
+
* collection explicitly at the call site when your tables do not live in `vibe_app` (the UMI
|
|
79
|
+
* VIBE_COLLECTION-double-duty lesson, POST-INSTALL.md item 16).
|
|
80
|
+
*/
|
|
81
|
+
collection?: string;
|
|
67
82
|
/** Custom fetch function (for testing or custom auth) */
|
|
68
83
|
fetch?: typeof fetch;
|
|
69
84
|
/** Retry configuration for transient errors */
|
|
@@ -92,7 +107,13 @@ export interface FindManyOptions<T> {
|
|
|
92
107
|
take?: number;
|
|
93
108
|
skip?: number;
|
|
94
109
|
}
|
|
95
|
-
/**
|
|
110
|
+
/**
|
|
111
|
+
* Options for findUnique.
|
|
112
|
+
*
|
|
113
|
+
* PAY-1767 (NightHawk 64534 MUST 2): `where.id` is the DOCUMENT id - the storage-layer primary key the
|
|
114
|
+
* route constrains (`{id:int}`), NOT a row's own `id` column. The unwrapped row exposes it as
|
|
115
|
+
* `_vibe_doc_id`; use THAT value for a subsequent update/delete.
|
|
116
|
+
*/
|
|
96
117
|
export interface FindUniqueOptions {
|
|
97
118
|
where: {
|
|
98
119
|
id: number;
|
|
@@ -102,14 +123,16 @@ export interface FindUniqueOptions {
|
|
|
102
123
|
export interface CreateOptions<T> {
|
|
103
124
|
data: Omit<T, 'id' | 'created_at' | 'updated_at'>;
|
|
104
125
|
}
|
|
105
|
-
/**
|
|
126
|
+
/**
|
|
127
|
+
* Options for update. `where.id` is the DOCUMENT id (see FindUniqueOptions), not a row's own id column.
|
|
128
|
+
*/
|
|
106
129
|
export interface UpdateOptions<T> {
|
|
107
130
|
where: {
|
|
108
131
|
id: number;
|
|
109
132
|
};
|
|
110
133
|
data: Partial<Omit<T, 'id' | 'created_at' | 'updated_at'>>;
|
|
111
134
|
}
|
|
112
|
-
/** Options for delete */
|
|
135
|
+
/** Options for delete. `where.id` is the DOCUMENT id (see FindUniqueOptions). */
|
|
113
136
|
export interface DeleteOptions {
|
|
114
137
|
where: {
|
|
115
138
|
id: number;
|
|
@@ -122,8 +145,36 @@ export interface CountOptions<T> {
|
|
|
122
145
|
/** Result of findMany with metadata */
|
|
123
146
|
export interface FindManyResult<T> {
|
|
124
147
|
data: T[];
|
|
148
|
+
/**
|
|
149
|
+
* PAY-1767 (NightHawk 64552 item B): true when `data` is a PAGE of a larger population. Read from Core's
|
|
150
|
+
* `pagination.totalCount`; a caller must not treat `data.length` as the table's size.
|
|
151
|
+
*/
|
|
152
|
+
hasMore?: boolean;
|
|
125
153
|
meta: VibeMeta;
|
|
126
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* PAY-1767: the most records `createMany` will create in one call. It is per-record and NOT atomic, so a
|
|
157
|
+
* ceiling bounds the blast radius of a partial failure. 50 is the house's only batch ceiling (Vibe's own
|
|
158
|
+
* DecryptController BATCH_TOO_LARGE = 50).
|
|
159
|
+
*/
|
|
160
|
+
export declare const MAX_BATCH_CREATE_ROWS = 50;
|
|
161
|
+
/**
|
|
162
|
+
* PAY-1767: the default page size findMany asks the query route for. Core clamps pageSize/limit to 100
|
|
163
|
+
* (DocumentsController.Query), so this is the most one call returns.
|
|
164
|
+
*/
|
|
165
|
+
export declare const MAX_FIND_MANY_PAGE_SIZE = 100;
|
|
166
|
+
/**
|
|
167
|
+
* PAY-1767: map a Prisma-style `where` to Core's VibeFilterNode OBJECT.
|
|
168
|
+
*
|
|
169
|
+
* { status: 'active' } -> { 'status': { eq: 'active' } }
|
|
170
|
+
* { score: { gt: 5 } } -> { 'score': { gt: 5 } }
|
|
171
|
+
* { status: 'a', tier: { in: [] } } -> { AND: [ {status:{eq:'a'}}, {tier:{in:[]}} ] }
|
|
172
|
+
*
|
|
173
|
+
* A field name is used as-is: a top-level document column (document_id, created_at...) or a JSONB path
|
|
174
|
+
* (Core treats an unknown name as data->>'name'; a nested path like 'data.x' is honoured explicitly).
|
|
175
|
+
* An unknown OPERATOR throws - dropping an operator would widen the result silently.
|
|
176
|
+
*/
|
|
177
|
+
export declare function buildVibeFilter(where: Record<string, unknown>, table: string): Record<string, unknown>;
|
|
127
178
|
/**
|
|
128
179
|
* Delegate class for table operations.
|
|
129
180
|
* Provides Prisma-style methods for a specific table.
|
|
@@ -133,9 +184,33 @@ export declare class VibeTableDelegate<T extends VibeTableName> {
|
|
|
133
184
|
private client;
|
|
134
185
|
constructor(tableName: T, client: VibeClient);
|
|
135
186
|
/**
|
|
136
|
-
*
|
|
187
|
+
* PAY-1767: the Vibe API route for this table's document collection.
|
|
188
|
+
* `/v1/collections/{collection}/tables/{table}` (Core DocumentsController.cs:15) - the same shape
|
|
189
|
+
* generic.ts and sessions.ts already use. The OLD `/api/v1/vibe_app/{table}` had no route behind it.
|
|
190
|
+
*/
|
|
191
|
+
private tableEndpoint;
|
|
192
|
+
/**
|
|
193
|
+
* Find multiple records, WITH filtering, sorting and paging honoured.
|
|
194
|
+
*
|
|
195
|
+
* PAY-1767 (NightHawk 64534 MUST 1, QAPert 64530, BAPert 64535). The GET list route
|
|
196
|
+
* (`GET /v1/collections/{c}/tables/{t}`, Core DocumentsController.List :44-48) binds ONLY `page` and
|
|
197
|
+
* `pageSize` - it reads no filter, sort or offset, so sending where/orderBy/take/skip there DROPPED them
|
|
198
|
+
* silently. Through the proxy that is worse than loud: a proxied MVP service account is ADMIN mode
|
|
199
|
+
* (HasValidContext), so the ownership filter is bypassed and an unfiltered page is 20 ARBITRARY rows of
|
|
200
|
+
* the tenant - caller code doing `rows[0]` gets the WRONG record with no error.
|
|
201
|
+
*
|
|
202
|
+
* So this now goes to `POST .../tables/{t}/query`, which binds QueryRequest (Filter / OrderBy / Page /
|
|
203
|
+
* PageSize / Limit). The FILTER is a VibeFilterNode OBJECT - `{ "<field>": { "<op>": value } }` - NOT the
|
|
204
|
+
* filter ARRAY that sessions.ts sends: QueryRequest.Filter is typed VibeFilterNode, which has no array
|
|
205
|
+
* form (measured at Core origin/master; CliInitProjectService.cs:197 builds the same object shape). An
|
|
206
|
+
* unmappable option THROWS rather than being dropped.
|
|
137
207
|
*/
|
|
138
208
|
findMany(options?: FindManyOptions<VibeTableType<T>>): Promise<FindManyResult<VibeTableType<T>>>;
|
|
209
|
+
/**
|
|
210
|
+
* PAY-1767: map the Prisma-style findMany options onto Core's QueryRequest body. THROWS on anything the
|
|
211
|
+
* query endpoint cannot express, so nothing is silently dropped.
|
|
212
|
+
*/
|
|
213
|
+
private buildQueryBody;
|
|
139
214
|
/**
|
|
140
215
|
* Find a single record by ID.
|
|
141
216
|
* Throws VibeNotFoundError if not found.
|
|
@@ -147,8 +222,12 @@ export declare class VibeTableDelegate<T extends VibeTableName> {
|
|
|
147
222
|
*/
|
|
148
223
|
findUniqueOrNull(options: FindUniqueOptions): Promise<VibeTableType<T> | null>;
|
|
149
224
|
/**
|
|
150
|
-
* Find the first record
|
|
225
|
+
* Find the first record.
|
|
151
226
|
* Returns null if none found.
|
|
227
|
+
*
|
|
228
|
+
* PAY-1767: takes the first row of the (caller-owned, created_at-ordered) list. Any where/orderBy in
|
|
229
|
+
* `options` is passed through to findMany, which REFUSES them on this route rather than dropping them,
|
|
230
|
+
* so this cannot pretend to filter.
|
|
152
231
|
*/
|
|
153
232
|
findFirst(options?: FindManyOptions<VibeTableType<T>>): Promise<VibeTableType<T> | null>;
|
|
154
233
|
/**
|
|
@@ -163,19 +242,45 @@ export declare class VibeTableDelegate<T extends VibeTableName> {
|
|
|
163
242
|
/**
|
|
164
243
|
* Delete a record by ID (soft delete).
|
|
165
244
|
* Throws VibeNotFoundError if not found.
|
|
245
|
+
*
|
|
246
|
+
* PAY-1767 (NightHawk 64578 item 2): Core answers DELETE with **204 No Content** (DocumentsController
|
|
247
|
+
* Delete returns NoContent()), so a SUCCESSFUL delete carries NO document - and there is none to unwrap.
|
|
248
|
+
* This must therefore NOT go through requireRow (that is the create/update rule: "a success WITH no
|
|
249
|
+
* document throws"). A 2xx with no body is the success here; the request layer already resolves it to an
|
|
250
|
+
* empty success. Returns null when the server sent no document.
|
|
166
251
|
*/
|
|
167
|
-
delete(options: DeleteOptions): Promise<VibeTableType<T
|
|
252
|
+
delete(options: DeleteOptions): Promise<VibeTableType<T> | null>;
|
|
253
|
+
/** Unwrap a single-document response, or THROW - never return an empty object typed as a row. */
|
|
254
|
+
private requireRow;
|
|
168
255
|
/**
|
|
169
256
|
* Count records matching the filter.
|
|
257
|
+
*
|
|
258
|
+
* PAY-1767 (NightHawk 64534): goes to POST /query with limit:0, which is Core's COUNT-ONLY mode
|
|
259
|
+
* (DocumentsController.Query returns an empty Data array plus Pagination.TotalCount). So this is the
|
|
260
|
+
* true population of the filter, not the first page's size.
|
|
170
261
|
*/
|
|
171
262
|
count(options?: CountOptions<VibeTableType<T>>): Promise<number>;
|
|
172
263
|
/**
|
|
173
|
-
* Create multiple records
|
|
264
|
+
* Create multiple records.
|
|
265
|
+
*
|
|
266
|
+
* PAY-1767: the old path `/api/v1/vibe_app/batch` has no route (there is no documents batch endpoint in
|
|
267
|
+
* Core's Vibe Public API), so this CANNOT be atomic here. Until a Core batch route exists, this creates
|
|
268
|
+
* each record against the REAL table endpoint in turn.
|
|
269
|
+
*
|
|
270
|
+
* BOUNDED ON PURPOSE (BAPert 64527). Non-atomic is tolerable only while the blast radius is small, so a
|
|
271
|
+
* request above MAX_BATCH_CREATE_ROWS is REFUSED before anything is sent. 50 matches the house's only
|
|
272
|
+
* batch ceiling (Vibe's DecryptController BATCH_TOO_LARGE).
|
|
273
|
+
*
|
|
274
|
+
* PARTIAL FAILURE IS REPORTED, NOT SWALLOWED (NightHawk 64534 condition (a)). A failure at item k throws
|
|
275
|
+
* a VibeBatchCreateError that CARRIES the count and the document ids already created - a cap cannot fix
|
|
276
|
+
* partial failure, only reporting it can. The created rows are unwrapped (MUST 2) so their own fields are
|
|
277
|
+
* present, not the wrapper's.
|
|
174
278
|
*/
|
|
175
279
|
createMany(options: {
|
|
176
280
|
data: CreateOptions<VibeTableType<T>>['data'][];
|
|
177
281
|
}): Promise<{
|
|
178
282
|
count: number;
|
|
283
|
+
ids: Array<number | string>;
|
|
179
284
|
}>;
|
|
180
285
|
}
|
|
181
286
|
/**
|
|
@@ -205,9 +310,19 @@ export declare class VibeClient {
|
|
|
205
310
|
* Message format: {timestamp}|{method}|{endpoint}
|
|
206
311
|
*/
|
|
207
312
|
private generateProxySignature;
|
|
313
|
+
/**
|
|
314
|
+
* PAY-1767: the Vibe API endpoint PATH for a table in this client's collection.
|
|
315
|
+
* `/v1/collections/{collection}/tables/{table}[/{id}]` - what the IDP proxy validates, signs and forwards.
|
|
316
|
+
* Returned as a PATH, not a URL: proxy mode needs no VIBE_API_URL (the proxy owns the base).
|
|
317
|
+
*/
|
|
318
|
+
tableEndpoint(table: string, id?: number | string): string;
|
|
208
319
|
/**
|
|
209
320
|
* Make a request through the IDP Vibe proxy.
|
|
210
321
|
* The proxy handles credential injection securely.
|
|
322
|
+
*
|
|
323
|
+
* PAY-1767: `endpoint` is the RAW path (+query) to forward. It is what gets signed and sent, so this
|
|
324
|
+
* takes a path directly rather than parsing one out of a URL - proxy mode is complete without a Vibe
|
|
325
|
+
* base URL, which is exactly what an endpoint-path call needs.
|
|
211
326
|
*/
|
|
212
327
|
private requestViaProxy;
|
|
213
328
|
/**
|
|
@@ -220,27 +335,28 @@ export declare class VibeClient {
|
|
|
220
335
|
* Routing:
|
|
221
336
|
* - All requests route through the IDP proxy (requires VIBE_HMAC_KEY + IDP_URL)
|
|
222
337
|
*
|
|
338
|
+
* PAY-1767: `urlOrPath` is EITHER a full URL or a bare Vibe path ('/v1/collections/...'). Proxy mode
|
|
339
|
+
* signs and forwards the PATH either way, so a caller with only a path (the table delegates) needs no
|
|
340
|
+
* Vibe base URL - VIBE_API_URL is not required to make a proxied call.
|
|
341
|
+
*
|
|
223
342
|
* Retry behavior:
|
|
224
343
|
* - 5xx errors: Retry up to maxRetries times with exponential backoff
|
|
225
344
|
* - 429 errors: Retry once after Retry-After delay (if retryOnRateLimit enabled)
|
|
226
345
|
* - 401/403 errors: No retry (auth errors)
|
|
227
346
|
* - Network errors: Retry with backoff
|
|
228
347
|
*/
|
|
229
|
-
request<T>(
|
|
348
|
+
request<T>(urlOrPath: string, method: 'GET' | 'POST' | 'PUT' | 'DELETE', body?: unknown, authToken?: string): Promise<VibeResponse<T>>;
|
|
230
349
|
/**
|
|
231
350
|
* Parse error response body, handling non-JSON responses gracefully.
|
|
232
351
|
*/
|
|
233
352
|
private parseErrorResponse;
|
|
234
353
|
/**
|
|
235
354
|
* Fetch the schema for type generation.
|
|
355
|
+
*
|
|
356
|
+
* PAY-1767: the old path `/api/v1/vibe_app/schema` has no route. The real per-collection schema route is
|
|
357
|
+
* `/v1/mvp/schemas/{collection}` (Core MvpSchemaController.cs:65). Returns the collection schema DTO.
|
|
236
358
|
*/
|
|
237
|
-
getSchema(): Promise<Record<string,
|
|
238
|
-
columns: Array<{
|
|
239
|
-
name: string;
|
|
240
|
-
type: string;
|
|
241
|
-
nullable: boolean;
|
|
242
|
-
}>;
|
|
243
|
-
}>>;
|
|
359
|
+
getSchema(): Promise<Record<string, unknown>>;
|
|
244
360
|
}
|
|
245
361
|
/**
|
|
246
362
|
* Default Vibe client instance.
|