@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/kv/index.d.cts
CHANGED
|
@@ -850,7 +850,6 @@ declare class KVService extends BaseService implements IKVService {
|
|
|
850
850
|
* Get the service configuration.
|
|
851
851
|
*/
|
|
852
852
|
get config(): KVServiceConfig;
|
|
853
|
-
private parseQuotaInfo;
|
|
854
853
|
private handleQuotaErrorResponse;
|
|
855
854
|
/**
|
|
856
855
|
* Classify a KV 404 by reading the response body once.
|
|
@@ -919,12 +918,16 @@ declare class KVService extends BaseService implements IKVService {
|
|
|
919
918
|
*/
|
|
920
919
|
private parseResponse;
|
|
921
920
|
private createSignedReadUrlError;
|
|
921
|
+
/** A known 401/403 keeps its typed status even if reading its body fails. */
|
|
922
|
+
private readAuthorizationText;
|
|
922
923
|
/**
|
|
923
924
|
* AUTH_UNAUTHORIZED for a 401/403 response. The message keeps the HTTP
|
|
924
925
|
* status and the server text (`<context>: <status> - <text>`, the same
|
|
925
926
|
* shape as every other KV failure) so callers that rethrow only the message
|
|
926
927
|
* still see the status; `meta.status` carries it for typed callers.
|
|
927
|
-
*
|
|
928
|
+
* Server hints must match the requested path/action (whether the node sends
|
|
929
|
+
* a relative path or full resource); metadata uses only the request-derived
|
|
930
|
+
* canonical resource under the authenticated session's space.
|
|
928
931
|
*/
|
|
929
932
|
private authorizationFailure;
|
|
930
933
|
private normalizeSignedReadUrlResponse;
|
package/dist/kv/index.d.ts
CHANGED
|
@@ -850,7 +850,6 @@ declare class KVService extends BaseService implements IKVService {
|
|
|
850
850
|
* Get the service configuration.
|
|
851
851
|
*/
|
|
852
852
|
get config(): KVServiceConfig;
|
|
853
|
-
private parseQuotaInfo;
|
|
854
853
|
private handleQuotaErrorResponse;
|
|
855
854
|
/**
|
|
856
855
|
* Classify a KV 404 by reading the response body once.
|
|
@@ -919,12 +918,16 @@ declare class KVService extends BaseService implements IKVService {
|
|
|
919
918
|
*/
|
|
920
919
|
private parseResponse;
|
|
921
920
|
private createSignedReadUrlError;
|
|
921
|
+
/** A known 401/403 keeps its typed status even if reading its body fails. */
|
|
922
|
+
private readAuthorizationText;
|
|
922
923
|
/**
|
|
923
924
|
* AUTH_UNAUTHORIZED for a 401/403 response. The message keeps the HTTP
|
|
924
925
|
* status and the server text (`<context>: <status> - <text>`, the same
|
|
925
926
|
* shape as every other KV failure) so callers that rethrow only the message
|
|
926
927
|
* still see the status; `meta.status` carries it for typed callers.
|
|
927
|
-
*
|
|
928
|
+
* Server hints must match the requested path/action (whether the node sends
|
|
929
|
+
* a relative path or full resource); metadata uses only the request-derived
|
|
930
|
+
* canonical resource under the authenticated session's space.
|
|
928
931
|
*/
|
|
929
932
|
private authorizationFailure;
|
|
930
933
|
private normalizeSignedReadUrlResponse;
|
package/dist/kv/index.js
CHANGED
|
@@ -70,6 +70,55 @@ function serviceError(code, message, service, options) {
|
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
// src/errors.ts
|
|
74
|
+
import { CAPABILITY_REGISTRY } from "@tinycloud/bootstrap";
|
|
75
|
+
|
|
76
|
+
// src/encryption/networkId.ts
|
|
77
|
+
var URN_PREFIX = "urn:tinycloud:encryption:";
|
|
78
|
+
var NETWORK_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
79
|
+
var NetworkIdError = class extends Error {
|
|
80
|
+
constructor(message) {
|
|
81
|
+
super(message);
|
|
82
|
+
this.name = "NetworkIdError";
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
function parseNetworkId(networkId) {
|
|
86
|
+
if (typeof networkId !== "string" || networkId.length === 0) {
|
|
87
|
+
throw new NetworkIdError("networkId must be a non-empty string");
|
|
88
|
+
}
|
|
89
|
+
if (!networkId.startsWith(URN_PREFIX)) {
|
|
90
|
+
throw new NetworkIdError(
|
|
91
|
+
`networkId must start with ${URN_PREFIX} (got ${JSON.stringify(networkId)})`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
const body = networkId.slice(URN_PREFIX.length);
|
|
95
|
+
const lastColon = body.lastIndexOf(":");
|
|
96
|
+
if (lastColon <= 0 || lastColon === body.length - 1) {
|
|
97
|
+
throw new NetworkIdError(
|
|
98
|
+
`networkId missing ownerDid or name segment (got ${JSON.stringify(networkId)})`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
const ownerDid = body.slice(0, lastColon);
|
|
102
|
+
const name = body.slice(lastColon + 1);
|
|
103
|
+
if (!ownerDid.startsWith("did:")) {
|
|
104
|
+
throw new NetworkIdError(
|
|
105
|
+
`networkId ownerDid must be a DID (got ${JSON.stringify(ownerDid)})`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
const didParts = ownerDid.split(":");
|
|
109
|
+
if (didParts.length < 3 || didParts.some((p) => p.length === 0)) {
|
|
110
|
+
throw new NetworkIdError(
|
|
111
|
+
`networkId ownerDid is not a well-formed DID (got ${JSON.stringify(ownerDid)})`
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
if (!NETWORK_NAME_RE.test(name)) {
|
|
115
|
+
throw new NetworkIdError(
|
|
116
|
+
`networkId name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
return { networkId, ownerDid, name };
|
|
120
|
+
}
|
|
121
|
+
|
|
73
122
|
// src/errors.ts
|
|
74
123
|
function parsePermissionHint(value) {
|
|
75
124
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
@@ -138,6 +187,101 @@ function parseAuthError(responseText) {
|
|
|
138
187
|
function authUnauthorizedError(service, message, meta) {
|
|
139
188
|
return serviceError(ErrorCodes.AUTH_UNAUTHORIZED, message, service, { meta });
|
|
140
189
|
}
|
|
190
|
+
var grantableActions = new Set(
|
|
191
|
+
CAPABILITY_REGISTRY.map(({ urn }) => urn).filter((urn) => !urn.endsWith("/*"))
|
|
192
|
+
);
|
|
193
|
+
var capabilityServices = new Set(
|
|
194
|
+
CAPABILITY_REGISTRY.map(({ service }) => service.slice("tinycloud.".length))
|
|
195
|
+
);
|
|
196
|
+
function parseCapabilityResource(resource, service) {
|
|
197
|
+
if (resource.length === 0 || resource.length > 1024 || !capabilityServices.has(service) || /[\s\p{C}*#]/u.test(resource) || resource.includes("://")) return void 0;
|
|
198
|
+
const segments = resource.split("/");
|
|
199
|
+
if (!/^tinycloud:[A-Za-z0-9][A-Za-z0-9:._-]*$/.test(segments[0])) return void 0;
|
|
200
|
+
const serviceIndex = segments.findIndex(
|
|
201
|
+
(segment, index) => index > 0 && capabilityServices.has(segment)
|
|
202
|
+
);
|
|
203
|
+
if (serviceIndex < 1 || segments[serviceIndex] !== service) return void 0;
|
|
204
|
+
if (segments.slice(1, serviceIndex).some(
|
|
205
|
+
(segment) => segment.length === 0 || segment === "." || segment === ".."
|
|
206
|
+
)) return void 0;
|
|
207
|
+
const pathSegments = segments.slice(serviceIndex + 1);
|
|
208
|
+
if (pathSegments.some((segment) => segment === "." || segment === "..")) return void 0;
|
|
209
|
+
if (service !== "kv" && (pathSegments.length === 0 || pathSegments[pathSegments.length - 1] === "")) return void 0;
|
|
210
|
+
return {
|
|
211
|
+
space: segments.slice(0, serviceIndex).join("/"),
|
|
212
|
+
path: pathSegments.join("/")
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function safeCapabilityResource(value, service, action) {
|
|
216
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 1024) return false;
|
|
217
|
+
if (service === "encryption" && value.startsWith("urn:tinycloud:encryption:")) {
|
|
218
|
+
if (!/^[A-Za-z0-9_][A-Za-z0-9_.:/-]*$/.test(value) || value.includes("//") || value.split("/").some((segment) => segment === "." || segment === "..")) return false;
|
|
219
|
+
try {
|
|
220
|
+
parseNetworkId(value);
|
|
221
|
+
return true;
|
|
222
|
+
} catch {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const parsed = parseCapabilityResource(value, service);
|
|
227
|
+
return parsed !== void 0 && (!parsed.path.endsWith("/") || service === "kv" && action === "tinycloud.kv/list") && (parsed.path !== "" || service === "kv" && action === "tinycloud.kv/list");
|
|
228
|
+
}
|
|
229
|
+
function validatedCapabilityOf(error) {
|
|
230
|
+
const verdict = authorizationVerdictOf(error);
|
|
231
|
+
if (verdict !== "unauthenticated" && verdict !== "forbidden") return void 0;
|
|
232
|
+
const seen = /* @__PURE__ */ new Set();
|
|
233
|
+
let node = error;
|
|
234
|
+
for (let depth = 0; depth < MAX_CAUSE_DEPTH && typeof node === "object" && node !== null && !seen.has(node); depth += 1) {
|
|
235
|
+
seen.add(node);
|
|
236
|
+
const record = node;
|
|
237
|
+
const status = errorStatusOf(record);
|
|
238
|
+
const meta = record.meta;
|
|
239
|
+
if ((status === 401 || status === 403 || status === void 0 && record.code === ErrorCodes.AUTH_UNAUTHORIZED) && typeof meta === "object" && meta !== null && !Array.isArray(meta)) {
|
|
240
|
+
const metadata = meta;
|
|
241
|
+
const { resource, requiredAction } = metadata;
|
|
242
|
+
if (typeof requiredAction === "string" && grantableActions.has(requiredAction)) {
|
|
243
|
+
const actionService = requiredAction.slice("tinycloud.".length, requiredAction.indexOf("/"));
|
|
244
|
+
const explicitService = record.service === void 0 ? metadata.service : record.service;
|
|
245
|
+
if (safeCapabilityResource(resource, actionService, requiredAction) && (explicitService === void 0 || explicitService === actionService || explicitService === `tinycloud.${actionService}`)) {
|
|
246
|
+
return { resource, requiredAction };
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
node = record.cause;
|
|
251
|
+
}
|
|
252
|
+
return void 0;
|
|
253
|
+
}
|
|
254
|
+
var MAX_CAUSE_DEPTH = 8;
|
|
255
|
+
function errorStatusOf(node) {
|
|
256
|
+
const meta = node.meta;
|
|
257
|
+
const candidates = [
|
|
258
|
+
node.status,
|
|
259
|
+
node.statusCode,
|
|
260
|
+
typeof meta === "object" && meta !== null ? meta.status : void 0
|
|
261
|
+
];
|
|
262
|
+
for (const candidate of candidates) {
|
|
263
|
+
if (typeof candidate === "number" && Number.isInteger(candidate) && candidate >= 400 && candidate <= 599) {
|
|
264
|
+
return candidate;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return void 0;
|
|
268
|
+
}
|
|
269
|
+
function authorizationVerdictOf(error) {
|
|
270
|
+
const seen = /* @__PURE__ */ new Set();
|
|
271
|
+
let sawUnauthorizedCode = false;
|
|
272
|
+
let node = error;
|
|
273
|
+
for (let depth = 0; depth < MAX_CAUSE_DEPTH && typeof node === "object" && node !== null && !seen.has(node); depth += 1) {
|
|
274
|
+
seen.add(node);
|
|
275
|
+
const record = node;
|
|
276
|
+
const status = errorStatusOf(record);
|
|
277
|
+
if (status === 401) return "unauthenticated";
|
|
278
|
+
if (status === 403) return "forbidden";
|
|
279
|
+
if (status !== void 0) return "other";
|
|
280
|
+
if (record.code === ErrorCodes.AUTH_UNAUTHORIZED) sawUnauthorizedCode = true;
|
|
281
|
+
node = record.cause;
|
|
282
|
+
}
|
|
283
|
+
return sawUnauthorizedCode ? "forbidden" : void 0;
|
|
284
|
+
}
|
|
141
285
|
function storageQuotaExceededError(service, message, meta) {
|
|
142
286
|
return {
|
|
143
287
|
code: ErrorCodes.STORAGE_QUOTA_EXCEEDED,
|
|
@@ -154,6 +298,20 @@ function storageLimitReachedError(service, message, meta) {
|
|
|
154
298
|
meta
|
|
155
299
|
};
|
|
156
300
|
}
|
|
301
|
+
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.";
|
|
302
|
+
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.";
|
|
303
|
+
function parseStorageQuotaBytes(responseText) {
|
|
304
|
+
const match = responseText.match(/Used:\s*(\d+)\s*bytes,\s*Limit:\s*(\d+)\s*bytes/i);
|
|
305
|
+
if (!match) return void 0;
|
|
306
|
+
return {
|
|
307
|
+
usedBytes: parseInt(match[1], 10),
|
|
308
|
+
limitBytes: parseInt(match[2], 10)
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
function storageRejectionError(service, status, meta, responseText) {
|
|
312
|
+
const quotaMeta = { ...meta, ...parseStorageQuotaBytes(responseText) };
|
|
313
|
+
return status === 402 ? storageQuotaExceededError(service, STORAGE_FULL_MESSAGE, quotaMeta) : storageLimitReachedError(service, STORAGE_WRITE_TOO_LARGE_MESSAGE, quotaMeta);
|
|
314
|
+
}
|
|
157
315
|
function wrapError(service, error, defaultCode = ErrorCodes.NETWORK_ERROR) {
|
|
158
316
|
if (error instanceof Error) {
|
|
159
317
|
if (error.name === "AbortError") {
|
|
@@ -544,47 +702,21 @@ var KVService = class extends BaseService {
|
|
|
544
702
|
get config() {
|
|
545
703
|
return this._config;
|
|
546
704
|
}
|
|
547
|
-
// Parses "Used: X bytes, Limit: Y bytes" from tinycloud-node error responses
|
|
548
|
-
parseQuotaInfo(errorText) {
|
|
549
|
-
const match = errorText.match(
|
|
550
|
-
/Used:\s*(\d+)\s*bytes,\s*Limit:\s*(\d+)\s*bytes/i
|
|
551
|
-
);
|
|
552
|
-
if (match) {
|
|
553
|
-
return {
|
|
554
|
-
usedBytes: parseInt(match[1], 10),
|
|
555
|
-
limitBytes: parseInt(match[2], 10)
|
|
556
|
-
};
|
|
557
|
-
}
|
|
558
|
-
return void 0;
|
|
559
|
-
}
|
|
560
705
|
handleQuotaErrorResponse(response, errorText, key) {
|
|
561
|
-
if (response.status
|
|
562
|
-
|
|
563
|
-
return err(
|
|
564
|
-
storageQuotaExceededError(
|
|
565
|
-
"kv",
|
|
566
|
-
`Storage quota exceeded for key ${JSON.stringify(key)}: ${errorText}`,
|
|
567
|
-
{
|
|
568
|
-
status: response.status,
|
|
569
|
-
...quotaInfo ? { usedBytes: quotaInfo.usedBytes, limitBytes: quotaInfo.limitBytes } : {}
|
|
570
|
-
}
|
|
571
|
-
)
|
|
572
|
-
);
|
|
706
|
+
if (response.status !== 402 && response.status !== 413) {
|
|
707
|
+
return void 0;
|
|
573
708
|
}
|
|
574
|
-
if (response.status === 413) {
|
|
575
|
-
|
|
576
|
-
return err(
|
|
577
|
-
storageLimitReachedError(
|
|
578
|
-
"kv",
|
|
579
|
-
`Storage limit reached for key ${JSON.stringify(key)}: ${errorText}`,
|
|
580
|
-
{
|
|
581
|
-
status: response.status,
|
|
582
|
-
...quotaInfo ? { usedBytes: quotaInfo.usedBytes, limitBytes: quotaInfo.limitBytes } : {}
|
|
583
|
-
}
|
|
584
|
-
)
|
|
585
|
-
);
|
|
709
|
+
if (response.status === 413 && !parseStorageQuotaBytes(errorText)) {
|
|
710
|
+
return void 0;
|
|
586
711
|
}
|
|
587
|
-
return
|
|
712
|
+
return err(
|
|
713
|
+
storageRejectionError(
|
|
714
|
+
"kv",
|
|
715
|
+
response.status,
|
|
716
|
+
{ status: response.status, statusText: response.statusText, key },
|
|
717
|
+
errorText
|
|
718
|
+
)
|
|
719
|
+
);
|
|
588
720
|
}
|
|
589
721
|
/**
|
|
590
722
|
* Classify a KV 404 by reading the response body once.
|
|
@@ -860,12 +992,14 @@ var KVService = class extends BaseService {
|
|
|
860
992
|
signal: this.combineSignals(options?.signal)
|
|
861
993
|
});
|
|
862
994
|
if (!response.ok) {
|
|
863
|
-
const errorText = await response.text();
|
|
995
|
+
const errorText = response.status === 401 || response.status === 403 ? await this.readAuthorizationText(response) : await response.text();
|
|
864
996
|
if (response.status === 401 || response.status === 403) {
|
|
865
997
|
return this.authorizationFailure(
|
|
866
998
|
`Failed to batch read ${keys.length} key(s)`,
|
|
867
999
|
response,
|
|
868
|
-
errorText
|
|
1000
|
+
errorText,
|
|
1001
|
+
paths,
|
|
1002
|
+
action
|
|
869
1003
|
);
|
|
870
1004
|
}
|
|
871
1005
|
if (response.status === 413) {
|
|
@@ -955,7 +1089,7 @@ var KVService = class extends BaseService {
|
|
|
955
1089
|
return text;
|
|
956
1090
|
}
|
|
957
1091
|
}
|
|
958
|
-
async createSignedReadUrlError(response, key) {
|
|
1092
|
+
async createSignedReadUrlError(response, key, path) {
|
|
959
1093
|
let errorText = response.statusText;
|
|
960
1094
|
try {
|
|
961
1095
|
const text = await response.text();
|
|
@@ -968,7 +1102,9 @@ var KVService = class extends BaseService {
|
|
|
968
1102
|
return this.authorizationFailure(
|
|
969
1103
|
`Failed to create signed read URL for key ${JSON.stringify(key)}`,
|
|
970
1104
|
response,
|
|
971
|
-
errorText
|
|
1105
|
+
errorText,
|
|
1106
|
+
[path],
|
|
1107
|
+
KVAction.GET
|
|
972
1108
|
);
|
|
973
1109
|
}
|
|
974
1110
|
const code = response.status === 400 ? ErrorCodes.INVALID_INPUT : ErrorCodes.NETWORK_ERROR;
|
|
@@ -981,21 +1117,42 @@ var KVService = class extends BaseService {
|
|
|
981
1117
|
)
|
|
982
1118
|
);
|
|
983
1119
|
}
|
|
1120
|
+
/** A known 401/403 keeps its typed status even if reading its body fails. */
|
|
1121
|
+
async readAuthorizationText(response) {
|
|
1122
|
+
try {
|
|
1123
|
+
return await response.text();
|
|
1124
|
+
} catch {
|
|
1125
|
+
return "";
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
984
1128
|
/**
|
|
985
1129
|
* AUTH_UNAUTHORIZED for a 401/403 response. The message keeps the HTTP
|
|
986
1130
|
* status and the server text (`<context>: <status> - <text>`, the same
|
|
987
1131
|
* shape as every other KV failure) so callers that rethrow only the message
|
|
988
1132
|
* still see the status; `meta.status` carries it for typed callers.
|
|
989
|
-
*
|
|
1133
|
+
* Server hints must match the requested path/action (whether the node sends
|
|
1134
|
+
* a relative path or full resource); metadata uses only the request-derived
|
|
1135
|
+
* canonical resource under the authenticated session's space.
|
|
990
1136
|
*/
|
|
991
|
-
authorizationFailure(context, response, errorText, extraMeta = {}) {
|
|
1137
|
+
authorizationFailure(context, response, errorText, paths, expectedAction, extraMeta = {}) {
|
|
992
1138
|
const { resource, action } = parseAuthError(errorText);
|
|
1139
|
+
const requestedPath = paths.find(
|
|
1140
|
+
(path) => resource === path || resource === `${this.context.session.spaceId}/kv/${path}`
|
|
1141
|
+
);
|
|
1142
|
+
const capability = requestedPath !== void 0 && action === expectedAction ? validatedCapabilityOf({
|
|
1143
|
+
service: "kv",
|
|
1144
|
+
code: ErrorCodes.AUTH_UNAUTHORIZED,
|
|
1145
|
+
meta: {
|
|
1146
|
+
status: response.status,
|
|
1147
|
+
resource: `${this.context.session.spaceId}/kv/${requestedPath}`,
|
|
1148
|
+
requiredAction: action
|
|
1149
|
+
}
|
|
1150
|
+
}) : void 0;
|
|
993
1151
|
const detail = errorText.trim().length > 0 ? errorText : response.statusText || "authorization failed";
|
|
994
1152
|
return err(authUnauthorizedError("kv", `${context}: ${response.status} - ${detail}`, {
|
|
995
1153
|
status: response.status,
|
|
996
|
-
...
|
|
997
|
-
...
|
|
998
|
-
...extraMeta
|
|
1154
|
+
...extraMeta,
|
|
1155
|
+
...capability
|
|
999
1156
|
}));
|
|
1000
1157
|
}
|
|
1001
1158
|
normalizeSignedReadUrlResponse(data) {
|
|
@@ -1039,13 +1196,24 @@ var KVService = class extends BaseService {
|
|
|
1039
1196
|
);
|
|
1040
1197
|
if (!response.ok) {
|
|
1041
1198
|
if (response.status === 401 || response.status === 403) {
|
|
1042
|
-
const errorText2 = await
|
|
1199
|
+
const errorText2 = await this.readAuthorizationText(response);
|
|
1043
1200
|
const permissionHint = parsePermissionHintFromErrorText(errorText2);
|
|
1201
|
+
const structuredCapability = permissionHint?.service === "tinycloud.kv" && permissionHint.space === this.context.session.spaceId && permissionHint.path === path ? validatedCapabilityOf({
|
|
1202
|
+
service: "kv",
|
|
1203
|
+
code: ErrorCodes.AUTH_UNAUTHORIZED,
|
|
1204
|
+
meta: {
|
|
1205
|
+
status: response.status,
|
|
1206
|
+
resource: `${this.context.session.spaceId}/kv/${path}`,
|
|
1207
|
+
requiredAction: KVAction.GET
|
|
1208
|
+
}
|
|
1209
|
+
}) : void 0;
|
|
1044
1210
|
return this.authorizationFailure(
|
|
1045
1211
|
`Failed to get key ${JSON.stringify(key)}`,
|
|
1046
1212
|
response,
|
|
1047
1213
|
errorText2,
|
|
1048
|
-
|
|
1214
|
+
[path],
|
|
1215
|
+
KVAction.GET,
|
|
1216
|
+
structuredCapability ? { permissionHint, ...structuredCapability } : {}
|
|
1049
1217
|
);
|
|
1050
1218
|
}
|
|
1051
1219
|
if (response.status === 404) {
|
|
@@ -1122,7 +1290,9 @@ var KVService = class extends BaseService {
|
|
|
1122
1290
|
return this.authorizationFailure(
|
|
1123
1291
|
`Failed to put key ${JSON.stringify(key)}`,
|
|
1124
1292
|
response,
|
|
1125
|
-
await
|
|
1293
|
+
await this.readAuthorizationText(response),
|
|
1294
|
+
[path],
|
|
1295
|
+
KVAction.PUT
|
|
1126
1296
|
);
|
|
1127
1297
|
}
|
|
1128
1298
|
if (!response.ok) {
|
|
@@ -1251,7 +1421,9 @@ var KVService = class extends BaseService {
|
|
|
1251
1421
|
return this.authorizationFailure(
|
|
1252
1422
|
`Failed to batch put ${items.length} key(s)`,
|
|
1253
1423
|
response,
|
|
1254
|
-
errorText
|
|
1424
|
+
errorText,
|
|
1425
|
+
paths,
|
|
1426
|
+
KVAction.PUT
|
|
1255
1427
|
);
|
|
1256
1428
|
}
|
|
1257
1429
|
const quotaError = this.handleQuotaErrorResponse(
|
|
@@ -1372,11 +1544,13 @@ var KVService = class extends BaseService {
|
|
|
1372
1544
|
}
|
|
1373
1545
|
);
|
|
1374
1546
|
if (!response.ok) {
|
|
1375
|
-
if (response.status === 401) {
|
|
1547
|
+
if (response.status === 401 || response.status === 403) {
|
|
1376
1548
|
return this.authorizationFailure(
|
|
1377
1549
|
"Failed to list keys",
|
|
1378
1550
|
response,
|
|
1379
|
-
await
|
|
1551
|
+
await this.readAuthorizationText(response),
|
|
1552
|
+
[listPath],
|
|
1553
|
+
KVAction.LIST
|
|
1380
1554
|
);
|
|
1381
1555
|
}
|
|
1382
1556
|
const errorText = await response.text();
|
|
@@ -1425,11 +1599,13 @@ var KVService = class extends BaseService {
|
|
|
1425
1599
|
options?.ifMatch === void 0 ? void 0 : { "if-match": options.ifMatch }
|
|
1426
1600
|
);
|
|
1427
1601
|
if (!response.ok) {
|
|
1428
|
-
if (response.status === 401) {
|
|
1602
|
+
if (response.status === 401 || response.status === 403) {
|
|
1429
1603
|
return this.authorizationFailure(
|
|
1430
1604
|
`Failed to delete key ${JSON.stringify(key)}`,
|
|
1431
1605
|
response,
|
|
1432
|
-
await
|
|
1606
|
+
await this.readAuthorizationText(response),
|
|
1607
|
+
[path],
|
|
1608
|
+
KVAction.DELETE
|
|
1433
1609
|
);
|
|
1434
1610
|
}
|
|
1435
1611
|
if (response.status === 404) {
|
|
@@ -1487,11 +1663,13 @@ var KVService = class extends BaseService {
|
|
|
1487
1663
|
options?.signal
|
|
1488
1664
|
);
|
|
1489
1665
|
if (!response.ok) {
|
|
1490
|
-
if (response.status === 401) {
|
|
1666
|
+
if (response.status === 401 || response.status === 403) {
|
|
1491
1667
|
return this.authorizationFailure(
|
|
1492
1668
|
`Failed to get metadata for key ${JSON.stringify(key)}`,
|
|
1493
1669
|
response,
|
|
1494
|
-
await
|
|
1670
|
+
await this.readAuthorizationText(response),
|
|
1671
|
+
[path],
|
|
1672
|
+
KVAction.HEAD
|
|
1495
1673
|
);
|
|
1496
1674
|
}
|
|
1497
1675
|
if (response.status === 404) {
|
|
@@ -1558,7 +1736,7 @@ var KVService = class extends BaseService {
|
|
|
1558
1736
|
signal: this.combineSignals(options?.signal)
|
|
1559
1737
|
});
|
|
1560
1738
|
if (!response.ok) {
|
|
1561
|
-
return this.createSignedReadUrlError(response, key);
|
|
1739
|
+
return this.createSignedReadUrlError(response, key, path);
|
|
1562
1740
|
}
|
|
1563
1741
|
const signedUrl = this.normalizeSignedReadUrlResponse(
|
|
1564
1742
|
await response.json()
|