@bounded-sh/server 0.0.56 → 0.0.58
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/dist/index.js +53 -19
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +54 -20
- package/dist/index.mjs.map +1 -1
- package/dist/sanitize-headers.d.ts +1 -0
- package/dist/wallet-client.d.ts +2 -1
- package/dist/webhooks.d.ts +15 -0
- package/package.json +4 -3
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { init as init$1, convertRemainingAccounts, buildSetDocumentsTransaction, normalizeEvmAddress, genAuthNonce, getConfig, genSiweMessage, createSessionWithSignature, assertCanonicalBoundedRuntimeWriteCalldata, live as live$1, set as set$1, setMany as setMany$1, setFile as setFile$1, get as get$1, getMany as getMany$1, getFiles as getFiles$1, search as search$1, runQuery as runQuery$1, runQueryMany as runQueryMany$1, runExpression as runExpression$1, runExpressionMany as runExpressionMany$1, count as count$1, aggregate as aggregate$1, queryAggregate as queryAggregate$1, subscribe as subscribe$1, functions as functions$1, ServerSessionManager, getWebhookKeysUrl } from '@bounded-sh/core';
|
|
1
|
+
import { init as init$1, convertRemainingAccounts, buildSetDocumentsTransaction, normalizeEvmAddress, genAuthNonce, getConfig, genSiweMessage, createSessionWithSignature, assertCanonicalBoundedRuntimeWriteCalldata, live as live$1, set as set$1, setMany as setMany$1, setFile as setFile$1, get as get$1, getMany as getMany$1, getFiles as getFiles$1, search as search$1, runQuery as runQuery$1, runQueryMany as runQueryMany$1, runExpression as runExpression$1, runExpressionMany as runExpressionMany$1, count as count$1, aggregate as aggregate$1, queryAggregate as queryAggregate$1, subscribe as subscribe$1, functions as functions$1, ServerSessionManager, getWebhookKeysUrl, getConfiguredAppId } from '@bounded-sh/core';
|
|
2
2
|
export { BoundedDeclineError, DECLINE_EVENT_NAME, FunctionInvokeError, InsufficientBalanceError, LiveIntentError, deriveDecline, getConfig, getWebhookKeysUrl } from '@bounded-sh/core';
|
|
3
3
|
import { Connection, VersionedTransaction, ComputeBudgetProgram, SystemProgram, Keypair } from '@solana/web3.js';
|
|
4
4
|
import bs58 from 'bs58';
|
|
@@ -790,17 +790,23 @@ class EvmKeypairProvider {
|
|
|
790
790
|
}
|
|
791
791
|
}
|
|
792
792
|
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
793
|
+
// #023: strip caller-supplied auth headers so an operation runs as the isolated wallet, not
|
|
794
|
+
// as whatever headers were forwarded in. HTTP header names are case-insensitive on the wire,
|
|
795
|
+
// so a fixed `Authorization`/`authorization` destructure let a mixed-case `AUTHORIZATION`
|
|
796
|
+
// survive and ride along. Drop every header whose lowercased name is authorization (plus
|
|
797
|
+
// proxy-authorization / cookie, which would likewise leak a caller identity into the session).
|
|
798
|
+
const STRIP_HEADER_NAMES = new Set(["authorization", "proxy-authorization", "cookie"]);
|
|
799
|
+
function stripCallerAuthHeaders(headers) {
|
|
800
|
+
if (!headers)
|
|
801
|
+
return undefined;
|
|
802
|
+
const rest = {};
|
|
803
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
804
|
+
if (!STRIP_HEADER_NAMES.has(name.toLowerCase()))
|
|
805
|
+
rest[name] = value;
|
|
806
|
+
}
|
|
807
|
+
return Object.keys(rest).length > 0 ? rest : undefined;
|
|
808
|
+
}
|
|
809
|
+
|
|
804
810
|
/* ------------------------------------------------------------------ */
|
|
805
811
|
/* Keypair parsing */
|
|
806
812
|
/* ------------------------------------------------------------------ */
|
|
@@ -848,7 +854,11 @@ class WalletClient {
|
|
|
848
854
|
this.evmTransactionPolicy = evmTransactionPolicy;
|
|
849
855
|
this.live = {
|
|
850
856
|
intent: (roomPath, intent, opts = {}) => live$1.intent(roomPath, intent, this.mergeLiveIntentOptions(opts)),
|
|
851
|
-
|
|
857
|
+
// Route status through the SAME auth merge intent uses so a server-keypair
|
|
858
|
+
// agent's session token rides the request. Without it the agent sends the
|
|
859
|
+
// public app id only and the server returns the slim anonymous shape rather
|
|
860
|
+
// than the full diagnostics the authenticated agent is entitled to (#102).
|
|
861
|
+
status: (roomPath, opts = {}) => live$1.status(roomPath, this.mergeLiveStatusOptions(opts)),
|
|
852
862
|
// Agent-as-player view stream: subscribe to THIS wallet's per-player view
|
|
853
863
|
// under the wallet's own session (per-subscription overrides — no global
|
|
854
864
|
// auth needed). The view id defaults to this wallet's @user.id (== its
|
|
@@ -888,12 +898,9 @@ class WalletClient {
|
|
|
888
898
|
this.sessionManager.clearSession();
|
|
889
899
|
}, _walletAddress: this.address }, (this.evmTransactionPolicy ? { _evmTransactionPolicy: this.evmTransactionPolicy } : {}));
|
|
890
900
|
}
|
|
891
|
-
/**
|
|
901
|
+
/** #023: strip caller Authorization (any casing) so operations run as the isolated wallet. */
|
|
892
902
|
sanitizeHeaders(headers) {
|
|
893
|
-
|
|
894
|
-
return undefined;
|
|
895
|
-
const _a = headers, { Authorization, authorization } = _a, rest = __rest(_a, ["Authorization", "authorization"]);
|
|
896
|
-
return Object.keys(rest).length > 0 ? rest : undefined;
|
|
903
|
+
return stripCallerAuthHeaders(headers);
|
|
897
904
|
}
|
|
898
905
|
mergeSetOverrides(opts) {
|
|
899
906
|
var _a;
|
|
@@ -910,6 +917,12 @@ class WalletClient {
|
|
|
910
917
|
const merged = Object.assign(Object.assign({}, opts), { headers: this.sanitizeHeaders(opts === null || opts === void 0 ? void 0 : opts.headers), _overrides: Object.assign(Object.assign({}, opts === null || opts === void 0 ? void 0 : opts._overrides), { headers: this.sanitizeHeaders((_a = opts === null || opts === void 0 ? void 0 : opts._overrides) === null || _a === void 0 ? void 0 : _a.headers), _getAuthHeaders: ovr._getAuthHeaders, _clearAuth: ovr._clearAuth, _walletAddress: ovr._walletAddress }) });
|
|
911
918
|
return merged;
|
|
912
919
|
}
|
|
920
|
+
mergeLiveStatusOptions(opts) {
|
|
921
|
+
var _a;
|
|
922
|
+
const ovr = this.buildOverrides();
|
|
923
|
+
const merged = Object.assign(Object.assign({}, opts), { headers: this.sanitizeHeaders(opts === null || opts === void 0 ? void 0 : opts.headers), _overrides: Object.assign(Object.assign({}, opts === null || opts === void 0 ? void 0 : opts._overrides), { headers: this.sanitizeHeaders((_a = opts === null || opts === void 0 ? void 0 : opts._overrides) === null || _a === void 0 ? void 0 : _a.headers), _getAuthHeaders: ovr._getAuthHeaders, _clearAuth: ovr._clearAuth, _walletAddress: ovr._walletAddress }) });
|
|
924
|
+
return merged;
|
|
925
|
+
}
|
|
913
926
|
/* ---- Data operations ---- */
|
|
914
927
|
async set(path, document, options) {
|
|
915
928
|
return set$1(path, document, this.mergeSetOverrides(options));
|
|
@@ -1324,8 +1337,29 @@ async function verifyWebhook(rawBody, headers, opts = {}) {
|
|
|
1324
1337
|
if (Math.abs(nowSeconds - payload.timestamp) > maxSkew) {
|
|
1325
1338
|
throw new WebhookVerificationError(`Webhook timestamp outside allowed skew of ${maxSkew}s (delta ${Math.abs(nowSeconds - payload.timestamp)}s).`);
|
|
1326
1339
|
}
|
|
1327
|
-
|
|
1328
|
-
|
|
1340
|
+
// #072: bind the delivery to an app by DEFAULT. The shared platform signing key means a
|
|
1341
|
+
// valid signature proves "from Bounded", not "from your app"; payload.appId is the ONLY
|
|
1342
|
+
// per-app separation. Previously the bind ran only when expectedAppId was passed, so the
|
|
1343
|
+
// secure check was opt-in and a malicious app owner could point their app's webhook at a
|
|
1344
|
+
// victim receiver. Now: an explicit expectedAppId is authoritative; otherwise bind to the
|
|
1345
|
+
// init()-time app id; a multi-app receiver must set allowAnyAppId to accept any app; and
|
|
1346
|
+
// when no app id is resolvable we FAIL CLOSED rather than silently trust any app.
|
|
1347
|
+
if (opts.expectedAppId !== undefined) {
|
|
1348
|
+
if (payload.appId !== opts.expectedAppId) {
|
|
1349
|
+
throw new WebhookVerificationError(`Webhook appId "${payload.appId}" does not match expected appId "${opts.expectedAppId}".`);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
else if (!opts.allowAnyAppId) {
|
|
1353
|
+
const configuredAppId = getConfiguredAppId();
|
|
1354
|
+
if (!configuredAppId) {
|
|
1355
|
+
throw new WebhookVerificationError("verifyWebhook cannot determine which app this webhook is for: no expectedAppId was " +
|
|
1356
|
+
"passed and the SDK has no init()-time app id. Pass expectedAppId, or set " +
|
|
1357
|
+
"allowAnyAppId: true to intentionally accept webhooks from any app.");
|
|
1358
|
+
}
|
|
1359
|
+
if (payload.appId !== configuredAppId) {
|
|
1360
|
+
throw new WebhookVerificationError(`Webhook appId "${payload.appId}" does not match the configured app id "${configuredAppId}". ` +
|
|
1361
|
+
"Pass allowAnyAppId: true to accept webhooks from any app.");
|
|
1362
|
+
}
|
|
1329
1363
|
}
|
|
1330
1364
|
// Replay cache: reject a previously-seen signed delivery within the skew
|
|
1331
1365
|
// window. The worker's payload.id is the document id/path, so using it alone
|