@tinycloud/sdk-services 3.1.0-beta.1 → 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 +344 -227
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +53 -1
- package/dist/index.d.ts +53 -1
- package/dist/index.js +319 -209
- package/dist/index.js.map +1 -1
- package/dist/kv/index.cjs +236 -58
- package/dist/kv/index.cjs.map +1 -1
- package/dist/kv/index.d.cts +5 -2
- package/dist/kv/index.d.ts +5 -2
- package/dist/kv/index.js +236 -58
- package/dist/kv/index.js.map +1 -1
- package/dist/sql/index.cjs +77 -26
- package/dist/sql/index.cjs.map +1 -1
- package/dist/sql/index.js +68 -17
- package/dist/sql/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -55,6 +55,8 @@ __export(src_exports, {
|
|
|
55
55
|
SQLAction: () => SQLAction,
|
|
56
56
|
SQLMigrations: () => SQLMigrations,
|
|
57
57
|
SQLService: () => SQLService,
|
|
58
|
+
STORAGE_FULL_MESSAGE: () => STORAGE_FULL_MESSAGE,
|
|
59
|
+
STORAGE_WRITE_TOO_LARGE_MESSAGE: () => STORAGE_WRITE_TOO_LARGE_MESSAGE,
|
|
58
60
|
SecretsService: () => SecretsService,
|
|
59
61
|
ServiceContext: () => ServiceContext,
|
|
60
62
|
ServiceErrorEventSchema: () => ServiceErrorEventSchema,
|
|
@@ -107,22 +109,26 @@ __export(src_exports, {
|
|
|
107
109
|
hexEncode: () => hexEncode2,
|
|
108
110
|
installTinyCloudDebugGlobals: () => installTinyCloudDebugGlobals,
|
|
109
111
|
isNetworkId: () => isNetworkId,
|
|
112
|
+
isStorageFullError: () => isStorageFullError,
|
|
110
113
|
networkDiscoveryKey: () => networkDiscoveryKey,
|
|
111
114
|
networkError: () => networkError,
|
|
112
115
|
notFoundError: () => notFoundError,
|
|
113
116
|
ok: () => ok,
|
|
114
117
|
openWrappedKey: () => openWrappedKey,
|
|
115
118
|
parseAuthError: () => parseAuthError,
|
|
119
|
+
parseCapabilityResource: () => parseCapabilityResource,
|
|
116
120
|
parseNetworkId: () => parseNetworkId,
|
|
117
121
|
parsePermissionHint: () => parsePermissionHint,
|
|
118
122
|
parsePermissionHintFromErrorText: () => parsePermissionHintFromErrorText,
|
|
119
123
|
parseSecretCatalogKey: () => parseSecretCatalogKey,
|
|
124
|
+
parseStorageQuotaBytes: () => parseStorageQuotaBytes,
|
|
120
125
|
permissionDeniedError: () => permissionDeniedError,
|
|
121
126
|
resolveSecretListPrefix: () => resolveSecretListPrefix,
|
|
122
127
|
resolveSecretPath: () => resolveSecretPath,
|
|
123
128
|
serviceError: () => serviceError,
|
|
124
129
|
storageLimitReachedError: () => storageLimitReachedError,
|
|
125
130
|
storageQuotaExceededError: () => storageQuotaExceededError,
|
|
131
|
+
storageRejectionError: () => storageRejectionError,
|
|
126
132
|
timeoutError: () => timeoutError,
|
|
127
133
|
tinyCloudDebugLogger: () => tinyCloudDebugLogger,
|
|
128
134
|
utf8Decode: () => utf8Decode,
|
|
@@ -136,6 +142,7 @@ __export(src_exports, {
|
|
|
136
142
|
validateServiceResponseEvent: () => validateServiceResponseEvent,
|
|
137
143
|
validateServiceSession: () => validateServiceSession,
|
|
138
144
|
validateTelemetrySpanEvent: () => validateTelemetrySpanEvent,
|
|
145
|
+
validatedCapabilityOf: () => validatedCapabilityOf,
|
|
139
146
|
verifyDecryptResponse: () => verifyDecryptResponse,
|
|
140
147
|
wrapError: () => wrapError
|
|
141
148
|
});
|
|
@@ -1039,6 +1046,106 @@ var ServiceContext = class {
|
|
|
1039
1046
|
}
|
|
1040
1047
|
};
|
|
1041
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
|
+
|
|
1042
1149
|
// src/errors.ts
|
|
1043
1150
|
function parsePermissionHint(value) {
|
|
1044
1151
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
@@ -1136,6 +1243,70 @@ function parseAuthError(responseText) {
|
|
|
1136
1243
|
function authUnauthorizedError(service, message, meta) {
|
|
1137
1244
|
return serviceError(ErrorCodes.AUTH_UNAUTHORIZED, message, service, { meta });
|
|
1138
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
|
+
}
|
|
1139
1310
|
var MAX_CAUSE_DEPTH = 8;
|
|
1140
1311
|
function errorStatusOf(node) {
|
|
1141
1312
|
const meta = node.meta;
|
|
@@ -1183,6 +1354,23 @@ function storageLimitReachedError(service, message, meta) {
|
|
|
1183
1354
|
meta
|
|
1184
1355
|
};
|
|
1185
1356
|
}
|
|
1357
|
+
var STORAGE_FULL_MESSAGE = "TinyCloud storage is full, so this change was not saved. Reading still works. Free up space or upgrade your plan to save again.";
|
|
1358
|
+
var STORAGE_WRITE_TOO_LARGE_MESSAGE = "This change is larger than the TinyCloud storage you have left, so it was not saved. Reading still works. Free up space or upgrade your plan to save it.";
|
|
1359
|
+
function parseStorageQuotaBytes(responseText) {
|
|
1360
|
+
const match = responseText.match(/Used:\s*(\d+)\s*bytes,\s*Limit:\s*(\d+)\s*bytes/i);
|
|
1361
|
+
if (!match) return void 0;
|
|
1362
|
+
return {
|
|
1363
|
+
usedBytes: parseInt(match[1], 10),
|
|
1364
|
+
limitBytes: parseInt(match[2], 10)
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
function storageRejectionError(service, status, meta, responseText) {
|
|
1368
|
+
const quotaMeta = { ...meta, ...parseStorageQuotaBytes(responseText) };
|
|
1369
|
+
return status === 402 ? storageQuotaExceededError(service, STORAGE_FULL_MESSAGE, quotaMeta) : storageLimitReachedError(service, STORAGE_WRITE_TOO_LARGE_MESSAGE, quotaMeta);
|
|
1370
|
+
}
|
|
1371
|
+
function isStorageFullError(error) {
|
|
1372
|
+
return error?.code === ErrorCodes.STORAGE_QUOTA_EXCEEDED || error?.code === ErrorCodes.STORAGE_LIMIT_REACHED;
|
|
1373
|
+
}
|
|
1186
1374
|
function wrapError(service, error, defaultCode = ErrorCodes.NETWORK_ERROR) {
|
|
1187
1375
|
if (error instanceof Error) {
|
|
1188
1376
|
if (error.name === "AbortError") {
|
|
@@ -1576,47 +1764,21 @@ var KVService = class extends BaseService {
|
|
|
1576
1764
|
get config() {
|
|
1577
1765
|
return this._config;
|
|
1578
1766
|
}
|
|
1579
|
-
// Parses "Used: X bytes, Limit: Y bytes" from tinycloud-node error responses
|
|
1580
|
-
parseQuotaInfo(errorText) {
|
|
1581
|
-
const match = errorText.match(
|
|
1582
|
-
/Used:\s*(\d+)\s*bytes,\s*Limit:\s*(\d+)\s*bytes/i
|
|
1583
|
-
);
|
|
1584
|
-
if (match) {
|
|
1585
|
-
return {
|
|
1586
|
-
usedBytes: parseInt(match[1], 10),
|
|
1587
|
-
limitBytes: parseInt(match[2], 10)
|
|
1588
|
-
};
|
|
1589
|
-
}
|
|
1590
|
-
return void 0;
|
|
1591
|
-
}
|
|
1592
1767
|
handleQuotaErrorResponse(response, errorText, key) {
|
|
1593
|
-
if (response.status
|
|
1594
|
-
|
|
1595
|
-
return err(
|
|
1596
|
-
storageQuotaExceededError(
|
|
1597
|
-
"kv",
|
|
1598
|
-
`Storage quota exceeded for key ${JSON.stringify(key)}: ${errorText}`,
|
|
1599
|
-
{
|
|
1600
|
-
status: response.status,
|
|
1601
|
-
...quotaInfo ? { usedBytes: quotaInfo.usedBytes, limitBytes: quotaInfo.limitBytes } : {}
|
|
1602
|
-
}
|
|
1603
|
-
)
|
|
1604
|
-
);
|
|
1768
|
+
if (response.status !== 402 && response.status !== 413) {
|
|
1769
|
+
return void 0;
|
|
1605
1770
|
}
|
|
1606
|
-
if (response.status === 413) {
|
|
1607
|
-
|
|
1608
|
-
return err(
|
|
1609
|
-
storageLimitReachedError(
|
|
1610
|
-
"kv",
|
|
1611
|
-
`Storage limit reached for key ${JSON.stringify(key)}: ${errorText}`,
|
|
1612
|
-
{
|
|
1613
|
-
status: response.status,
|
|
1614
|
-
...quotaInfo ? { usedBytes: quotaInfo.usedBytes, limitBytes: quotaInfo.limitBytes } : {}
|
|
1615
|
-
}
|
|
1616
|
-
)
|
|
1617
|
-
);
|
|
1771
|
+
if (response.status === 413 && !parseStorageQuotaBytes(errorText)) {
|
|
1772
|
+
return void 0;
|
|
1618
1773
|
}
|
|
1619
|
-
return
|
|
1774
|
+
return err(
|
|
1775
|
+
storageRejectionError(
|
|
1776
|
+
"kv",
|
|
1777
|
+
response.status,
|
|
1778
|
+
{ status: response.status, statusText: response.statusText, key },
|
|
1779
|
+
errorText
|
|
1780
|
+
)
|
|
1781
|
+
);
|
|
1620
1782
|
}
|
|
1621
1783
|
/**
|
|
1622
1784
|
* Classify a KV 404 by reading the response body once.
|
|
@@ -1892,12 +2054,14 @@ var KVService = class extends BaseService {
|
|
|
1892
2054
|
signal: this.combineSignals(options?.signal)
|
|
1893
2055
|
});
|
|
1894
2056
|
if (!response.ok) {
|
|
1895
|
-
const errorText = await response.text();
|
|
2057
|
+
const errorText = response.status === 401 || response.status === 403 ? await this.readAuthorizationText(response) : await response.text();
|
|
1896
2058
|
if (response.status === 401 || response.status === 403) {
|
|
1897
2059
|
return this.authorizationFailure(
|
|
1898
2060
|
`Failed to batch read ${keys.length} key(s)`,
|
|
1899
2061
|
response,
|
|
1900
|
-
errorText
|
|
2062
|
+
errorText,
|
|
2063
|
+
paths,
|
|
2064
|
+
action
|
|
1901
2065
|
);
|
|
1902
2066
|
}
|
|
1903
2067
|
if (response.status === 413) {
|
|
@@ -1987,7 +2151,7 @@ var KVService = class extends BaseService {
|
|
|
1987
2151
|
return text;
|
|
1988
2152
|
}
|
|
1989
2153
|
}
|
|
1990
|
-
async createSignedReadUrlError(response, key) {
|
|
2154
|
+
async createSignedReadUrlError(response, key, path) {
|
|
1991
2155
|
let errorText = response.statusText;
|
|
1992
2156
|
try {
|
|
1993
2157
|
const text = await response.text();
|
|
@@ -2000,7 +2164,9 @@ var KVService = class extends BaseService {
|
|
|
2000
2164
|
return this.authorizationFailure(
|
|
2001
2165
|
`Failed to create signed read URL for key ${JSON.stringify(key)}`,
|
|
2002
2166
|
response,
|
|
2003
|
-
errorText
|
|
2167
|
+
errorText,
|
|
2168
|
+
[path],
|
|
2169
|
+
KVAction.GET
|
|
2004
2170
|
);
|
|
2005
2171
|
}
|
|
2006
2172
|
const code = response.status === 400 ? ErrorCodes.INVALID_INPUT : ErrorCodes.NETWORK_ERROR;
|
|
@@ -2013,21 +2179,42 @@ var KVService = class extends BaseService {
|
|
|
2013
2179
|
)
|
|
2014
2180
|
);
|
|
2015
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
|
+
}
|
|
2016
2190
|
/**
|
|
2017
2191
|
* AUTH_UNAUTHORIZED for a 401/403 response. The message keeps the HTTP
|
|
2018
2192
|
* status and the server text (`<context>: <status> - <text>`, the same
|
|
2019
2193
|
* shape as every other KV failure) so callers that rethrow only the message
|
|
2020
2194
|
* still see the status; `meta.status` carries it for typed callers.
|
|
2021
|
-
*
|
|
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.
|
|
2022
2198
|
*/
|
|
2023
|
-
authorizationFailure(context, response, errorText, extraMeta = {}) {
|
|
2199
|
+
authorizationFailure(context, response, errorText, paths, expectedAction, extraMeta = {}) {
|
|
2024
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;
|
|
2025
2213
|
const detail = errorText.trim().length > 0 ? errorText : response.statusText || "authorization failed";
|
|
2026
2214
|
return err(authUnauthorizedError("kv", `${context}: ${response.status} - ${detail}`, {
|
|
2027
2215
|
status: response.status,
|
|
2028
|
-
...
|
|
2029
|
-
...
|
|
2030
|
-
...extraMeta
|
|
2216
|
+
...extraMeta,
|
|
2217
|
+
...capability
|
|
2031
2218
|
}));
|
|
2032
2219
|
}
|
|
2033
2220
|
normalizeSignedReadUrlResponse(data) {
|
|
@@ -2071,13 +2258,24 @@ var KVService = class extends BaseService {
|
|
|
2071
2258
|
);
|
|
2072
2259
|
if (!response.ok) {
|
|
2073
2260
|
if (response.status === 401 || response.status === 403) {
|
|
2074
|
-
const errorText2 = await
|
|
2261
|
+
const errorText2 = await this.readAuthorizationText(response);
|
|
2075
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;
|
|
2076
2272
|
return this.authorizationFailure(
|
|
2077
2273
|
`Failed to get key ${JSON.stringify(key)}`,
|
|
2078
2274
|
response,
|
|
2079
2275
|
errorText2,
|
|
2080
|
-
|
|
2276
|
+
[path],
|
|
2277
|
+
KVAction.GET,
|
|
2278
|
+
structuredCapability ? { permissionHint, ...structuredCapability } : {}
|
|
2081
2279
|
);
|
|
2082
2280
|
}
|
|
2083
2281
|
if (response.status === 404) {
|
|
@@ -2154,7 +2352,9 @@ var KVService = class extends BaseService {
|
|
|
2154
2352
|
return this.authorizationFailure(
|
|
2155
2353
|
`Failed to put key ${JSON.stringify(key)}`,
|
|
2156
2354
|
response,
|
|
2157
|
-
await
|
|
2355
|
+
await this.readAuthorizationText(response),
|
|
2356
|
+
[path],
|
|
2357
|
+
KVAction.PUT
|
|
2158
2358
|
);
|
|
2159
2359
|
}
|
|
2160
2360
|
if (!response.ok) {
|
|
@@ -2283,7 +2483,9 @@ var KVService = class extends BaseService {
|
|
|
2283
2483
|
return this.authorizationFailure(
|
|
2284
2484
|
`Failed to batch put ${items.length} key(s)`,
|
|
2285
2485
|
response,
|
|
2286
|
-
errorText
|
|
2486
|
+
errorText,
|
|
2487
|
+
paths,
|
|
2488
|
+
KVAction.PUT
|
|
2287
2489
|
);
|
|
2288
2490
|
}
|
|
2289
2491
|
const quotaError = this.handleQuotaErrorResponse(
|
|
@@ -2404,11 +2606,13 @@ var KVService = class extends BaseService {
|
|
|
2404
2606
|
}
|
|
2405
2607
|
);
|
|
2406
2608
|
if (!response.ok) {
|
|
2407
|
-
if (response.status === 401) {
|
|
2609
|
+
if (response.status === 401 || response.status === 403) {
|
|
2408
2610
|
return this.authorizationFailure(
|
|
2409
2611
|
"Failed to list keys",
|
|
2410
2612
|
response,
|
|
2411
|
-
await
|
|
2613
|
+
await this.readAuthorizationText(response),
|
|
2614
|
+
[listPath],
|
|
2615
|
+
KVAction.LIST
|
|
2412
2616
|
);
|
|
2413
2617
|
}
|
|
2414
2618
|
const errorText = await response.text();
|
|
@@ -2457,11 +2661,13 @@ var KVService = class extends BaseService {
|
|
|
2457
2661
|
options?.ifMatch === void 0 ? void 0 : { "if-match": options.ifMatch }
|
|
2458
2662
|
);
|
|
2459
2663
|
if (!response.ok) {
|
|
2460
|
-
if (response.status === 401) {
|
|
2664
|
+
if (response.status === 401 || response.status === 403) {
|
|
2461
2665
|
return this.authorizationFailure(
|
|
2462
2666
|
`Failed to delete key ${JSON.stringify(key)}`,
|
|
2463
2667
|
response,
|
|
2464
|
-
await
|
|
2668
|
+
await this.readAuthorizationText(response),
|
|
2669
|
+
[path],
|
|
2670
|
+
KVAction.DELETE
|
|
2465
2671
|
);
|
|
2466
2672
|
}
|
|
2467
2673
|
if (response.status === 404) {
|
|
@@ -2519,11 +2725,13 @@ var KVService = class extends BaseService {
|
|
|
2519
2725
|
options?.signal
|
|
2520
2726
|
);
|
|
2521
2727
|
if (!response.ok) {
|
|
2522
|
-
if (response.status === 401) {
|
|
2728
|
+
if (response.status === 401 || response.status === 403) {
|
|
2523
2729
|
return this.authorizationFailure(
|
|
2524
2730
|
`Failed to get metadata for key ${JSON.stringify(key)}`,
|
|
2525
2731
|
response,
|
|
2526
|
-
await
|
|
2732
|
+
await this.readAuthorizationText(response),
|
|
2733
|
+
[path],
|
|
2734
|
+
KVAction.HEAD
|
|
2527
2735
|
);
|
|
2528
2736
|
}
|
|
2529
2737
|
if (response.status === 404) {
|
|
@@ -2590,7 +2798,7 @@ var KVService = class extends BaseService {
|
|
|
2590
2798
|
signal: this.combineSignals(options?.signal)
|
|
2591
2799
|
});
|
|
2592
2800
|
if (!response.ok) {
|
|
2593
|
-
return this.createSignedReadUrlError(response, key);
|
|
2801
|
+
return this.createSignedReadUrlError(response, key, path);
|
|
2594
2802
|
}
|
|
2595
2803
|
const signedUrl = this.normalizeSignedReadUrlResponse(
|
|
2596
2804
|
await response.json()
|
|
@@ -2733,16 +2941,16 @@ var DatabaseHandle = class {
|
|
|
2733
2941
|
};
|
|
2734
2942
|
|
|
2735
2943
|
// src/sql/types.ts
|
|
2736
|
-
var
|
|
2944
|
+
var import_bootstrap2 = require("@tinycloud/bootstrap");
|
|
2737
2945
|
var SQLAction = {
|
|
2738
|
-
READ:
|
|
2739
|
-
WRITE:
|
|
2740
|
-
SCHEMA:
|
|
2741
|
-
ADMIN:
|
|
2742
|
-
SELECT:
|
|
2743
|
-
EXECUTE:
|
|
2744
|
-
EXPORT:
|
|
2745
|
-
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
|
|
2746
2954
|
};
|
|
2747
2955
|
|
|
2748
2956
|
// src/sql/SQLService.ts
|
|
@@ -2828,14 +3036,15 @@ var SQLService = class extends BaseService {
|
|
|
2828
3036
|
};
|
|
2829
3037
|
if (options?.maxRows !== void 0) body.maxRows = options.maxRows;
|
|
2830
3038
|
if (options?.maxBytes !== void 0) body.maxBytes = options.maxBytes;
|
|
3039
|
+
const action = this.actionForSql(sql, SQLAction.READ);
|
|
2831
3040
|
const response = await this.invokeSQL(
|
|
2832
3041
|
dbName,
|
|
2833
|
-
|
|
3042
|
+
action,
|
|
2834
3043
|
body,
|
|
2835
3044
|
options?.signal
|
|
2836
3045
|
);
|
|
2837
3046
|
if (!response.ok) {
|
|
2838
|
-
return this.handleErrorResponse(response, "query");
|
|
3047
|
+
return this.handleErrorResponse(response, "query", dbName, [action]);
|
|
2839
3048
|
}
|
|
2840
3049
|
const data = await response.json();
|
|
2841
3050
|
return ok(data);
|
|
@@ -2864,14 +3073,15 @@ var SQLService = class extends BaseService {
|
|
|
2864
3073
|
(statement) => this.actionForSql(statement, SQLAction.SCHEMA)
|
|
2865
3074
|
)
|
|
2866
3075
|
];
|
|
3076
|
+
const requestedActions = this.dedupeActions(actions);
|
|
2867
3077
|
const response = await this.invokeSQL(
|
|
2868
3078
|
dbName,
|
|
2869
|
-
|
|
3079
|
+
requestedActions,
|
|
2870
3080
|
body,
|
|
2871
3081
|
options?.signal
|
|
2872
3082
|
);
|
|
2873
3083
|
if (!response.ok) {
|
|
2874
|
-
return this.handleErrorResponse(response, "execute");
|
|
3084
|
+
return this.handleErrorResponse(response, "execute", dbName, requestedActions);
|
|
2875
3085
|
}
|
|
2876
3086
|
const data = await response.json();
|
|
2877
3087
|
return ok(data);
|
|
@@ -2886,14 +3096,15 @@ var SQLService = class extends BaseService {
|
|
|
2886
3096
|
return err(authRequiredError("sql"));
|
|
2887
3097
|
}
|
|
2888
3098
|
try {
|
|
3099
|
+
const requestedActions = this.actionsForSqlBatch(statements);
|
|
2889
3100
|
const response = await this.invokeSQL(
|
|
2890
3101
|
dbName,
|
|
2891
|
-
|
|
3102
|
+
requestedActions,
|
|
2892
3103
|
{ action: "batch", statements },
|
|
2893
3104
|
options?.signal
|
|
2894
3105
|
);
|
|
2895
3106
|
if (!response.ok) {
|
|
2896
|
-
return this.handleErrorResponse(response, "batch");
|
|
3107
|
+
return this.handleErrorResponse(response, "batch", dbName, requestedActions);
|
|
2897
3108
|
}
|
|
2898
3109
|
const data = await response.json();
|
|
2899
3110
|
return ok(data);
|
|
@@ -2915,7 +3126,7 @@ var SQLService = class extends BaseService {
|
|
|
2915
3126
|
options?.signal
|
|
2916
3127
|
);
|
|
2917
3128
|
if (!response.ok) {
|
|
2918
|
-
return this.handleErrorResponse(response, "executeStatement");
|
|
3129
|
+
return this.handleErrorResponse(response, "executeStatement", dbName, [SQLAction.WRITE]);
|
|
2919
3130
|
}
|
|
2920
3131
|
const data = await response.json();
|
|
2921
3132
|
return ok(data);
|
|
@@ -2937,7 +3148,7 @@ var SQLService = class extends BaseService {
|
|
|
2937
3148
|
options?.signal
|
|
2938
3149
|
);
|
|
2939
3150
|
if (!response.ok) {
|
|
2940
|
-
return this.handleErrorResponse(response, "export");
|
|
3151
|
+
return this.handleErrorResponse(response, "export", dbName, [SQLAction.READ]);
|
|
2941
3152
|
}
|
|
2942
3153
|
const resp = response;
|
|
2943
3154
|
if (typeof resp.blob === "function") {
|
|
@@ -2953,21 +3164,24 @@ var SQLService = class extends BaseService {
|
|
|
2953
3164
|
}
|
|
2954
3165
|
async applyMigrationsOnDbUnlocked(dbName, options) {
|
|
2955
3166
|
return this.withTelemetry("migrations.apply", dbName, async () => {
|
|
2956
|
-
const created = await this.ensureMigrationsTable(dbName, options.signal);
|
|
2957
|
-
if (!created.ok) return created;
|
|
2958
3167
|
const listed = await this.queryOnDb(
|
|
2959
3168
|
dbName,
|
|
2960
3169
|
`SELECT id FROM ${MIGRATIONS_TABLE} WHERE namespace = ? ORDER BY applied_at, id`,
|
|
2961
3170
|
[options.namespace],
|
|
2962
3171
|
{ signal: options.signal }
|
|
2963
3172
|
);
|
|
2964
|
-
|
|
3173
|
+
const tableMissing = !listed.ok && (listed.error.code === ErrorCodes.SQL_DATABASE_NOT_FOUND || isMissingTableError(listed.error.message));
|
|
3174
|
+
if (!listed.ok && !tableMissing) return listed;
|
|
2965
3175
|
const appliedIds = new Set(
|
|
2966
|
-
listed.data.rows.map((row) => rowValue(row, 0)).filter((id) => typeof id === "string")
|
|
3176
|
+
(listed.ok ? listed.data.rows : []).map((row) => rowValue(row, 0)).filter((id) => typeof id === "string")
|
|
2967
3177
|
);
|
|
2968
3178
|
const skipped = options.migrations.filter((migration) => appliedIds.has(migration.id)).map((migration) => migration.id);
|
|
2969
3179
|
const pending = options.migrations.filter((migration) => !appliedIds.has(migration.id));
|
|
2970
3180
|
const applied = [];
|
|
3181
|
+
if (pending.length > 0 && tableMissing) {
|
|
3182
|
+
const created = await this.ensureMigrationsTable(dbName, options.signal);
|
|
3183
|
+
if (!created.ok) return created;
|
|
3184
|
+
}
|
|
2971
3185
|
for (const migration of pending) {
|
|
2972
3186
|
const result = await this.applyOneMigration(dbName, options.namespace, migration, options.signal);
|
|
2973
3187
|
if (!result.ok) return result;
|
|
@@ -3068,8 +3282,12 @@ var SQLService = class extends BaseService {
|
|
|
3068
3282
|
}))
|
|
3069
3283
|
);
|
|
3070
3284
|
}
|
|
3071
|
-
async handleErrorResponse(response, operation) {
|
|
3285
|
+
async handleErrorResponse(response, operation, dbName, requestedActions) {
|
|
3072
3286
|
const errorText = await response.text();
|
|
3287
|
+
const meta = responseErrorMeta(response.status, response.statusText, errorText);
|
|
3288
|
+
if (response.status === 402) {
|
|
3289
|
+
return err(storageRejectionError("sql", 402, meta, errorText));
|
|
3290
|
+
}
|
|
3073
3291
|
const errorBody = parseServiceErrorBody(errorText);
|
|
3074
3292
|
const errorCode = this.mapHttpStatusToErrorCode(
|
|
3075
3293
|
response.status,
|
|
@@ -3082,11 +3300,12 @@ var SQLService = class extends BaseService {
|
|
|
3082
3300
|
errorText,
|
|
3083
3301
|
errorBody
|
|
3084
3302
|
);
|
|
3085
|
-
const meta = responseErrorMeta(response.status, response.statusText, errorText);
|
|
3086
3303
|
if (response.status === 401) {
|
|
3087
|
-
const {
|
|
3088
|
-
if (action
|
|
3089
|
-
|
|
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
|
+
}
|
|
3090
3309
|
}
|
|
3091
3310
|
return err(
|
|
3092
3311
|
serviceError(errorCode, message, "sql", { meta })
|
|
@@ -3183,6 +3402,9 @@ function serializeSqlValues(values) {
|
|
|
3183
3402
|
(value) => value instanceof Uint8Array ? Array.from(value) : value
|
|
3184
3403
|
);
|
|
3185
3404
|
}
|
|
3405
|
+
function isMissingTableError(message) {
|
|
3406
|
+
return /no such table/i.test(message);
|
|
3407
|
+
}
|
|
3186
3408
|
function migrationKey(namespace, id) {
|
|
3187
3409
|
return `${encodeURIComponent(namespace)}:${encodeURIComponent(id)}`;
|
|
3188
3410
|
}
|
|
@@ -3228,16 +3450,16 @@ var DuckDbDatabaseHandle = class {
|
|
|
3228
3450
|
};
|
|
3229
3451
|
|
|
3230
3452
|
// src/duckdb/types.ts
|
|
3231
|
-
var
|
|
3453
|
+
var import_bootstrap3 = require("@tinycloud/bootstrap");
|
|
3232
3454
|
var DuckDbAction = {
|
|
3233
|
-
READ:
|
|
3234
|
-
WRITE:
|
|
3235
|
-
ADMIN:
|
|
3236
|
-
DESCRIBE:
|
|
3237
|
-
EXPORT:
|
|
3238
|
-
IMPORT:
|
|
3239
|
-
EXECUTE:
|
|
3240
|
-
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
|
|
3241
3463
|
};
|
|
3242
3464
|
|
|
3243
3465
|
// src/duckdb/DuckDbService.ts
|
|
@@ -3504,6 +3726,10 @@ var DuckDbService = class extends BaseService {
|
|
|
3504
3726
|
}
|
|
3505
3727
|
async handleErrorResponse(response, operation) {
|
|
3506
3728
|
const errorText = await response.text();
|
|
3729
|
+
const meta = responseErrorMeta(response.status, response.statusText, errorText);
|
|
3730
|
+
if (response.status === 402) {
|
|
3731
|
+
return err(storageRejectionError("duckdb", 402, meta, errorText));
|
|
3732
|
+
}
|
|
3507
3733
|
const errorBody = parseServiceErrorBody(errorText);
|
|
3508
3734
|
const errorCode = this.mapHttpStatusToErrorCode(
|
|
3509
3735
|
response.status,
|
|
@@ -3516,7 +3742,6 @@ var DuckDbService = class extends BaseService {
|
|
|
3516
3742
|
errorText,
|
|
3517
3743
|
errorBody
|
|
3518
3744
|
);
|
|
3519
|
-
const meta = responseErrorMeta(response.status, response.statusText, errorText);
|
|
3520
3745
|
if (response.status === 401) {
|
|
3521
3746
|
const { resource, action } = parseAuthError(errorText);
|
|
3522
3747
|
if (action) meta.requiredAction = action;
|
|
@@ -4488,6 +4713,9 @@ function defaultVaultMessage(input) {
|
|
|
4488
4713
|
return input.message ?? `Public key not found for ${input.did}`;
|
|
4489
4714
|
case "STORAGE_ERROR":
|
|
4490
4715
|
return input.message ?? input.cause.message;
|
|
4716
|
+
case "STORAGE_QUOTA_EXCEEDED":
|
|
4717
|
+
case "STORAGE_LIMIT_REACHED":
|
|
4718
|
+
return input.message;
|
|
4491
4719
|
}
|
|
4492
4720
|
}
|
|
4493
4721
|
function vaultError(input) {
|
|
@@ -4498,6 +4726,15 @@ function vaultError(input) {
|
|
|
4498
4726
|
};
|
|
4499
4727
|
return { ok: false, error };
|
|
4500
4728
|
}
|
|
4729
|
+
function kvWriteError(context, error) {
|
|
4730
|
+
if (error.code === ErrorCodes.STORAGE_QUOTA_EXCEEDED || error.code === ErrorCodes.STORAGE_LIMIT_REACHED) {
|
|
4731
|
+
return vaultError({ code: error.code, message: error.message, meta: error.meta });
|
|
4732
|
+
}
|
|
4733
|
+
return vaultError({
|
|
4734
|
+
code: "STORAGE_ERROR",
|
|
4735
|
+
cause: new Error(`${context}: ${error.message}`)
|
|
4736
|
+
});
|
|
4737
|
+
}
|
|
4501
4738
|
var DataVaultService = class extends BaseService {
|
|
4502
4739
|
/**
|
|
4503
4740
|
* Create a new DataVaultService instance.
|
|
@@ -4780,12 +5017,7 @@ var DataVaultService = class extends BaseService {
|
|
|
4780
5017
|
JSON.stringify(envelopeResult.data)
|
|
4781
5018
|
);
|
|
4782
5019
|
if (!valuePutResult.ok) {
|
|
4783
|
-
return
|
|
4784
|
-
code: "STORAGE_ERROR",
|
|
4785
|
-
cause: new Error(
|
|
4786
|
-
`Failed to store encrypted value: ${valuePutResult.error.message}`
|
|
4787
|
-
)
|
|
4788
|
-
});
|
|
5020
|
+
return kvWriteError("Failed to store encrypted value", valuePutResult.error);
|
|
4789
5021
|
}
|
|
4790
5022
|
return { ok: true, data: void 0 };
|
|
4791
5023
|
} catch (error) {
|
|
@@ -5003,12 +5235,7 @@ var DataVaultService = class extends BaseService {
|
|
|
5003
5235
|
keyPayload
|
|
5004
5236
|
);
|
|
5005
5237
|
if (!keyPutResult.ok) {
|
|
5006
|
-
return
|
|
5007
|
-
code: "STORAGE_ERROR",
|
|
5008
|
-
cause: new Error(
|
|
5009
|
-
`Failed to store key blob: ${keyPutResult.error.message}`
|
|
5010
|
-
)
|
|
5011
|
-
});
|
|
5238
|
+
return kvWriteError("Failed to store key blob", keyPutResult.error);
|
|
5012
5239
|
}
|
|
5013
5240
|
const valuePayload = JSON.stringify({
|
|
5014
5241
|
data: base64Encode(encrypted),
|
|
@@ -5019,12 +5246,7 @@ var DataVaultService = class extends BaseService {
|
|
|
5019
5246
|
valuePayload
|
|
5020
5247
|
);
|
|
5021
5248
|
if (!valuePutResult.ok) {
|
|
5022
|
-
return
|
|
5023
|
-
code: "STORAGE_ERROR",
|
|
5024
|
-
cause: new Error(
|
|
5025
|
-
`Failed to store encrypted value: ${valuePutResult.error.message}`
|
|
5026
|
-
)
|
|
5027
|
-
});
|
|
5249
|
+
return kvWriteError("Failed to store encrypted value", valuePutResult.error);
|
|
5028
5250
|
}
|
|
5029
5251
|
return ok(void 0);
|
|
5030
5252
|
} catch (error) {
|
|
@@ -5355,12 +5577,7 @@ var DataVaultService = class extends BaseService {
|
|
|
5355
5577
|
grantPayload
|
|
5356
5578
|
);
|
|
5357
5579
|
if (!grantPutResult.ok) {
|
|
5358
|
-
return
|
|
5359
|
-
code: "STORAGE_ERROR",
|
|
5360
|
-
cause: new Error(
|
|
5361
|
-
`Failed to store grant: ${grantPutResult.error.message}`
|
|
5362
|
-
)
|
|
5363
|
-
});
|
|
5580
|
+
return kvWriteError("Failed to store grant", grantPutResult.error);
|
|
5364
5581
|
}
|
|
5365
5582
|
return ok(void 0);
|
|
5366
5583
|
} catch (error) {
|
|
@@ -5705,12 +5922,7 @@ var DataVaultService = class extends BaseService {
|
|
|
5705
5922
|
keyPayload
|
|
5706
5923
|
);
|
|
5707
5924
|
if (!keyPutResult.ok) {
|
|
5708
|
-
return
|
|
5709
|
-
code: "STORAGE_ERROR",
|
|
5710
|
-
cause: new Error(
|
|
5711
|
-
`Failed to store rotated key blob: ${keyPutResult.error.message}`
|
|
5712
|
-
)
|
|
5713
|
-
});
|
|
5925
|
+
return kvWriteError("Failed to store rotated key blob", keyPutResult.error);
|
|
5714
5926
|
}
|
|
5715
5927
|
const valuePayload = JSON.stringify({
|
|
5716
5928
|
data: base64Encode(encrypted),
|
|
@@ -5721,12 +5933,7 @@ var DataVaultService = class extends BaseService {
|
|
|
5721
5933
|
valuePayload
|
|
5722
5934
|
);
|
|
5723
5935
|
if (!valuePutResult.ok) {
|
|
5724
|
-
return
|
|
5725
|
-
code: "STORAGE_ERROR",
|
|
5726
|
-
cause: new Error(
|
|
5727
|
-
`Failed to store re-encrypted value: ${valuePutResult.error.message}`
|
|
5728
|
-
)
|
|
5729
|
-
});
|
|
5936
|
+
return kvWriteError("Failed to store re-encrypted value", valuePutResult.error);
|
|
5730
5937
|
}
|
|
5731
5938
|
for (const did of remainingGrantees) {
|
|
5732
5939
|
const grantResult = await this.reencrypt(key, did);
|
|
@@ -6077,103 +6284,6 @@ function canonicalHashHex(sha256, value) {
|
|
|
6077
6284
|
return hexEncode2(sha256(utf8Encode(canonical)));
|
|
6078
6285
|
}
|
|
6079
6286
|
|
|
6080
|
-
// src/encryption/networkId.ts
|
|
6081
|
-
var URN_PREFIX = "urn:tinycloud:encryption:";
|
|
6082
|
-
var NETWORK_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
6083
|
-
var PKH_EIP155_DID_RE = /^did:pkh:eip155:(\d+):(0x[a-fA-F0-9]{40})$/;
|
|
6084
|
-
var NetworkIdError = class extends Error {
|
|
6085
|
-
constructor(message) {
|
|
6086
|
-
super(message);
|
|
6087
|
-
this.name = "NetworkIdError";
|
|
6088
|
-
}
|
|
6089
|
-
};
|
|
6090
|
-
function parseNetworkId(networkId) {
|
|
6091
|
-
if (typeof networkId !== "string" || networkId.length === 0) {
|
|
6092
|
-
throw new NetworkIdError("networkId must be a non-empty string");
|
|
6093
|
-
}
|
|
6094
|
-
if (!networkId.startsWith(URN_PREFIX)) {
|
|
6095
|
-
throw new NetworkIdError(
|
|
6096
|
-
`networkId must start with ${URN_PREFIX} (got ${JSON.stringify(networkId)})`
|
|
6097
|
-
);
|
|
6098
|
-
}
|
|
6099
|
-
const body = networkId.slice(URN_PREFIX.length);
|
|
6100
|
-
const lastColon = body.lastIndexOf(":");
|
|
6101
|
-
if (lastColon <= 0 || lastColon === body.length - 1) {
|
|
6102
|
-
throw new NetworkIdError(
|
|
6103
|
-
`networkId missing ownerDid or name segment (got ${JSON.stringify(networkId)})`
|
|
6104
|
-
);
|
|
6105
|
-
}
|
|
6106
|
-
const ownerDid = body.slice(0, lastColon);
|
|
6107
|
-
const name = body.slice(lastColon + 1);
|
|
6108
|
-
if (!ownerDid.startsWith("did:")) {
|
|
6109
|
-
throw new NetworkIdError(
|
|
6110
|
-
`networkId ownerDid must be a DID (got ${JSON.stringify(ownerDid)})`
|
|
6111
|
-
);
|
|
6112
|
-
}
|
|
6113
|
-
const didParts = ownerDid.split(":");
|
|
6114
|
-
if (didParts.length < 3 || didParts.some((p) => p.length === 0)) {
|
|
6115
|
-
throw new NetworkIdError(
|
|
6116
|
-
`networkId ownerDid is not a well-formed DID (got ${JSON.stringify(ownerDid)})`
|
|
6117
|
-
);
|
|
6118
|
-
}
|
|
6119
|
-
if (!NETWORK_NAME_RE.test(name)) {
|
|
6120
|
-
throw new NetworkIdError(
|
|
6121
|
-
`networkId name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
6122
|
-
);
|
|
6123
|
-
}
|
|
6124
|
-
return { networkId, ownerDid, name };
|
|
6125
|
-
}
|
|
6126
|
-
function buildNetworkId(ownerDid, name) {
|
|
6127
|
-
if (typeof ownerDid !== "string" || !ownerDid.startsWith("did:")) {
|
|
6128
|
-
throw new NetworkIdError("ownerDid must be a DID");
|
|
6129
|
-
}
|
|
6130
|
-
if (typeof name !== "string" || !NETWORK_NAME_RE.test(name)) {
|
|
6131
|
-
throw new NetworkIdError(
|
|
6132
|
-
`network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
6133
|
-
);
|
|
6134
|
-
}
|
|
6135
|
-
const networkId = `${URN_PREFIX}${ownerDid}:${name}`;
|
|
6136
|
-
parseNetworkId(networkId);
|
|
6137
|
-
return networkId;
|
|
6138
|
-
}
|
|
6139
|
-
function isNetworkId(networkId) {
|
|
6140
|
-
if (typeof networkId !== "string") {
|
|
6141
|
-
return false;
|
|
6142
|
-
}
|
|
6143
|
-
try {
|
|
6144
|
-
parseNetworkId(networkId);
|
|
6145
|
-
return true;
|
|
6146
|
-
} catch {
|
|
6147
|
-
return false;
|
|
6148
|
-
}
|
|
6149
|
-
}
|
|
6150
|
-
function parsePkhOwnerDid(ownerDid) {
|
|
6151
|
-
const match = ownerDid.match(PKH_EIP155_DID_RE);
|
|
6152
|
-
if (!match) return null;
|
|
6153
|
-
return {
|
|
6154
|
-
chainId: match[1],
|
|
6155
|
-
address: match[2].toLowerCase()
|
|
6156
|
-
};
|
|
6157
|
-
}
|
|
6158
|
-
function ownerDidMatches(a, b) {
|
|
6159
|
-
const aPkh = parsePkhOwnerDid(a);
|
|
6160
|
-
const bPkh = parsePkhOwnerDid(b);
|
|
6161
|
-
if (aPkh && bPkh) {
|
|
6162
|
-
return aPkh.chainId === bPkh.chainId && aPkh.address === bPkh.address;
|
|
6163
|
-
}
|
|
6164
|
-
return a === b;
|
|
6165
|
-
}
|
|
6166
|
-
function networkDiscoveryKey(name) {
|
|
6167
|
-
if (!NETWORK_NAME_RE.test(name)) {
|
|
6168
|
-
throw new NetworkIdError(
|
|
6169
|
-
`network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
6170
|
-
);
|
|
6171
|
-
}
|
|
6172
|
-
return `.well-known/encryption/network/${name}`;
|
|
6173
|
-
}
|
|
6174
|
-
var ENCRYPTION_NETWORK_URN_PREFIX = URN_PREFIX;
|
|
6175
|
-
var NETWORK_NAME_PATTERN = NETWORK_NAME_RE;
|
|
6176
|
-
|
|
6177
6287
|
// src/encryption/types.ts
|
|
6178
6288
|
var DEFAULT_ENCRYPTION_ALG = "x25519-aes256gcm/v1";
|
|
6179
6289
|
var ENVELOPE_VERSION = 1;
|
|
@@ -7056,6 +7166,8 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
|
|
|
7056
7166
|
SQLAction,
|
|
7057
7167
|
SQLMigrations,
|
|
7058
7168
|
SQLService,
|
|
7169
|
+
STORAGE_FULL_MESSAGE,
|
|
7170
|
+
STORAGE_WRITE_TOO_LARGE_MESSAGE,
|
|
7059
7171
|
SecretsService,
|
|
7060
7172
|
ServiceContext,
|
|
7061
7173
|
ServiceErrorEventSchema,
|
|
@@ -7108,22 +7220,26 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
|
|
|
7108
7220
|
hexEncode,
|
|
7109
7221
|
installTinyCloudDebugGlobals,
|
|
7110
7222
|
isNetworkId,
|
|
7223
|
+
isStorageFullError,
|
|
7111
7224
|
networkDiscoveryKey,
|
|
7112
7225
|
networkError,
|
|
7113
7226
|
notFoundError,
|
|
7114
7227
|
ok,
|
|
7115
7228
|
openWrappedKey,
|
|
7116
7229
|
parseAuthError,
|
|
7230
|
+
parseCapabilityResource,
|
|
7117
7231
|
parseNetworkId,
|
|
7118
7232
|
parsePermissionHint,
|
|
7119
7233
|
parsePermissionHintFromErrorText,
|
|
7120
7234
|
parseSecretCatalogKey,
|
|
7235
|
+
parseStorageQuotaBytes,
|
|
7121
7236
|
permissionDeniedError,
|
|
7122
7237
|
resolveSecretListPrefix,
|
|
7123
7238
|
resolveSecretPath,
|
|
7124
7239
|
serviceError,
|
|
7125
7240
|
storageLimitReachedError,
|
|
7126
7241
|
storageQuotaExceededError,
|
|
7242
|
+
storageRejectionError,
|
|
7127
7243
|
timeoutError,
|
|
7128
7244
|
tinyCloudDebugLogger,
|
|
7129
7245
|
utf8Decode,
|
|
@@ -7137,6 +7253,7 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
|
|
|
7137
7253
|
validateServiceResponseEvent,
|
|
7138
7254
|
validateServiceSession,
|
|
7139
7255
|
validateTelemetrySpanEvent,
|
|
7256
|
+
validatedCapabilityOf,
|
|
7140
7257
|
verifyDecryptResponse,
|
|
7141
7258
|
wrapError
|
|
7142
7259
|
});
|