@tinycloud/sdk-services 3.1.0-beta.2 → 3.1.0-beta.6
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/encryption/index.cjs +106 -97
- package/dist/encryption/index.cjs.map +1 -1
- package/dist/encryption/index.js +106 -97
- package/dist/encryption/index.js.map +1 -1
- package/dist/index.cjs +269 -147
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.js +249 -129
- package/dist/index.js.map +1 -1
- package/dist/kv/index.cjs +210 -20
- package/dist/kv/index.cjs.map +1 -1
- package/dist/kv/index.d.cts +5 -1
- package/dist/kv/index.d.ts +5 -1
- package/dist/kv/index.js +210 -20
- package/dist/kv/index.js.map +1 -1
- package/dist/sql/index.cjs +33 -21
- package/dist/sql/index.cjs.map +1 -1
- package/dist/sql/index.js +24 -12
- package/dist/sql/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -116,6 +116,7 @@ __export(src_exports, {
|
|
|
116
116
|
ok: () => ok,
|
|
117
117
|
openWrappedKey: () => openWrappedKey,
|
|
118
118
|
parseAuthError: () => parseAuthError,
|
|
119
|
+
parseCapabilityResource: () => parseCapabilityResource,
|
|
119
120
|
parseNetworkId: () => parseNetworkId,
|
|
120
121
|
parsePermissionHint: () => parsePermissionHint,
|
|
121
122
|
parsePermissionHintFromErrorText: () => parsePermissionHintFromErrorText,
|
|
@@ -141,6 +142,7 @@ __export(src_exports, {
|
|
|
141
142
|
validateServiceResponseEvent: () => validateServiceResponseEvent,
|
|
142
143
|
validateServiceSession: () => validateServiceSession,
|
|
143
144
|
validateTelemetrySpanEvent: () => validateTelemetrySpanEvent,
|
|
145
|
+
validatedCapabilityOf: () => validatedCapabilityOf,
|
|
144
146
|
verifyDecryptResponse: () => verifyDecryptResponse,
|
|
145
147
|
wrapError: () => wrapError
|
|
146
148
|
});
|
|
@@ -1044,6 +1046,106 @@ var ServiceContext = class {
|
|
|
1044
1046
|
}
|
|
1045
1047
|
};
|
|
1046
1048
|
|
|
1049
|
+
// src/errors.ts
|
|
1050
|
+
var import_bootstrap = require("@tinycloud/bootstrap");
|
|
1051
|
+
|
|
1052
|
+
// src/encryption/networkId.ts
|
|
1053
|
+
var URN_PREFIX = "urn:tinycloud:encryption:";
|
|
1054
|
+
var NETWORK_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
1055
|
+
var PKH_EIP155_DID_RE = /^did:pkh:eip155:(\d+):(0x[a-fA-F0-9]{40})$/;
|
|
1056
|
+
var NetworkIdError = class extends Error {
|
|
1057
|
+
constructor(message) {
|
|
1058
|
+
super(message);
|
|
1059
|
+
this.name = "NetworkIdError";
|
|
1060
|
+
}
|
|
1061
|
+
};
|
|
1062
|
+
function parseNetworkId(networkId) {
|
|
1063
|
+
if (typeof networkId !== "string" || networkId.length === 0) {
|
|
1064
|
+
throw new NetworkIdError("networkId must be a non-empty string");
|
|
1065
|
+
}
|
|
1066
|
+
if (!networkId.startsWith(URN_PREFIX)) {
|
|
1067
|
+
throw new NetworkIdError(
|
|
1068
|
+
`networkId must start with ${URN_PREFIX} (got ${JSON.stringify(networkId)})`
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
const body = networkId.slice(URN_PREFIX.length);
|
|
1072
|
+
const lastColon = body.lastIndexOf(":");
|
|
1073
|
+
if (lastColon <= 0 || lastColon === body.length - 1) {
|
|
1074
|
+
throw new NetworkIdError(
|
|
1075
|
+
`networkId missing ownerDid or name segment (got ${JSON.stringify(networkId)})`
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
const ownerDid = body.slice(0, lastColon);
|
|
1079
|
+
const name = body.slice(lastColon + 1);
|
|
1080
|
+
if (!ownerDid.startsWith("did:")) {
|
|
1081
|
+
throw new NetworkIdError(
|
|
1082
|
+
`networkId ownerDid must be a DID (got ${JSON.stringify(ownerDid)})`
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
const didParts = ownerDid.split(":");
|
|
1086
|
+
if (didParts.length < 3 || didParts.some((p) => p.length === 0)) {
|
|
1087
|
+
throw new NetworkIdError(
|
|
1088
|
+
`networkId ownerDid is not a well-formed DID (got ${JSON.stringify(ownerDid)})`
|
|
1089
|
+
);
|
|
1090
|
+
}
|
|
1091
|
+
if (!NETWORK_NAME_RE.test(name)) {
|
|
1092
|
+
throw new NetworkIdError(
|
|
1093
|
+
`networkId name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
return { networkId, ownerDid, name };
|
|
1097
|
+
}
|
|
1098
|
+
function buildNetworkId(ownerDid, name) {
|
|
1099
|
+
if (typeof ownerDid !== "string" || !ownerDid.startsWith("did:")) {
|
|
1100
|
+
throw new NetworkIdError("ownerDid must be a DID");
|
|
1101
|
+
}
|
|
1102
|
+
if (typeof name !== "string" || !NETWORK_NAME_RE.test(name)) {
|
|
1103
|
+
throw new NetworkIdError(
|
|
1104
|
+
`network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
const networkId = `${URN_PREFIX}${ownerDid}:${name}`;
|
|
1108
|
+
parseNetworkId(networkId);
|
|
1109
|
+
return networkId;
|
|
1110
|
+
}
|
|
1111
|
+
function isNetworkId(networkId) {
|
|
1112
|
+
if (typeof networkId !== "string") {
|
|
1113
|
+
return false;
|
|
1114
|
+
}
|
|
1115
|
+
try {
|
|
1116
|
+
parseNetworkId(networkId);
|
|
1117
|
+
return true;
|
|
1118
|
+
} catch {
|
|
1119
|
+
return false;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
function parsePkhOwnerDid(ownerDid) {
|
|
1123
|
+
const match = ownerDid.match(PKH_EIP155_DID_RE);
|
|
1124
|
+
if (!match) return null;
|
|
1125
|
+
return {
|
|
1126
|
+
chainId: match[1],
|
|
1127
|
+
address: match[2].toLowerCase()
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
function ownerDidMatches(a, b) {
|
|
1131
|
+
const aPkh = parsePkhOwnerDid(a);
|
|
1132
|
+
const bPkh = parsePkhOwnerDid(b);
|
|
1133
|
+
if (aPkh && bPkh) {
|
|
1134
|
+
return aPkh.chainId === bPkh.chainId && aPkh.address === bPkh.address;
|
|
1135
|
+
}
|
|
1136
|
+
return a === b;
|
|
1137
|
+
}
|
|
1138
|
+
function networkDiscoveryKey(name) {
|
|
1139
|
+
if (!NETWORK_NAME_RE.test(name)) {
|
|
1140
|
+
throw new NetworkIdError(
|
|
1141
|
+
`network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
1142
|
+
);
|
|
1143
|
+
}
|
|
1144
|
+
return `.well-known/encryption/network/${name}`;
|
|
1145
|
+
}
|
|
1146
|
+
var ENCRYPTION_NETWORK_URN_PREFIX = URN_PREFIX;
|
|
1147
|
+
var NETWORK_NAME_PATTERN = NETWORK_NAME_RE;
|
|
1148
|
+
|
|
1047
1149
|
// src/errors.ts
|
|
1048
1150
|
function parsePermissionHint(value) {
|
|
1049
1151
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
@@ -1141,6 +1243,70 @@ function parseAuthError(responseText) {
|
|
|
1141
1243
|
function authUnauthorizedError(service, message, meta) {
|
|
1142
1244
|
return serviceError(ErrorCodes.AUTH_UNAUTHORIZED, message, service, { meta });
|
|
1143
1245
|
}
|
|
1246
|
+
var grantableActions = new Set(
|
|
1247
|
+
import_bootstrap.CAPABILITY_REGISTRY.map(({ urn }) => urn).filter((urn) => !urn.endsWith("/*"))
|
|
1248
|
+
);
|
|
1249
|
+
var capabilityServices = new Set(
|
|
1250
|
+
import_bootstrap.CAPABILITY_REGISTRY.map(({ service }) => service.slice("tinycloud.".length))
|
|
1251
|
+
);
|
|
1252
|
+
function parseCapabilityResource(resource, service) {
|
|
1253
|
+
if (resource.length === 0 || resource.length > 1024 || !capabilityServices.has(service) || /[\s\p{C}*#]/u.test(resource) || resource.includes("://")) return void 0;
|
|
1254
|
+
const segments = resource.split("/");
|
|
1255
|
+
if (!/^tinycloud:[A-Za-z0-9][A-Za-z0-9:._-]*$/.test(segments[0])) return void 0;
|
|
1256
|
+
const serviceIndex = segments.findIndex(
|
|
1257
|
+
(segment, index) => index > 0 && capabilityServices.has(segment)
|
|
1258
|
+
);
|
|
1259
|
+
if (serviceIndex < 1 || segments[serviceIndex] !== service) return void 0;
|
|
1260
|
+
if (segments.slice(1, serviceIndex).some(
|
|
1261
|
+
(segment) => segment.length === 0 || segment === "." || segment === ".."
|
|
1262
|
+
)) return void 0;
|
|
1263
|
+
const pathSegments = segments.slice(serviceIndex + 1);
|
|
1264
|
+
if (pathSegments.some((segment) => segment === "." || segment === "..")) return void 0;
|
|
1265
|
+
if (service !== "kv" && (pathSegments.length === 0 || pathSegments[pathSegments.length - 1] === "")) return void 0;
|
|
1266
|
+
return {
|
|
1267
|
+
space: segments.slice(0, serviceIndex).join("/"),
|
|
1268
|
+
path: pathSegments.join("/")
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
function safeCapabilityResource(value, service, action) {
|
|
1272
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 1024) return false;
|
|
1273
|
+
if (service === "encryption" && value.startsWith("urn:tinycloud:encryption:")) {
|
|
1274
|
+
if (!/^[A-Za-z0-9_][A-Za-z0-9_.:/-]*$/.test(value) || value.includes("//") || value.split("/").some((segment) => segment === "." || segment === "..")) return false;
|
|
1275
|
+
try {
|
|
1276
|
+
parseNetworkId(value);
|
|
1277
|
+
return true;
|
|
1278
|
+
} catch {
|
|
1279
|
+
return false;
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
const parsed = parseCapabilityResource(value, service);
|
|
1283
|
+
return parsed !== void 0 && (!parsed.path.endsWith("/") || service === "kv" && action === "tinycloud.kv/list") && (parsed.path !== "" || service === "kv" && action === "tinycloud.kv/list");
|
|
1284
|
+
}
|
|
1285
|
+
function validatedCapabilityOf(error) {
|
|
1286
|
+
const verdict = authorizationVerdictOf(error);
|
|
1287
|
+
if (verdict !== "unauthenticated" && verdict !== "forbidden") return void 0;
|
|
1288
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1289
|
+
let node = error;
|
|
1290
|
+
for (let depth = 0; depth < MAX_CAUSE_DEPTH && typeof node === "object" && node !== null && !seen.has(node); depth += 1) {
|
|
1291
|
+
seen.add(node);
|
|
1292
|
+
const record = node;
|
|
1293
|
+
const status = errorStatusOf(record);
|
|
1294
|
+
const meta = record.meta;
|
|
1295
|
+
if ((status === 401 || status === 403 || status === void 0 && record.code === ErrorCodes.AUTH_UNAUTHORIZED) && typeof meta === "object" && meta !== null && !Array.isArray(meta)) {
|
|
1296
|
+
const metadata = meta;
|
|
1297
|
+
const { resource, requiredAction } = metadata;
|
|
1298
|
+
if (typeof requiredAction === "string" && grantableActions.has(requiredAction)) {
|
|
1299
|
+
const actionService = requiredAction.slice("tinycloud.".length, requiredAction.indexOf("/"));
|
|
1300
|
+
const explicitService = record.service === void 0 ? metadata.service : record.service;
|
|
1301
|
+
if (safeCapabilityResource(resource, actionService, requiredAction) && (explicitService === void 0 || explicitService === actionService || explicitService === `tinycloud.${actionService}`)) {
|
|
1302
|
+
return { resource, requiredAction };
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
node = record.cause;
|
|
1307
|
+
}
|
|
1308
|
+
return void 0;
|
|
1309
|
+
}
|
|
1144
1310
|
var MAX_CAUSE_DEPTH = 8;
|
|
1145
1311
|
function errorStatusOf(node) {
|
|
1146
1312
|
const meta = node.meta;
|
|
@@ -1888,12 +2054,14 @@ var KVService = class extends BaseService {
|
|
|
1888
2054
|
signal: this.combineSignals(options?.signal)
|
|
1889
2055
|
});
|
|
1890
2056
|
if (!response.ok) {
|
|
1891
|
-
const errorText = await response.text();
|
|
2057
|
+
const errorText = response.status === 401 || response.status === 403 ? await this.readAuthorizationText(response) : await response.text();
|
|
1892
2058
|
if (response.status === 401 || response.status === 403) {
|
|
1893
2059
|
return this.authorizationFailure(
|
|
1894
2060
|
`Failed to batch read ${keys.length} key(s)`,
|
|
1895
2061
|
response,
|
|
1896
|
-
errorText
|
|
2062
|
+
errorText,
|
|
2063
|
+
paths,
|
|
2064
|
+
action
|
|
1897
2065
|
);
|
|
1898
2066
|
}
|
|
1899
2067
|
if (response.status === 413) {
|
|
@@ -1983,7 +2151,7 @@ var KVService = class extends BaseService {
|
|
|
1983
2151
|
return text;
|
|
1984
2152
|
}
|
|
1985
2153
|
}
|
|
1986
|
-
async createSignedReadUrlError(response, key) {
|
|
2154
|
+
async createSignedReadUrlError(response, key, path) {
|
|
1987
2155
|
let errorText = response.statusText;
|
|
1988
2156
|
try {
|
|
1989
2157
|
const text = await response.text();
|
|
@@ -1996,7 +2164,9 @@ var KVService = class extends BaseService {
|
|
|
1996
2164
|
return this.authorizationFailure(
|
|
1997
2165
|
`Failed to create signed read URL for key ${JSON.stringify(key)}`,
|
|
1998
2166
|
response,
|
|
1999
|
-
errorText
|
|
2167
|
+
errorText,
|
|
2168
|
+
[path],
|
|
2169
|
+
KVAction.GET
|
|
2000
2170
|
);
|
|
2001
2171
|
}
|
|
2002
2172
|
const code = response.status === 400 ? ErrorCodes.INVALID_INPUT : ErrorCodes.NETWORK_ERROR;
|
|
@@ -2009,21 +2179,42 @@ var KVService = class extends BaseService {
|
|
|
2009
2179
|
)
|
|
2010
2180
|
);
|
|
2011
2181
|
}
|
|
2182
|
+
/** A known 401/403 keeps its typed status even if reading its body fails. */
|
|
2183
|
+
async readAuthorizationText(response) {
|
|
2184
|
+
try {
|
|
2185
|
+
return await response.text();
|
|
2186
|
+
} catch {
|
|
2187
|
+
return "";
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2012
2190
|
/**
|
|
2013
2191
|
* AUTH_UNAUTHORIZED for a 401/403 response. The message keeps the HTTP
|
|
2014
2192
|
* status and the server text (`<context>: <status> - <text>`, the same
|
|
2015
2193
|
* shape as every other KV failure) so callers that rethrow only the message
|
|
2016
2194
|
* still see the status; `meta.status` carries it for typed callers.
|
|
2017
|
-
*
|
|
2195
|
+
* Server hints must match the requested path/action (whether the node sends
|
|
2196
|
+
* a relative path or full resource); metadata uses only the request-derived
|
|
2197
|
+
* canonical resource under the authenticated session's space.
|
|
2018
2198
|
*/
|
|
2019
|
-
authorizationFailure(context, response, errorText, extraMeta = {}) {
|
|
2199
|
+
authorizationFailure(context, response, errorText, paths, expectedAction, extraMeta = {}) {
|
|
2020
2200
|
const { resource, action } = parseAuthError(errorText);
|
|
2201
|
+
const requestedPath = paths.find(
|
|
2202
|
+
(path) => resource === path || resource === `${this.context.session.spaceId}/kv/${path}`
|
|
2203
|
+
);
|
|
2204
|
+
const capability = requestedPath !== void 0 && action === expectedAction ? validatedCapabilityOf({
|
|
2205
|
+
service: "kv",
|
|
2206
|
+
code: ErrorCodes.AUTH_UNAUTHORIZED,
|
|
2207
|
+
meta: {
|
|
2208
|
+
status: response.status,
|
|
2209
|
+
resource: `${this.context.session.spaceId}/kv/${requestedPath}`,
|
|
2210
|
+
requiredAction: action
|
|
2211
|
+
}
|
|
2212
|
+
}) : void 0;
|
|
2021
2213
|
const detail = errorText.trim().length > 0 ? errorText : response.statusText || "authorization failed";
|
|
2022
2214
|
return err(authUnauthorizedError("kv", `${context}: ${response.status} - ${detail}`, {
|
|
2023
2215
|
status: response.status,
|
|
2024
|
-
...
|
|
2025
|
-
...
|
|
2026
|
-
...extraMeta
|
|
2216
|
+
...extraMeta,
|
|
2217
|
+
...capability
|
|
2027
2218
|
}));
|
|
2028
2219
|
}
|
|
2029
2220
|
normalizeSignedReadUrlResponse(data) {
|
|
@@ -2067,13 +2258,24 @@ var KVService = class extends BaseService {
|
|
|
2067
2258
|
);
|
|
2068
2259
|
if (!response.ok) {
|
|
2069
2260
|
if (response.status === 401 || response.status === 403) {
|
|
2070
|
-
const errorText2 = await
|
|
2261
|
+
const errorText2 = await this.readAuthorizationText(response);
|
|
2071
2262
|
const permissionHint = parsePermissionHintFromErrorText(errorText2);
|
|
2263
|
+
const structuredCapability = permissionHint?.service === "tinycloud.kv" && permissionHint.space === this.context.session.spaceId && permissionHint.path === path ? validatedCapabilityOf({
|
|
2264
|
+
service: "kv",
|
|
2265
|
+
code: ErrorCodes.AUTH_UNAUTHORIZED,
|
|
2266
|
+
meta: {
|
|
2267
|
+
status: response.status,
|
|
2268
|
+
resource: `${this.context.session.spaceId}/kv/${path}`,
|
|
2269
|
+
requiredAction: KVAction.GET
|
|
2270
|
+
}
|
|
2271
|
+
}) : void 0;
|
|
2072
2272
|
return this.authorizationFailure(
|
|
2073
2273
|
`Failed to get key ${JSON.stringify(key)}`,
|
|
2074
2274
|
response,
|
|
2075
2275
|
errorText2,
|
|
2076
|
-
|
|
2276
|
+
[path],
|
|
2277
|
+
KVAction.GET,
|
|
2278
|
+
structuredCapability ? { permissionHint, ...structuredCapability } : {}
|
|
2077
2279
|
);
|
|
2078
2280
|
}
|
|
2079
2281
|
if (response.status === 404) {
|
|
@@ -2150,7 +2352,9 @@ var KVService = class extends BaseService {
|
|
|
2150
2352
|
return this.authorizationFailure(
|
|
2151
2353
|
`Failed to put key ${JSON.stringify(key)}`,
|
|
2152
2354
|
response,
|
|
2153
|
-
await
|
|
2355
|
+
await this.readAuthorizationText(response),
|
|
2356
|
+
[path],
|
|
2357
|
+
KVAction.PUT
|
|
2154
2358
|
);
|
|
2155
2359
|
}
|
|
2156
2360
|
if (!response.ok) {
|
|
@@ -2279,7 +2483,9 @@ var KVService = class extends BaseService {
|
|
|
2279
2483
|
return this.authorizationFailure(
|
|
2280
2484
|
`Failed to batch put ${items.length} key(s)`,
|
|
2281
2485
|
response,
|
|
2282
|
-
errorText
|
|
2486
|
+
errorText,
|
|
2487
|
+
paths,
|
|
2488
|
+
KVAction.PUT
|
|
2283
2489
|
);
|
|
2284
2490
|
}
|
|
2285
2491
|
const quotaError = this.handleQuotaErrorResponse(
|
|
@@ -2400,11 +2606,13 @@ var KVService = class extends BaseService {
|
|
|
2400
2606
|
}
|
|
2401
2607
|
);
|
|
2402
2608
|
if (!response.ok) {
|
|
2403
|
-
if (response.status === 401) {
|
|
2609
|
+
if (response.status === 401 || response.status === 403) {
|
|
2404
2610
|
return this.authorizationFailure(
|
|
2405
2611
|
"Failed to list keys",
|
|
2406
2612
|
response,
|
|
2407
|
-
await
|
|
2613
|
+
await this.readAuthorizationText(response),
|
|
2614
|
+
[listPath],
|
|
2615
|
+
KVAction.LIST
|
|
2408
2616
|
);
|
|
2409
2617
|
}
|
|
2410
2618
|
const errorText = await response.text();
|
|
@@ -2453,11 +2661,13 @@ var KVService = class extends BaseService {
|
|
|
2453
2661
|
options?.ifMatch === void 0 ? void 0 : { "if-match": options.ifMatch }
|
|
2454
2662
|
);
|
|
2455
2663
|
if (!response.ok) {
|
|
2456
|
-
if (response.status === 401) {
|
|
2664
|
+
if (response.status === 401 || response.status === 403) {
|
|
2457
2665
|
return this.authorizationFailure(
|
|
2458
2666
|
`Failed to delete key ${JSON.stringify(key)}`,
|
|
2459
2667
|
response,
|
|
2460
|
-
await
|
|
2668
|
+
await this.readAuthorizationText(response),
|
|
2669
|
+
[path],
|
|
2670
|
+
KVAction.DELETE
|
|
2461
2671
|
);
|
|
2462
2672
|
}
|
|
2463
2673
|
if (response.status === 404) {
|
|
@@ -2515,11 +2725,13 @@ var KVService = class extends BaseService {
|
|
|
2515
2725
|
options?.signal
|
|
2516
2726
|
);
|
|
2517
2727
|
if (!response.ok) {
|
|
2518
|
-
if (response.status === 401) {
|
|
2728
|
+
if (response.status === 401 || response.status === 403) {
|
|
2519
2729
|
return this.authorizationFailure(
|
|
2520
2730
|
`Failed to get metadata for key ${JSON.stringify(key)}`,
|
|
2521
2731
|
response,
|
|
2522
|
-
await
|
|
2732
|
+
await this.readAuthorizationText(response),
|
|
2733
|
+
[path],
|
|
2734
|
+
KVAction.HEAD
|
|
2523
2735
|
);
|
|
2524
2736
|
}
|
|
2525
2737
|
if (response.status === 404) {
|
|
@@ -2586,7 +2798,7 @@ var KVService = class extends BaseService {
|
|
|
2586
2798
|
signal: this.combineSignals(options?.signal)
|
|
2587
2799
|
});
|
|
2588
2800
|
if (!response.ok) {
|
|
2589
|
-
return this.createSignedReadUrlError(response, key);
|
|
2801
|
+
return this.createSignedReadUrlError(response, key, path);
|
|
2590
2802
|
}
|
|
2591
2803
|
const signedUrl = this.normalizeSignedReadUrlResponse(
|
|
2592
2804
|
await response.json()
|
|
@@ -2729,16 +2941,16 @@ var DatabaseHandle = class {
|
|
|
2729
2941
|
};
|
|
2730
2942
|
|
|
2731
2943
|
// src/sql/types.ts
|
|
2732
|
-
var
|
|
2944
|
+
var import_bootstrap2 = require("@tinycloud/bootstrap");
|
|
2733
2945
|
var SQLAction = {
|
|
2734
|
-
READ:
|
|
2735
|
-
WRITE:
|
|
2736
|
-
SCHEMA:
|
|
2737
|
-
ADMIN:
|
|
2738
|
-
SELECT:
|
|
2739
|
-
EXECUTE:
|
|
2740
|
-
EXPORT:
|
|
2741
|
-
ALL:
|
|
2946
|
+
READ: import_bootstrap2.SQL.READ,
|
|
2947
|
+
WRITE: import_bootstrap2.SQL.WRITE,
|
|
2948
|
+
SCHEMA: import_bootstrap2.SQL.SCHEMA,
|
|
2949
|
+
ADMIN: import_bootstrap2.SQL.ADMIN,
|
|
2950
|
+
SELECT: import_bootstrap2.SQL.SELECT,
|
|
2951
|
+
EXECUTE: import_bootstrap2.SQL.EXECUTE,
|
|
2952
|
+
EXPORT: import_bootstrap2.SQL.EXPORT,
|
|
2953
|
+
ALL: import_bootstrap2.SQL.ALL
|
|
2742
2954
|
};
|
|
2743
2955
|
|
|
2744
2956
|
// src/sql/SQLService.ts
|
|
@@ -2824,14 +3036,15 @@ var SQLService = class extends BaseService {
|
|
|
2824
3036
|
};
|
|
2825
3037
|
if (options?.maxRows !== void 0) body.maxRows = options.maxRows;
|
|
2826
3038
|
if (options?.maxBytes !== void 0) body.maxBytes = options.maxBytes;
|
|
3039
|
+
const action = this.actionForSql(sql, SQLAction.READ);
|
|
2827
3040
|
const response = await this.invokeSQL(
|
|
2828
3041
|
dbName,
|
|
2829
|
-
|
|
3042
|
+
action,
|
|
2830
3043
|
body,
|
|
2831
3044
|
options?.signal
|
|
2832
3045
|
);
|
|
2833
3046
|
if (!response.ok) {
|
|
2834
|
-
return this.handleErrorResponse(response, "query");
|
|
3047
|
+
return this.handleErrorResponse(response, "query", dbName, [action]);
|
|
2835
3048
|
}
|
|
2836
3049
|
const data = await response.json();
|
|
2837
3050
|
return ok(data);
|
|
@@ -2860,14 +3073,15 @@ var SQLService = class extends BaseService {
|
|
|
2860
3073
|
(statement) => this.actionForSql(statement, SQLAction.SCHEMA)
|
|
2861
3074
|
)
|
|
2862
3075
|
];
|
|
3076
|
+
const requestedActions = this.dedupeActions(actions);
|
|
2863
3077
|
const response = await this.invokeSQL(
|
|
2864
3078
|
dbName,
|
|
2865
|
-
|
|
3079
|
+
requestedActions,
|
|
2866
3080
|
body,
|
|
2867
3081
|
options?.signal
|
|
2868
3082
|
);
|
|
2869
3083
|
if (!response.ok) {
|
|
2870
|
-
return this.handleErrorResponse(response, "execute");
|
|
3084
|
+
return this.handleErrorResponse(response, "execute", dbName, requestedActions);
|
|
2871
3085
|
}
|
|
2872
3086
|
const data = await response.json();
|
|
2873
3087
|
return ok(data);
|
|
@@ -2882,14 +3096,15 @@ var SQLService = class extends BaseService {
|
|
|
2882
3096
|
return err(authRequiredError("sql"));
|
|
2883
3097
|
}
|
|
2884
3098
|
try {
|
|
3099
|
+
const requestedActions = this.actionsForSqlBatch(statements);
|
|
2885
3100
|
const response = await this.invokeSQL(
|
|
2886
3101
|
dbName,
|
|
2887
|
-
|
|
3102
|
+
requestedActions,
|
|
2888
3103
|
{ action: "batch", statements },
|
|
2889
3104
|
options?.signal
|
|
2890
3105
|
);
|
|
2891
3106
|
if (!response.ok) {
|
|
2892
|
-
return this.handleErrorResponse(response, "batch");
|
|
3107
|
+
return this.handleErrorResponse(response, "batch", dbName, requestedActions);
|
|
2893
3108
|
}
|
|
2894
3109
|
const data = await response.json();
|
|
2895
3110
|
return ok(data);
|
|
@@ -2911,7 +3126,7 @@ var SQLService = class extends BaseService {
|
|
|
2911
3126
|
options?.signal
|
|
2912
3127
|
);
|
|
2913
3128
|
if (!response.ok) {
|
|
2914
|
-
return this.handleErrorResponse(response, "executeStatement");
|
|
3129
|
+
return this.handleErrorResponse(response, "executeStatement", dbName, [SQLAction.WRITE]);
|
|
2915
3130
|
}
|
|
2916
3131
|
const data = await response.json();
|
|
2917
3132
|
return ok(data);
|
|
@@ -2933,7 +3148,7 @@ var SQLService = class extends BaseService {
|
|
|
2933
3148
|
options?.signal
|
|
2934
3149
|
);
|
|
2935
3150
|
if (!response.ok) {
|
|
2936
|
-
return this.handleErrorResponse(response, "export");
|
|
3151
|
+
return this.handleErrorResponse(response, "export", dbName, [SQLAction.READ]);
|
|
2937
3152
|
}
|
|
2938
3153
|
const resp = response;
|
|
2939
3154
|
if (typeof resp.blob === "function") {
|
|
@@ -3067,7 +3282,7 @@ var SQLService = class extends BaseService {
|
|
|
3067
3282
|
}))
|
|
3068
3283
|
);
|
|
3069
3284
|
}
|
|
3070
|
-
async handleErrorResponse(response, operation) {
|
|
3285
|
+
async handleErrorResponse(response, operation, dbName, requestedActions) {
|
|
3071
3286
|
const errorText = await response.text();
|
|
3072
3287
|
const meta = responseErrorMeta(response.status, response.statusText, errorText);
|
|
3073
3288
|
if (response.status === 402) {
|
|
@@ -3086,9 +3301,11 @@ var SQLService = class extends BaseService {
|
|
|
3086
3301
|
errorBody
|
|
3087
3302
|
);
|
|
3088
3303
|
if (response.status === 401) {
|
|
3089
|
-
const {
|
|
3090
|
-
if (action
|
|
3091
|
-
|
|
3304
|
+
const { action } = parseAuthError(errorText);
|
|
3305
|
+
if (action && requestedActions.includes(action)) {
|
|
3306
|
+
meta.requiredAction = action;
|
|
3307
|
+
meta.resource = `${this.context.session.spaceId}/sql/${dbName}`;
|
|
3308
|
+
}
|
|
3092
3309
|
}
|
|
3093
3310
|
return err(
|
|
3094
3311
|
serviceError(errorCode, message, "sql", { meta })
|
|
@@ -3233,16 +3450,16 @@ var DuckDbDatabaseHandle = class {
|
|
|
3233
3450
|
};
|
|
3234
3451
|
|
|
3235
3452
|
// src/duckdb/types.ts
|
|
3236
|
-
var
|
|
3453
|
+
var import_bootstrap3 = require("@tinycloud/bootstrap");
|
|
3237
3454
|
var DuckDbAction = {
|
|
3238
|
-
READ:
|
|
3239
|
-
WRITE:
|
|
3240
|
-
ADMIN:
|
|
3241
|
-
DESCRIBE:
|
|
3242
|
-
EXPORT:
|
|
3243
|
-
IMPORT:
|
|
3244
|
-
EXECUTE:
|
|
3245
|
-
ALL:
|
|
3455
|
+
READ: import_bootstrap3.DUCKDB.READ,
|
|
3456
|
+
WRITE: import_bootstrap3.DUCKDB.WRITE,
|
|
3457
|
+
ADMIN: import_bootstrap3.DUCKDB.ADMIN,
|
|
3458
|
+
DESCRIBE: import_bootstrap3.DUCKDB.DESCRIBE,
|
|
3459
|
+
EXPORT: import_bootstrap3.DUCKDB.EXPORT,
|
|
3460
|
+
IMPORT: import_bootstrap3.DUCKDB.IMPORT,
|
|
3461
|
+
EXECUTE: import_bootstrap3.DUCKDB.EXECUTE,
|
|
3462
|
+
ALL: import_bootstrap3.DUCKDB.ALL
|
|
3246
3463
|
};
|
|
3247
3464
|
|
|
3248
3465
|
// src/duckdb/DuckDbService.ts
|
|
@@ -6067,103 +6284,6 @@ function canonicalHashHex(sha256, value) {
|
|
|
6067
6284
|
return hexEncode2(sha256(utf8Encode(canonical)));
|
|
6068
6285
|
}
|
|
6069
6286
|
|
|
6070
|
-
// src/encryption/networkId.ts
|
|
6071
|
-
var URN_PREFIX = "urn:tinycloud:encryption:";
|
|
6072
|
-
var NETWORK_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
6073
|
-
var PKH_EIP155_DID_RE = /^did:pkh:eip155:(\d+):(0x[a-fA-F0-9]{40})$/;
|
|
6074
|
-
var NetworkIdError = class extends Error {
|
|
6075
|
-
constructor(message) {
|
|
6076
|
-
super(message);
|
|
6077
|
-
this.name = "NetworkIdError";
|
|
6078
|
-
}
|
|
6079
|
-
};
|
|
6080
|
-
function parseNetworkId(networkId) {
|
|
6081
|
-
if (typeof networkId !== "string" || networkId.length === 0) {
|
|
6082
|
-
throw new NetworkIdError("networkId must be a non-empty string");
|
|
6083
|
-
}
|
|
6084
|
-
if (!networkId.startsWith(URN_PREFIX)) {
|
|
6085
|
-
throw new NetworkIdError(
|
|
6086
|
-
`networkId must start with ${URN_PREFIX} (got ${JSON.stringify(networkId)})`
|
|
6087
|
-
);
|
|
6088
|
-
}
|
|
6089
|
-
const body = networkId.slice(URN_PREFIX.length);
|
|
6090
|
-
const lastColon = body.lastIndexOf(":");
|
|
6091
|
-
if (lastColon <= 0 || lastColon === body.length - 1) {
|
|
6092
|
-
throw new NetworkIdError(
|
|
6093
|
-
`networkId missing ownerDid or name segment (got ${JSON.stringify(networkId)})`
|
|
6094
|
-
);
|
|
6095
|
-
}
|
|
6096
|
-
const ownerDid = body.slice(0, lastColon);
|
|
6097
|
-
const name = body.slice(lastColon + 1);
|
|
6098
|
-
if (!ownerDid.startsWith("did:")) {
|
|
6099
|
-
throw new NetworkIdError(
|
|
6100
|
-
`networkId ownerDid must be a DID (got ${JSON.stringify(ownerDid)})`
|
|
6101
|
-
);
|
|
6102
|
-
}
|
|
6103
|
-
const didParts = ownerDid.split(":");
|
|
6104
|
-
if (didParts.length < 3 || didParts.some((p) => p.length === 0)) {
|
|
6105
|
-
throw new NetworkIdError(
|
|
6106
|
-
`networkId ownerDid is not a well-formed DID (got ${JSON.stringify(ownerDid)})`
|
|
6107
|
-
);
|
|
6108
|
-
}
|
|
6109
|
-
if (!NETWORK_NAME_RE.test(name)) {
|
|
6110
|
-
throw new NetworkIdError(
|
|
6111
|
-
`networkId name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
6112
|
-
);
|
|
6113
|
-
}
|
|
6114
|
-
return { networkId, ownerDid, name };
|
|
6115
|
-
}
|
|
6116
|
-
function buildNetworkId(ownerDid, name) {
|
|
6117
|
-
if (typeof ownerDid !== "string" || !ownerDid.startsWith("did:")) {
|
|
6118
|
-
throw new NetworkIdError("ownerDid must be a DID");
|
|
6119
|
-
}
|
|
6120
|
-
if (typeof name !== "string" || !NETWORK_NAME_RE.test(name)) {
|
|
6121
|
-
throw new NetworkIdError(
|
|
6122
|
-
`network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
6123
|
-
);
|
|
6124
|
-
}
|
|
6125
|
-
const networkId = `${URN_PREFIX}${ownerDid}:${name}`;
|
|
6126
|
-
parseNetworkId(networkId);
|
|
6127
|
-
return networkId;
|
|
6128
|
-
}
|
|
6129
|
-
function isNetworkId(networkId) {
|
|
6130
|
-
if (typeof networkId !== "string") {
|
|
6131
|
-
return false;
|
|
6132
|
-
}
|
|
6133
|
-
try {
|
|
6134
|
-
parseNetworkId(networkId);
|
|
6135
|
-
return true;
|
|
6136
|
-
} catch {
|
|
6137
|
-
return false;
|
|
6138
|
-
}
|
|
6139
|
-
}
|
|
6140
|
-
function parsePkhOwnerDid(ownerDid) {
|
|
6141
|
-
const match = ownerDid.match(PKH_EIP155_DID_RE);
|
|
6142
|
-
if (!match) return null;
|
|
6143
|
-
return {
|
|
6144
|
-
chainId: match[1],
|
|
6145
|
-
address: match[2].toLowerCase()
|
|
6146
|
-
};
|
|
6147
|
-
}
|
|
6148
|
-
function ownerDidMatches(a, b) {
|
|
6149
|
-
const aPkh = parsePkhOwnerDid(a);
|
|
6150
|
-
const bPkh = parsePkhOwnerDid(b);
|
|
6151
|
-
if (aPkh && bPkh) {
|
|
6152
|
-
return aPkh.chainId === bPkh.chainId && aPkh.address === bPkh.address;
|
|
6153
|
-
}
|
|
6154
|
-
return a === b;
|
|
6155
|
-
}
|
|
6156
|
-
function networkDiscoveryKey(name) {
|
|
6157
|
-
if (!NETWORK_NAME_RE.test(name)) {
|
|
6158
|
-
throw new NetworkIdError(
|
|
6159
|
-
`network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
6160
|
-
);
|
|
6161
|
-
}
|
|
6162
|
-
return `.well-known/encryption/network/${name}`;
|
|
6163
|
-
}
|
|
6164
|
-
var ENCRYPTION_NETWORK_URN_PREFIX = URN_PREFIX;
|
|
6165
|
-
var NETWORK_NAME_PATTERN = NETWORK_NAME_RE;
|
|
6166
|
-
|
|
6167
6287
|
// src/encryption/types.ts
|
|
6168
6288
|
var DEFAULT_ENCRYPTION_ALG = "x25519-aes256gcm/v1";
|
|
6169
6289
|
var ENVELOPE_VERSION = 1;
|
|
@@ -7107,6 +7227,7 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
|
|
|
7107
7227
|
ok,
|
|
7108
7228
|
openWrappedKey,
|
|
7109
7229
|
parseAuthError,
|
|
7230
|
+
parseCapabilityResource,
|
|
7110
7231
|
parseNetworkId,
|
|
7111
7232
|
parsePermissionHint,
|
|
7112
7233
|
parsePermissionHintFromErrorText,
|
|
@@ -7132,6 +7253,7 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
|
|
|
7132
7253
|
validateServiceResponseEvent,
|
|
7133
7254
|
validateServiceSession,
|
|
7134
7255
|
validateTelemetrySpanEvent,
|
|
7256
|
+
validatedCapabilityOf,
|
|
7135
7257
|
verifyDecryptResponse,
|
|
7136
7258
|
wrapError
|
|
7137
7259
|
});
|