@tinycloud/sdk-services 3.2.6-beta.4 → 3.3.0-beta.11
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/{BaseService-1Ug6y7DC.d.cts → BaseService-Cc6Ueicm.d.cts} +1 -1
- package/dist/{BaseService-Bn07Vudy.d.ts → BaseService-DOdXd7sr.d.ts} +1 -1
- package/dist/encryption/index.cjs +8 -0
- package/dist/encryption/index.cjs.map +1 -1
- package/dist/encryption/index.d.cts +2 -2
- package/dist/encryption/index.d.ts +2 -2
- package/dist/encryption/index.js +8 -0
- package/dist/encryption/index.js.map +1 -1
- package/dist/index.cjs +345 -203
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +107 -7
- package/dist/index.d.ts +107 -7
- package/dist/index.js +344 -203
- package/dist/index.js.map +1 -1
- package/dist/internal/decrypt-transport-response-error.d.cts +1 -1
- package/dist/internal/decrypt-transport-response-error.d.ts +1 -1
- package/dist/kv/index.cjs +66 -21
- package/dist/kv/index.cjs.map +1 -1
- package/dist/kv/index.d.cts +4 -4
- package/dist/kv/index.d.ts +4 -4
- package/dist/kv/index.js +66 -21
- package/dist/kv/index.js.map +1 -1
- package/dist/kv/replication/grantPolicy.cjs +82 -0
- package/dist/kv/replication/grantPolicy.cjs.map +1 -0
- package/dist/kv/replication/grantPolicy.d.cts +19 -0
- package/dist/kv/replication/grantPolicy.d.ts +19 -0
- package/dist/kv/replication/grantPolicy.js +50 -0
- package/dist/kv/replication/grantPolicy.js.map +1 -0
- package/dist/kv/replication/index.cjs +571 -128
- package/dist/kv/replication/index.cjs.map +1 -1
- package/dist/kv/replication/index.d.cts +3 -3
- package/dist/kv/replication/index.d.ts +3 -3
- package/dist/kv/replication/index.js +571 -128
- package/dist/kv/replication/index.js.map +1 -1
- package/dist/{scope-BnyVPZ5E.d.ts → scope-B-m6cKH-.d.ts} +310 -7
- package/dist/{scope-CQ5kmHlt.d.cts → scope-B7oeKdLM.d.cts} +310 -7
- package/dist/sql/index.cjs +70 -36
- package/dist/sql/index.cjs.map +1 -1
- package/dist/sql/index.d.cts +2 -2
- package/dist/sql/index.d.ts +2 -2
- package/dist/sql/index.js +70 -36
- package/dist/sql/index.js.map +1 -1
- package/dist/{types-gmdUiorz.d.cts → types-BjH0ZtGy.d.cts} +6 -0
- package/dist/{types-gmdUiorz.d.ts → types-BjH0ZtGy.d.ts} +6 -0
- package/package.json +16 -2
package/dist/index.js
CHANGED
|
@@ -14,6 +14,14 @@ var ErrorCodes = {
|
|
|
14
14
|
ABORTED: "ABORTED",
|
|
15
15
|
INVALID_INPUT: "INVALID_INPUT",
|
|
16
16
|
PERMISSION_DENIED: "PERMISSION_DENIED",
|
|
17
|
+
// Node retry signals (tinycloud-node #347). `meta.retryable` says whether a
|
|
18
|
+
// retry is safe; `meta.retryAfterMs` carries the node's Retry-After.
|
|
19
|
+
/** HTTP 503 `sql_busy:<stage>` / `unavailable_retryable`: nothing was applied; retry after `meta.retryAfterMs`. */
|
|
20
|
+
NODE_BUSY: "NODE_BUSY",
|
|
21
|
+
/** HTTP 503 `request_timeout`: the node's deadline passed before any write began; nothing was applied. */
|
|
22
|
+
REQUEST_TIMEOUT: "REQUEST_TIMEOUT",
|
|
23
|
+
/** HTTP 500 `outcome_unknown`: a write may have been applied; read back before retrying a non-idempotent operation. */
|
|
24
|
+
OUTCOME_UNKNOWN: "OUTCOME_UNKNOWN",
|
|
17
25
|
// KV-specific errors
|
|
18
26
|
KV_NOT_FOUND: "KV_NOT_FOUND",
|
|
19
27
|
KV_WRITE_FAILED: "KV_WRITE_FAILED",
|
|
@@ -958,6 +966,42 @@ var ServiceContext = class {
|
|
|
958
966
|
}
|
|
959
967
|
};
|
|
960
968
|
|
|
969
|
+
// src/responseErrors.ts
|
|
970
|
+
function parseServiceErrorBody(errorText) {
|
|
971
|
+
try {
|
|
972
|
+
return JSON.parse(errorText);
|
|
973
|
+
} catch {
|
|
974
|
+
return {};
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
function formatServiceResponseError(serviceLabel, operation, status, errorText, parsed) {
|
|
978
|
+
if (parsed.message) {
|
|
979
|
+
return compactMessage(parsed.message);
|
|
980
|
+
}
|
|
981
|
+
if (looksLikeHtml(errorText)) {
|
|
982
|
+
if (status === 524 || /524\s*[:-]/i.test(errorText) || /a timeout occurred/i.test(errorText)) {
|
|
983
|
+
return `${serviceLabel} ${operation} failed: upstream request timed out. Please retry.`;
|
|
984
|
+
}
|
|
985
|
+
return `${serviceLabel} ${operation} failed: upstream service returned an HTML error page (${status}).`;
|
|
986
|
+
}
|
|
987
|
+
return `${serviceLabel} ${operation} failed: ${status} - ${compactMessage(errorText)}`;
|
|
988
|
+
}
|
|
989
|
+
function responseErrorMeta(status, statusText, errorText) {
|
|
990
|
+
const meta = { status, statusText };
|
|
991
|
+
const snippet = compactMessage(errorText);
|
|
992
|
+
if (snippet) {
|
|
993
|
+
meta.responseSnippet = snippet.slice(0, 300);
|
|
994
|
+
}
|
|
995
|
+
return meta;
|
|
996
|
+
}
|
|
997
|
+
function looksLikeHtml(text) {
|
|
998
|
+
return /<!doctype html/i.test(text) || /<html[\s>]/i.test(text);
|
|
999
|
+
}
|
|
1000
|
+
function compactMessage(message) {
|
|
1001
|
+
const compact = message.replace(/\s+/g, " ").trim();
|
|
1002
|
+
return compact.length > 500 ? `${compact.slice(0, 500)}...` : compact;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
961
1005
|
// src/errors.ts
|
|
962
1006
|
import { CAPABILITY_REGISTRY } from "@tinycloud/bootstrap";
|
|
963
1007
|
|
|
@@ -1117,6 +1161,30 @@ function networkError(service, message, cause) {
|
|
|
1117
1161
|
cause
|
|
1118
1162
|
};
|
|
1119
1163
|
}
|
|
1164
|
+
function nodeRetryError(service, message, response, errorText) {
|
|
1165
|
+
const body = errorText.trim();
|
|
1166
|
+
const [, token, stage] = /^([a-z_]+)(?::\s*([a-z_]+):)?/.exec(body) ?? [];
|
|
1167
|
+
let code;
|
|
1168
|
+
if (response.status === 503 && (token === "sql_busy" || token === "unavailable_retryable")) {
|
|
1169
|
+
code = ErrorCodes.NODE_BUSY;
|
|
1170
|
+
} else if (response.status === 503 && token === "request_timeout") {
|
|
1171
|
+
code = ErrorCodes.REQUEST_TIMEOUT;
|
|
1172
|
+
} else if (response.status === 500 && token === "outcome_unknown") {
|
|
1173
|
+
code = ErrorCodes.OUTCOME_UNKNOWN;
|
|
1174
|
+
} else {
|
|
1175
|
+
return void 0;
|
|
1176
|
+
}
|
|
1177
|
+
const retryable = code !== ErrorCodes.OUTCOME_UNKNOWN;
|
|
1178
|
+
const retryAfter = response.headers.get("retry-after")?.trim();
|
|
1179
|
+
return serviceError(code, message, service, {
|
|
1180
|
+
meta: {
|
|
1181
|
+
...responseErrorMeta(response.status, response.statusText, errorText),
|
|
1182
|
+
retryable,
|
|
1183
|
+
...token === "sql_busy" && stage ? { stage } : {},
|
|
1184
|
+
...retryable && retryAfter && /^\d{1,9}$/.test(retryAfter) ? { retryAfterMs: Number(retryAfter) * 1e3 } : {}
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1120
1188
|
var RequestTimeoutError = class extends Error {
|
|
1121
1189
|
constructor(timeoutMs) {
|
|
1122
1190
|
super(`Request timed out after ${timeoutMs}ms.`);
|
|
@@ -2455,9 +2523,12 @@ var KVService = class extends BaseService {
|
|
|
2455
2523
|
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2456
2524
|
));
|
|
2457
2525
|
}
|
|
2526
|
+
const message = `Failed to get key ${JSON.stringify(key)}: ${response.status} - ${errorText}`;
|
|
2527
|
+
const retryError = nodeRetryError("kv", message, response, errorText);
|
|
2528
|
+
if (retryError) return err(retryError);
|
|
2458
2529
|
return err(serviceError(
|
|
2459
2530
|
ErrorCodes.NETWORK_ERROR,
|
|
2460
|
-
|
|
2531
|
+
message,
|
|
2461
2532
|
"kv",
|
|
2462
2533
|
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2463
2534
|
));
|
|
@@ -2539,19 +2610,14 @@ var KVService = class extends BaseService {
|
|
|
2539
2610
|
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2540
2611
|
));
|
|
2541
2612
|
}
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
`Concurrent KV update conflicted for key ${JSON.stringify(key)}`,
|
|
2546
|
-
"kv",
|
|
2547
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2548
|
-
));
|
|
2549
|
-
}
|
|
2613
|
+
const message = `Failed to put key ${JSON.stringify(key)}: ${response.status} - ${errorText}`;
|
|
2614
|
+
const retryError = nodeRetryError("kv", message, response, errorText);
|
|
2615
|
+
if (retryError) return err(retryError);
|
|
2550
2616
|
const quotaError = this.handleQuotaErrorResponse(response, errorText, key);
|
|
2551
2617
|
if (quotaError) return quotaError;
|
|
2552
2618
|
return err(serviceError(
|
|
2553
2619
|
ErrorCodes.KV_WRITE_FAILED,
|
|
2554
|
-
|
|
2620
|
+
message,
|
|
2555
2621
|
"kv",
|
|
2556
2622
|
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2557
2623
|
));
|
|
@@ -2851,9 +2917,12 @@ var KVService = class extends BaseService {
|
|
|
2851
2917
|
);
|
|
2852
2918
|
}
|
|
2853
2919
|
const errorText = await response.text();
|
|
2920
|
+
const message = `Failed to list keys: ${response.status} - ${errorText}`;
|
|
2921
|
+
const retryError = nodeRetryError("kv", message, response, errorText);
|
|
2922
|
+
if (retryError) return err(retryError);
|
|
2854
2923
|
return err(serviceError(
|
|
2855
2924
|
ErrorCodes.NETWORK_ERROR,
|
|
2856
|
-
|
|
2925
|
+
message,
|
|
2857
2926
|
"kv",
|
|
2858
2927
|
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2859
2928
|
));
|
|
@@ -3104,17 +3173,12 @@ var KVService = class extends BaseService {
|
|
|
3104
3173
|
{ meta: { status: response.status, statusText: response.statusText } }
|
|
3105
3174
|
));
|
|
3106
3175
|
}
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
`Concurrent KV delete conflicted for key ${JSON.stringify(key)}`,
|
|
3111
|
-
"kv",
|
|
3112
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
3113
|
-
));
|
|
3114
|
-
}
|
|
3176
|
+
const message = `Failed to delete key ${JSON.stringify(key)}: ${response.status} - ${errorText}`;
|
|
3177
|
+
const retryError = nodeRetryError("kv", message, response, errorText);
|
|
3178
|
+
if (retryError) return err(retryError);
|
|
3115
3179
|
return err(serviceError(
|
|
3116
3180
|
ErrorCodes.NETWORK_ERROR,
|
|
3117
|
-
|
|
3181
|
+
message,
|
|
3118
3182
|
"kv",
|
|
3119
3183
|
{ meta: { status: response.status, statusText: response.statusText } }
|
|
3120
3184
|
));
|
|
@@ -3155,10 +3219,13 @@ var KVService = class extends BaseService {
|
|
|
3155
3219
|
return await this.classifyNotFound(response, key);
|
|
3156
3220
|
}
|
|
3157
3221
|
const errorText = await response.text();
|
|
3222
|
+
const message = `Failed to get metadata for key ${JSON.stringify(key)}: ${response.status} - ${errorText}`;
|
|
3223
|
+
const retryError = nodeRetryError("kv", message, response, errorText);
|
|
3224
|
+
if (retryError) return err(retryError);
|
|
3158
3225
|
return err(
|
|
3159
3226
|
serviceError(
|
|
3160
3227
|
ErrorCodes.NETWORK_ERROR,
|
|
3161
|
-
|
|
3228
|
+
message,
|
|
3162
3229
|
"kv",
|
|
3163
3230
|
{ meta: { status: response.status, statusText: response.statusText } }
|
|
3164
3231
|
)
|
|
@@ -3350,42 +3417,6 @@ function requiresSecretsOptIn(space, prefix) {
|
|
|
3350
3417
|
return prefix.split("/", 1)[0] === "vault";
|
|
3351
3418
|
}
|
|
3352
3419
|
|
|
3353
|
-
// src/responseErrors.ts
|
|
3354
|
-
function parseServiceErrorBody(errorText) {
|
|
3355
|
-
try {
|
|
3356
|
-
return JSON.parse(errorText);
|
|
3357
|
-
} catch {
|
|
3358
|
-
return {};
|
|
3359
|
-
}
|
|
3360
|
-
}
|
|
3361
|
-
function formatServiceResponseError(serviceLabel, operation, status, errorText, parsed) {
|
|
3362
|
-
if (parsed.message) {
|
|
3363
|
-
return compactMessage(parsed.message);
|
|
3364
|
-
}
|
|
3365
|
-
if (looksLikeHtml(errorText)) {
|
|
3366
|
-
if (status === 524 || /524\s*[:-]/i.test(errorText) || /a timeout occurred/i.test(errorText)) {
|
|
3367
|
-
return `${serviceLabel} ${operation} failed: upstream request timed out. Please retry.`;
|
|
3368
|
-
}
|
|
3369
|
-
return `${serviceLabel} ${operation} failed: upstream service returned an HTML error page (${status}).`;
|
|
3370
|
-
}
|
|
3371
|
-
return `${serviceLabel} ${operation} failed: ${status} - ${compactMessage(errorText)}`;
|
|
3372
|
-
}
|
|
3373
|
-
function responseErrorMeta(status, statusText, errorText) {
|
|
3374
|
-
const meta = { status, statusText };
|
|
3375
|
-
const snippet = compactMessage(errorText);
|
|
3376
|
-
if (snippet) {
|
|
3377
|
-
meta.responseSnippet = snippet.slice(0, 300);
|
|
3378
|
-
}
|
|
3379
|
-
return meta;
|
|
3380
|
-
}
|
|
3381
|
-
function looksLikeHtml(text) {
|
|
3382
|
-
return /<!doctype html/i.test(text) || /<html[\s>]/i.test(text);
|
|
3383
|
-
}
|
|
3384
|
-
function compactMessage(message) {
|
|
3385
|
-
const compact = message.replace(/\s+/g, " ").trim();
|
|
3386
|
-
return compact.length > 500 ? `${compact.slice(0, 500)}...` : compact;
|
|
3387
|
-
}
|
|
3388
|
-
|
|
3389
3420
|
// src/sql/SQLMigrations.ts
|
|
3390
3421
|
var SQLMigrations = class {
|
|
3391
3422
|
constructor(service, dbName) {
|
|
@@ -3801,6 +3832,8 @@ var SQLService = class extends BaseService {
|
|
|
3801
3832
|
if (response.status === 413 && parseStorageRejection(errorText)) {
|
|
3802
3833
|
return err(storageRejectionError("sql", 413, meta, errorText));
|
|
3803
3834
|
}
|
|
3835
|
+
const retryError = nodeRetryError("sql", `SQL ${operation} failed: ${response.status} - ${errorText}`, response, errorText);
|
|
3836
|
+
if (retryError) return err(retryError);
|
|
3804
3837
|
const errorBody = parseServiceErrorBody(errorText);
|
|
3805
3838
|
const errorCode = this.mapHttpStatusToErrorCode(
|
|
3806
3839
|
response.status,
|
|
@@ -4270,6 +4303,8 @@ var DuckDbService = class extends BaseService {
|
|
|
4270
4303
|
if (response.status === 413 && parseStorageRejection(errorText)) {
|
|
4271
4304
|
return err(storageRejectionError("duckdb", 413, meta, errorText));
|
|
4272
4305
|
}
|
|
4306
|
+
const retryError = nodeRetryError("duckdb", `DuckDB ${operation} failed: ${response.status} - ${errorText}`, response, errorText);
|
|
4307
|
+
if (retryError) return err(retryError);
|
|
4273
4308
|
const errorBody = parseServiceErrorBody(errorText);
|
|
4274
4309
|
const errorCode = this.mapHttpStatusToErrorCode(
|
|
4275
4310
|
response.status,
|
|
@@ -5477,6 +5512,116 @@ var TinyCloudQuota = class {
|
|
|
5477
5512
|
}
|
|
5478
5513
|
};
|
|
5479
5514
|
|
|
5515
|
+
// src/encryption/canonical.ts
|
|
5516
|
+
function canonicalize(value) {
|
|
5517
|
+
if (value === void 0) {
|
|
5518
|
+
return "";
|
|
5519
|
+
}
|
|
5520
|
+
return stringify(value);
|
|
5521
|
+
}
|
|
5522
|
+
function stringify(value) {
|
|
5523
|
+
if (value === null) return "null";
|
|
5524
|
+
switch (typeof value) {
|
|
5525
|
+
case "boolean":
|
|
5526
|
+
case "number":
|
|
5527
|
+
return JSON.stringify(value);
|
|
5528
|
+
case "string":
|
|
5529
|
+
return JSON.stringify(value);
|
|
5530
|
+
case "object": {
|
|
5531
|
+
if (Array.isArray(value)) {
|
|
5532
|
+
return `[${value.map(stringify).join(",")}]`;
|
|
5533
|
+
}
|
|
5534
|
+
const keys = Object.keys(value).sort();
|
|
5535
|
+
const parts = [];
|
|
5536
|
+
for (const k of keys) {
|
|
5537
|
+
const v = value[k];
|
|
5538
|
+
if (v === void 0) continue;
|
|
5539
|
+
parts.push(`${JSON.stringify(k)}:${stringify(v)}`);
|
|
5540
|
+
}
|
|
5541
|
+
return `{${parts.join(",")}}`;
|
|
5542
|
+
}
|
|
5543
|
+
default:
|
|
5544
|
+
throw new TypeError(
|
|
5545
|
+
`canonicalize: unsupported value type ${typeof value}`
|
|
5546
|
+
);
|
|
5547
|
+
}
|
|
5548
|
+
}
|
|
5549
|
+
var HEX = "0123456789abcdef";
|
|
5550
|
+
function hexEncode(bytes) {
|
|
5551
|
+
let out = "";
|
|
5552
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
5553
|
+
const b = bytes[i];
|
|
5554
|
+
out += HEX[b >> 4 & 15] + HEX[b & 15];
|
|
5555
|
+
}
|
|
5556
|
+
return out;
|
|
5557
|
+
}
|
|
5558
|
+
function hexDecode(hex) {
|
|
5559
|
+
if (hex.length % 2 !== 0) {
|
|
5560
|
+
throw new Error("hex string must have even length");
|
|
5561
|
+
}
|
|
5562
|
+
const out = new Uint8Array(hex.length / 2);
|
|
5563
|
+
for (let i = 0; i < out.length; i++) {
|
|
5564
|
+
const hi = parseInt(hex[i * 2], 16);
|
|
5565
|
+
const lo = parseInt(hex[i * 2 + 1], 16);
|
|
5566
|
+
if (Number.isNaN(hi) || Number.isNaN(lo)) {
|
|
5567
|
+
throw new Error("invalid hex character");
|
|
5568
|
+
}
|
|
5569
|
+
out[i] = hi << 4 | lo;
|
|
5570
|
+
}
|
|
5571
|
+
return out;
|
|
5572
|
+
}
|
|
5573
|
+
function base64Encode(bytes) {
|
|
5574
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
5575
|
+
let out = "";
|
|
5576
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
5577
|
+
const b0 = bytes[i];
|
|
5578
|
+
const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;
|
|
5579
|
+
const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;
|
|
5580
|
+
out += chars[b0 >> 2 & 63];
|
|
5581
|
+
out += chars[(b0 << 4 | b1 >> 4) & 63];
|
|
5582
|
+
out += i + 1 < bytes.length ? chars[(b1 << 2 | b2 >> 6) & 63] : "=";
|
|
5583
|
+
out += i + 2 < bytes.length ? chars[b2 & 63] : "=";
|
|
5584
|
+
}
|
|
5585
|
+
return out;
|
|
5586
|
+
}
|
|
5587
|
+
function base64Decode(s) {
|
|
5588
|
+
const clean = s.replace(/[^A-Za-z0-9+/=]/g, "");
|
|
5589
|
+
const len = clean.length;
|
|
5590
|
+
if (len % 4 !== 0) {
|
|
5591
|
+
throw new Error("invalid base64 input");
|
|
5592
|
+
}
|
|
5593
|
+
const padding = clean.endsWith("==") ? 2 : clean.endsWith("=") ? 1 : 0;
|
|
5594
|
+
const outLen = len / 4 * 3 - padding;
|
|
5595
|
+
const out = new Uint8Array(outLen);
|
|
5596
|
+
const lookup = {};
|
|
5597
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
5598
|
+
for (let i = 0; i < chars.length; i++) lookup[chars[i]] = i;
|
|
5599
|
+
let outIdx = 0;
|
|
5600
|
+
for (let i = 0; i < len; i += 4) {
|
|
5601
|
+
const v0 = lookup[clean[i]] ?? 0;
|
|
5602
|
+
const v1 = lookup[clean[i + 1]] ?? 0;
|
|
5603
|
+
const v2 = clean[i + 2] === "=" ? 0 : lookup[clean[i + 2]] ?? 0;
|
|
5604
|
+
const v3 = clean[i + 3] === "=" ? 0 : lookup[clean[i + 3]] ?? 0;
|
|
5605
|
+
const b0 = v0 << 2 | v1 >> 4;
|
|
5606
|
+
const b1 = (v1 & 15) << 4 | v2 >> 2;
|
|
5607
|
+
const b2 = (v2 & 3) << 6 | v3;
|
|
5608
|
+
if (outIdx < outLen) out[outIdx++] = b0;
|
|
5609
|
+
if (outIdx < outLen) out[outIdx++] = b1;
|
|
5610
|
+
if (outIdx < outLen) out[outIdx++] = b2;
|
|
5611
|
+
}
|
|
5612
|
+
return out;
|
|
5613
|
+
}
|
|
5614
|
+
function utf8Encode(s) {
|
|
5615
|
+
return new TextEncoder().encode(s);
|
|
5616
|
+
}
|
|
5617
|
+
function utf8Decode(b) {
|
|
5618
|
+
return new TextDecoder().decode(b);
|
|
5619
|
+
}
|
|
5620
|
+
function canonicalHashHex(sha256, value) {
|
|
5621
|
+
const canonical = canonicalize(value);
|
|
5622
|
+
return hexEncode(sha256(utf8Encode(canonical)));
|
|
5623
|
+
}
|
|
5624
|
+
|
|
5480
5625
|
// src/vault/types.ts
|
|
5481
5626
|
var VaultPublicSpaceKVActions = [
|
|
5482
5627
|
"tinycloud.kv/get",
|
|
@@ -5646,7 +5791,7 @@ function toBytes(str) {
|
|
|
5646
5791
|
function fromBytes(bytes) {
|
|
5647
5792
|
return new TextDecoder().decode(bytes);
|
|
5648
5793
|
}
|
|
5649
|
-
function
|
|
5794
|
+
function hexEncode2(bytes) {
|
|
5650
5795
|
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
5651
5796
|
}
|
|
5652
5797
|
function concatBytes(...arrays) {
|
|
@@ -5659,14 +5804,14 @@ function concatBytes(...arrays) {
|
|
|
5659
5804
|
}
|
|
5660
5805
|
return result;
|
|
5661
5806
|
}
|
|
5662
|
-
function
|
|
5807
|
+
function base64Encode2(bytes) {
|
|
5663
5808
|
let binary = "";
|
|
5664
5809
|
for (let i = 0; i < bytes.length; i++) {
|
|
5665
5810
|
binary += String.fromCharCode(bytes[i]);
|
|
5666
5811
|
}
|
|
5667
5812
|
return btoa(binary);
|
|
5668
5813
|
}
|
|
5669
|
-
function
|
|
5814
|
+
function base64Decode2(str) {
|
|
5670
5815
|
const binary = atob(str);
|
|
5671
5816
|
const bytes = new Uint8Array(binary.length);
|
|
5672
5817
|
for (let i = 0; i < binary.length; i++) {
|
|
@@ -5708,6 +5853,11 @@ function defaultVaultMessage(input) {
|
|
|
5708
5853
|
return input.message;
|
|
5709
5854
|
case "PERMISSION_DENIED":
|
|
5710
5855
|
return input.message;
|
|
5856
|
+
case "AAD_MISMATCH":
|
|
5857
|
+
case "ETAG_MISMATCH":
|
|
5858
|
+
case "INVALID_ETAG":
|
|
5859
|
+
case "OUTCOME_UNKNOWN":
|
|
5860
|
+
case "NODE_BUSY":
|
|
5711
5861
|
case "STORAGE_QUOTA_EXCEEDED":
|
|
5712
5862
|
case "STORAGE_LIMIT_REACHED":
|
|
5713
5863
|
return input.message;
|
|
@@ -5730,6 +5880,37 @@ function typedKvCause(error) {
|
|
|
5730
5880
|
...error.cause !== void 0 ? { cause: error.cause } : {}
|
|
5731
5881
|
});
|
|
5732
5882
|
}
|
|
5883
|
+
function canonicalAadSpaceId(spaceId) {
|
|
5884
|
+
return spaceId.replace(/0x[0-9a-fA-F]{40}/g, (address) => address.toLowerCase());
|
|
5885
|
+
}
|
|
5886
|
+
function checkEnvelopeAad(value, spaceId, key) {
|
|
5887
|
+
const e = value;
|
|
5888
|
+
if (typeof e !== "object" || e === null || Array.isArray(e) || !["networkId", "alg", "encryptedSymmetricKey", "encryptedSymmetricKeyHash", "ciphertext"].every(
|
|
5889
|
+
(field) => typeof e[field] === "string" && e[field] !== ""
|
|
5890
|
+
) || !Number.isInteger(e.keyVersion) || e.aad !== void 0 && typeof e.aad !== "string") {
|
|
5891
|
+
return "corrupt";
|
|
5892
|
+
}
|
|
5893
|
+
let aad = "";
|
|
5894
|
+
try {
|
|
5895
|
+
if (typeof e.aad === "string") aad = fromBytes(base64Decode(e.aad));
|
|
5896
|
+
} catch {
|
|
5897
|
+
}
|
|
5898
|
+
const suffix = `:${key}`;
|
|
5899
|
+
return aad.endsWith(suffix) && canonicalAadSpaceId(aad.slice(0, -suffix.length)) === canonicalAadSpaceId(`tinycloud.vault:${spaceId}`) ? "ok" : "mismatch";
|
|
5900
|
+
}
|
|
5901
|
+
var AMBIGUOUS_WRITE_STATUSES = [500, 502, 503, 504, 520, 522, 524];
|
|
5902
|
+
function ifMatchWriteError(key, error) {
|
|
5903
|
+
const status = error.meta?.status;
|
|
5904
|
+
const code = error.code === ErrorCodes.KV_PRECONDITION_FAILED ? "ETAG_MISMATCH" : error.code === ErrorCodes.KV_WRITE_FAILED && status === 400 ? "INVALID_ETAG" : error.code === ErrorCodes.OUTCOME_UNKNOWN || error.code === ErrorCodes.KV_WRITE_FAILED && typeof status === "number" && AMBIGUOUS_WRITE_STATUSES.includes(status) || (error.code === ErrorCodes.NETWORK_ERROR || error.code === ErrorCodes.TIMEOUT) && status === void 0 && error.meta?.requestMayHaveDispatched !== false ? "OUTCOME_UNKNOWN" : error.code === ErrorCodes.NODE_BUSY || error.code === ErrorCodes.REQUEST_TIMEOUT ? "NODE_BUSY" : void 0;
|
|
5905
|
+
if (code === void 0) return void 0;
|
|
5906
|
+
return vaultError({
|
|
5907
|
+
code,
|
|
5908
|
+
key,
|
|
5909
|
+
message: error.message,
|
|
5910
|
+
...error.meta !== void 0 ? { meta: error.meta } : {},
|
|
5911
|
+
cause: typedKvCause(error)
|
|
5912
|
+
});
|
|
5913
|
+
}
|
|
5733
5914
|
function kvWriteError(context, error) {
|
|
5734
5915
|
if (error.code === ErrorCodes.STORAGE_QUOTA_EXCEEDED || error.code === ErrorCodes.STORAGE_LIMIT_REACHED) {
|
|
5735
5916
|
return vaultError({ code: error.code, message: error.message, meta: error.meta });
|
|
@@ -5916,7 +6097,7 @@ var DataVaultService = class extends BaseService {
|
|
|
5916
6097
|
}
|
|
5917
6098
|
if (existingPubKey) {
|
|
5918
6099
|
this.encryptionIdentity = {
|
|
5919
|
-
publicKey:
|
|
6100
|
+
publicKey: base64Decode2(existingPubKey),
|
|
5920
6101
|
privateKey: new Uint8Array(0)
|
|
5921
6102
|
// private key not available without signing
|
|
5922
6103
|
};
|
|
@@ -5941,7 +6122,7 @@ var DataVaultService = class extends BaseService {
|
|
|
5941
6122
|
);
|
|
5942
6123
|
this.encryptionIdentity = this.crypto.x25519FromSeed(seed);
|
|
5943
6124
|
try {
|
|
5944
|
-
const pubKeyB64 =
|
|
6125
|
+
const pubKeyB64 = base64Encode2(this.encryptionIdentity.publicKey);
|
|
5945
6126
|
await this.tc.ensurePublicSpace();
|
|
5946
6127
|
await this.tc.publicKV.put(".well-known/vault-pubkey", pubKeyB64);
|
|
5947
6128
|
await this.tc.publicKV.put(".well-known/vault-version", CURRENT_VAULT_VERSION);
|
|
@@ -6007,6 +6188,21 @@ var DataVaultService = class extends BaseService {
|
|
|
6007
6188
|
message: "Authentication required"
|
|
6008
6189
|
});
|
|
6009
6190
|
}
|
|
6191
|
+
if (options?.ifMatch !== void 0) {
|
|
6192
|
+
if (options.ifNoneMatch !== void 0) {
|
|
6193
|
+
return vaultError({
|
|
6194
|
+
code: "STORAGE_ERROR",
|
|
6195
|
+
cause: new Error("ifMatch cannot be combined with ifNoneMatch")
|
|
6196
|
+
});
|
|
6197
|
+
}
|
|
6198
|
+
if (!/^"[^"\s]+"$/.test(options.ifMatch)) {
|
|
6199
|
+
return vaultError({
|
|
6200
|
+
code: "INVALID_ETAG",
|
|
6201
|
+
key,
|
|
6202
|
+
message: options.ifMatch.startsWith("W/") ? 'ifMatch is a weak etag (W/"..."): the node only issues strong etags, so the etag was weakened in transit (a proxy or CDN rewrote it). Read it from a connection that preserves strong ETags' : "ifMatch must be the strong, quoted ETag returned by a network read (VaultEntry.etag), passed exactly as returned"
|
|
6203
|
+
});
|
|
6204
|
+
}
|
|
6205
|
+
}
|
|
6010
6206
|
try {
|
|
6011
6207
|
const { plaintext, contentType } = this.serializeValue(value, options);
|
|
6012
6208
|
const metadata = {
|
|
@@ -6029,7 +6225,8 @@ var DataVaultService = class extends BaseService {
|
|
|
6029
6225
|
}
|
|
6030
6226
|
const valuePath = `vault/${key}`;
|
|
6031
6227
|
const valueBody = JSON.stringify(envelopeResult.data);
|
|
6032
|
-
const
|
|
6228
|
+
const putOptions = options?.ifMatch !== void 0 ? { ifMatch: options.ifMatch } : options?.ifNoneMatch !== void 0 ? { ifNoneMatch: options.ifNoneMatch } : void 0;
|
|
6229
|
+
const valuePutResult = putOptions !== void 0 ? await this.tc.kv.put(valuePath, valueBody, putOptions) : await this.tc.kv.put(valuePath, valueBody);
|
|
6033
6230
|
if (!valuePutResult.ok) {
|
|
6034
6231
|
if (options?.ifNoneMatch !== void 0 && valuePutResult.error.code === ErrorCodes.KV_PRECONDITION_FAILED) {
|
|
6035
6232
|
return vaultError({
|
|
@@ -6039,6 +6236,10 @@ var DataVaultService = class extends BaseService {
|
|
|
6039
6236
|
cause: typedKvCause(valuePutResult.error)
|
|
6040
6237
|
});
|
|
6041
6238
|
}
|
|
6239
|
+
if (options?.ifMatch !== void 0) {
|
|
6240
|
+
const classified = ifMatchWriteError(key, valuePutResult.error);
|
|
6241
|
+
if (classified !== void 0) return classified;
|
|
6242
|
+
}
|
|
6042
6243
|
return kvWriteError("Failed to store encrypted value", valuePutResult.error);
|
|
6043
6244
|
}
|
|
6044
6245
|
return { ok: true, data: void 0 };
|
|
@@ -6073,6 +6274,19 @@ var DataVaultService = class extends BaseService {
|
|
|
6073
6274
|
}
|
|
6074
6275
|
const rawEnvelope = unwrapKVData(valueResult.data);
|
|
6075
6276
|
const envelope = typeof rawEnvelope === "string" ? JSON.parse(rawEnvelope) : rawEnvelope;
|
|
6277
|
+
if (options?.expectAad === true) {
|
|
6278
|
+
const verdict = checkEnvelopeAad(envelope, this.vaultConfig.spaceId, key);
|
|
6279
|
+
if (verdict === "corrupt") {
|
|
6280
|
+
return vaultError({ code: "DECRYPTION_FAILED", message: "envelope is malformed" });
|
|
6281
|
+
}
|
|
6282
|
+
if (verdict === "mismatch") {
|
|
6283
|
+
return vaultError({
|
|
6284
|
+
code: "AAD_MISMATCH",
|
|
6285
|
+
key,
|
|
6286
|
+
message: `Envelope at ${key} is not bound to this space and key`
|
|
6287
|
+
});
|
|
6288
|
+
}
|
|
6289
|
+
}
|
|
6076
6290
|
const proof = await this.decryptCapabilityProof();
|
|
6077
6291
|
const plaintextResult = await config.service.decryptEnvelope(envelope, proof);
|
|
6078
6292
|
if (!plaintextResult.ok) {
|
|
@@ -6089,7 +6303,11 @@ var DataVaultService = class extends BaseService {
|
|
|
6089
6303
|
contentType,
|
|
6090
6304
|
options
|
|
6091
6305
|
);
|
|
6092
|
-
|
|
6306
|
+
const etag = options?.source === "network" ? valueResult.data.headers?.etag : void 0;
|
|
6307
|
+
return {
|
|
6308
|
+
ok: true,
|
|
6309
|
+
data: { value, metadata, keyId, ...etag === void 0 ? {} : { etag } }
|
|
6310
|
+
};
|
|
6093
6311
|
} catch (error) {
|
|
6094
6312
|
return vaultError({
|
|
6095
6313
|
code: "STORAGE_ERROR",
|
|
@@ -6135,6 +6353,12 @@ var DataVaultService = class extends BaseService {
|
|
|
6135
6353
|
} catch {
|
|
6136
6354
|
return { status: "corrupt_envelope" };
|
|
6137
6355
|
}
|
|
6356
|
+
if (options?.expectAad === true) {
|
|
6357
|
+
const verdict = checkEnvelopeAad(envelope, this.vaultConfig.spaceId, key);
|
|
6358
|
+
if (verdict !== "ok") {
|
|
6359
|
+
return { status: verdict === "corrupt" ? "corrupt_envelope" : "aad_mismatch" };
|
|
6360
|
+
}
|
|
6361
|
+
}
|
|
6138
6362
|
let plaintextResult;
|
|
6139
6363
|
try {
|
|
6140
6364
|
const proof = await this.decryptCapabilityProof();
|
|
@@ -6166,7 +6390,11 @@ var DataVaultService = class extends BaseService {
|
|
|
6166
6390
|
contentType,
|
|
6167
6391
|
options
|
|
6168
6392
|
);
|
|
6169
|
-
|
|
6393
|
+
const etag = valueResult.data.headers?.etag;
|
|
6394
|
+
return {
|
|
6395
|
+
status: "ok",
|
|
6396
|
+
entry: { value, metadata, keyId, ...etag === void 0 ? {} : { etag } }
|
|
6397
|
+
};
|
|
6170
6398
|
} catch {
|
|
6171
6399
|
return { status: "invalid_payload" };
|
|
6172
6400
|
}
|
|
@@ -6216,6 +6444,14 @@ var DataVaultService = class extends BaseService {
|
|
|
6216
6444
|
)
|
|
6217
6445
|
});
|
|
6218
6446
|
}
|
|
6447
|
+
if (options?.ifMatch !== void 0) {
|
|
6448
|
+
return vaultError({
|
|
6449
|
+
code: "STORAGE_ERROR",
|
|
6450
|
+
cause: new Error(
|
|
6451
|
+
"ifMatch is only supported by network-encrypted vaults"
|
|
6452
|
+
)
|
|
6453
|
+
});
|
|
6454
|
+
}
|
|
6219
6455
|
if (!this._isUnlocked || !this.masterKey) {
|
|
6220
6456
|
return vaultError({
|
|
6221
6457
|
code: "VAULT_LOCKED",
|
|
@@ -6241,7 +6477,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6241
6477
|
}
|
|
6242
6478
|
const contentType = options?.contentType ?? (value instanceof Uint8Array ? "application/octet-stream" : "application/json");
|
|
6243
6479
|
const entryKey = this.crypto.randomBytes(32);
|
|
6244
|
-
const keyId =
|
|
6480
|
+
const keyId = hexEncode2(this.crypto.sha256(entryKey)).slice(0, 16);
|
|
6245
6481
|
const encrypted = this.crypto.encrypt(entryKey, plaintext);
|
|
6246
6482
|
const keyBlob = this.crypto.encrypt(this.masterKey, entryKey);
|
|
6247
6483
|
const metadata = {
|
|
@@ -6259,7 +6495,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6259
6495
|
...metadata
|
|
6260
6496
|
});
|
|
6261
6497
|
const keyPayload = JSON.stringify({
|
|
6262
|
-
key:
|
|
6498
|
+
key: base64Encode2(keyBlob),
|
|
6263
6499
|
metadata: keyMetadata
|
|
6264
6500
|
});
|
|
6265
6501
|
const keyPutResult = await this.tc.kv.put(
|
|
@@ -6270,7 +6506,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6270
6506
|
return kvWriteError("Failed to store key blob", keyPutResult.error);
|
|
6271
6507
|
}
|
|
6272
6508
|
const valuePayload = JSON.stringify({
|
|
6273
|
-
data:
|
|
6509
|
+
data: base64Encode2(encrypted),
|
|
6274
6510
|
metadata
|
|
6275
6511
|
});
|
|
6276
6512
|
const valuePutResult = await this.tc.kv.put(
|
|
@@ -6301,6 +6537,12 @@ var DataVaultService = class extends BaseService {
|
|
|
6301
6537
|
if (this.usesNetworkEncryption) {
|
|
6302
6538
|
return this.getNetworkEncrypted(key, options);
|
|
6303
6539
|
}
|
|
6540
|
+
if (options?.expectAad === true) {
|
|
6541
|
+
return vaultError({
|
|
6542
|
+
code: "STORAGE_ERROR",
|
|
6543
|
+
cause: new Error("expectAad is only supported by network-encrypted vaults")
|
|
6544
|
+
});
|
|
6545
|
+
}
|
|
6304
6546
|
if (!this._isUnlocked || !this.masterKey) {
|
|
6305
6547
|
return vaultError({
|
|
6306
6548
|
code: "VAULT_LOCKED",
|
|
@@ -6322,7 +6564,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6322
6564
|
return vaultError({ code: "KEY_NOT_FOUND", key });
|
|
6323
6565
|
}
|
|
6324
6566
|
const keyEnvelope = JSON.parse(keyResult.data.data);
|
|
6325
|
-
const keyBlobBytes =
|
|
6567
|
+
const keyBlobBytes = base64Decode2(keyEnvelope.key);
|
|
6326
6568
|
const entryKey = this.crypto.decrypt(this.masterKey, keyBlobBytes);
|
|
6327
6569
|
const valueResult = await this.tc.kv.get(`vault/${key}`, {
|
|
6328
6570
|
raw: true,
|
|
@@ -6332,7 +6574,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6332
6574
|
return vaultError({ code: "KEY_NOT_FOUND", key });
|
|
6333
6575
|
}
|
|
6334
6576
|
const valueEnvelope = JSON.parse(valueResult.data.data);
|
|
6335
|
-
const encryptedBytes =
|
|
6577
|
+
const encryptedBytes = base64Decode2(valueEnvelope.data);
|
|
6336
6578
|
const plaintext = this.crypto.decrypt(entryKey, encryptedBytes);
|
|
6337
6579
|
const metadata = valueEnvelope.metadata ?? {};
|
|
6338
6580
|
const contentType = metadata[VaultHeaders.CONTENT_TYPE] ?? "application/json";
|
|
@@ -6580,7 +6822,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6580
6822
|
return vaultError({ code: "KEY_NOT_FOUND", key });
|
|
6581
6823
|
}
|
|
6582
6824
|
const keyEnvelope = JSON.parse(keyResult.data.data);
|
|
6583
|
-
const keyBlobBytes =
|
|
6825
|
+
const keyBlobBytes = base64Decode2(keyEnvelope.key);
|
|
6584
6826
|
const entryKey = this.crypto.decrypt(this.masterKey, keyBlobBytes);
|
|
6585
6827
|
const ephemeralSeed = this.crypto.randomBytes(32);
|
|
6586
6828
|
const ephemeralKeyPair = this.crypto.x25519FromSeed(ephemeralSeed);
|
|
@@ -6599,7 +6841,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6599
6841
|
encryptedGrant
|
|
6600
6842
|
);
|
|
6601
6843
|
const grantPayload = JSON.stringify({
|
|
6602
|
-
grant:
|
|
6844
|
+
grant: base64Encode2(grantBlob),
|
|
6603
6845
|
spaceId: this.vaultConfig.spaceId,
|
|
6604
6846
|
metadata: {
|
|
6605
6847
|
[VaultHeaders.GRANT_VERSION]: "1",
|
|
@@ -6639,6 +6881,14 @@ var DataVaultService = class extends BaseService {
|
|
|
6639
6881
|
*/
|
|
6640
6882
|
async getShared(grantorDID, key, options) {
|
|
6641
6883
|
return this.withTelemetry("getShared", key, async () => {
|
|
6884
|
+
if (options?.expectAad === true) {
|
|
6885
|
+
return vaultError({
|
|
6886
|
+
code: "STORAGE_ERROR",
|
|
6887
|
+
cause: new Error(
|
|
6888
|
+
"expectAad is not supported by getShared: the AAD binds the grantor's space, which this vault does not know"
|
|
6889
|
+
)
|
|
6890
|
+
});
|
|
6891
|
+
}
|
|
6642
6892
|
if (this.usesNetworkEncryption) {
|
|
6643
6893
|
const grantorKV = options?.kv;
|
|
6644
6894
|
if (!grantorKV) {
|
|
@@ -6733,7 +6983,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6733
6983
|
});
|
|
6734
6984
|
}
|
|
6735
6985
|
const grantEnvelope = typeof grantResult.data?.data === "string" ? JSON.parse(grantResult.data.data) : grantResult.data?.data;
|
|
6736
|
-
const grantBlobBytes =
|
|
6986
|
+
const grantBlobBytes = base64Decode2(grantEnvelope.grant);
|
|
6737
6987
|
const ephemeralPubKey = grantBlobBytes.slice(0, 32);
|
|
6738
6988
|
const encryptedGrant = grantBlobBytes.slice(32);
|
|
6739
6989
|
const sharedSecret = this.crypto.x25519Dh(
|
|
@@ -6756,7 +7006,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6756
7006
|
});
|
|
6757
7007
|
}
|
|
6758
7008
|
const valueEnvelope = typeof valueResult.data?.data === "string" ? JSON.parse(valueResult.data.data) : valueResult.data?.data;
|
|
6759
|
-
const encryptedBytes =
|
|
7009
|
+
const encryptedBytes = base64Decode2(valueEnvelope.data);
|
|
6760
7010
|
const plaintext = this.crypto.decrypt(entryKey, encryptedBytes);
|
|
6761
7011
|
const metadata = valueEnvelope.metadata ?? {};
|
|
6762
7012
|
const contentType = metadata[VaultHeaders.CONTENT_TYPE] ?? "application/json";
|
|
@@ -6810,7 +7060,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6810
7060
|
if (!result.ok) {
|
|
6811
7061
|
return vaultError({ code: "PUBLIC_KEY_NOT_FOUND", did });
|
|
6812
7062
|
}
|
|
6813
|
-
const pubKeyBytes =
|
|
7063
|
+
const pubKeyBytes = base64Decode2(result.data);
|
|
6814
7064
|
return { ok: true, data: pubKeyBytes };
|
|
6815
7065
|
} catch (error) {
|
|
6816
7066
|
return vaultError({ code: "PUBLIC_KEY_NOT_FOUND", did });
|
|
@@ -6929,7 +7179,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6929
7179
|
}
|
|
6930
7180
|
const currentEntry = getResult.data;
|
|
6931
7181
|
const newEntryKey = this.crypto.randomBytes(32);
|
|
6932
|
-
const newKeyId =
|
|
7182
|
+
const newKeyId = hexEncode2(this.crypto.sha256(newEntryKey)).slice(
|
|
6933
7183
|
0,
|
|
6934
7184
|
16
|
|
6935
7185
|
);
|
|
@@ -6946,7 +7196,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6946
7196
|
[VaultHeaders.KEY_ID]: newKeyId
|
|
6947
7197
|
};
|
|
6948
7198
|
const keyPayload = JSON.stringify({
|
|
6949
|
-
key:
|
|
7199
|
+
key: base64Encode2(newKeyBlob),
|
|
6950
7200
|
metadata: JSON.stringify({
|
|
6951
7201
|
keyId: newKeyId,
|
|
6952
7202
|
...metadata
|
|
@@ -6960,7 +7210,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6960
7210
|
return kvWriteError("Failed to store rotated key blob", keyPutResult.error);
|
|
6961
7211
|
}
|
|
6962
7212
|
const valuePayload = JSON.stringify({
|
|
6963
|
-
data:
|
|
7213
|
+
data: base64Encode2(encrypted),
|
|
6964
7214
|
metadata
|
|
6965
7215
|
});
|
|
6966
7216
|
const valuePutResult = await this.tc.kv.put(
|
|
@@ -7252,116 +7502,6 @@ var SecretsService = class {
|
|
|
7252
7502
|
}
|
|
7253
7503
|
};
|
|
7254
7504
|
|
|
7255
|
-
// src/encryption/canonical.ts
|
|
7256
|
-
function canonicalize(value) {
|
|
7257
|
-
if (value === void 0) {
|
|
7258
|
-
return "";
|
|
7259
|
-
}
|
|
7260
|
-
return stringify(value);
|
|
7261
|
-
}
|
|
7262
|
-
function stringify(value) {
|
|
7263
|
-
if (value === null) return "null";
|
|
7264
|
-
switch (typeof value) {
|
|
7265
|
-
case "boolean":
|
|
7266
|
-
case "number":
|
|
7267
|
-
return JSON.stringify(value);
|
|
7268
|
-
case "string":
|
|
7269
|
-
return JSON.stringify(value);
|
|
7270
|
-
case "object": {
|
|
7271
|
-
if (Array.isArray(value)) {
|
|
7272
|
-
return `[${value.map(stringify).join(",")}]`;
|
|
7273
|
-
}
|
|
7274
|
-
const keys = Object.keys(value).sort();
|
|
7275
|
-
const parts = [];
|
|
7276
|
-
for (const k of keys) {
|
|
7277
|
-
const v = value[k];
|
|
7278
|
-
if (v === void 0) continue;
|
|
7279
|
-
parts.push(`${JSON.stringify(k)}:${stringify(v)}`);
|
|
7280
|
-
}
|
|
7281
|
-
return `{${parts.join(",")}}`;
|
|
7282
|
-
}
|
|
7283
|
-
default:
|
|
7284
|
-
throw new TypeError(
|
|
7285
|
-
`canonicalize: unsupported value type ${typeof value}`
|
|
7286
|
-
);
|
|
7287
|
-
}
|
|
7288
|
-
}
|
|
7289
|
-
var HEX = "0123456789abcdef";
|
|
7290
|
-
function hexEncode2(bytes) {
|
|
7291
|
-
let out = "";
|
|
7292
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
7293
|
-
const b = bytes[i];
|
|
7294
|
-
out += HEX[b >> 4 & 15] + HEX[b & 15];
|
|
7295
|
-
}
|
|
7296
|
-
return out;
|
|
7297
|
-
}
|
|
7298
|
-
function hexDecode(hex) {
|
|
7299
|
-
if (hex.length % 2 !== 0) {
|
|
7300
|
-
throw new Error("hex string must have even length");
|
|
7301
|
-
}
|
|
7302
|
-
const out = new Uint8Array(hex.length / 2);
|
|
7303
|
-
for (let i = 0; i < out.length; i++) {
|
|
7304
|
-
const hi = parseInt(hex[i * 2], 16);
|
|
7305
|
-
const lo = parseInt(hex[i * 2 + 1], 16);
|
|
7306
|
-
if (Number.isNaN(hi) || Number.isNaN(lo)) {
|
|
7307
|
-
throw new Error("invalid hex character");
|
|
7308
|
-
}
|
|
7309
|
-
out[i] = hi << 4 | lo;
|
|
7310
|
-
}
|
|
7311
|
-
return out;
|
|
7312
|
-
}
|
|
7313
|
-
function base64Encode2(bytes) {
|
|
7314
|
-
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
7315
|
-
let out = "";
|
|
7316
|
-
for (let i = 0; i < bytes.length; i += 3) {
|
|
7317
|
-
const b0 = bytes[i];
|
|
7318
|
-
const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;
|
|
7319
|
-
const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;
|
|
7320
|
-
out += chars[b0 >> 2 & 63];
|
|
7321
|
-
out += chars[(b0 << 4 | b1 >> 4) & 63];
|
|
7322
|
-
out += i + 1 < bytes.length ? chars[(b1 << 2 | b2 >> 6) & 63] : "=";
|
|
7323
|
-
out += i + 2 < bytes.length ? chars[b2 & 63] : "=";
|
|
7324
|
-
}
|
|
7325
|
-
return out;
|
|
7326
|
-
}
|
|
7327
|
-
function base64Decode2(s) {
|
|
7328
|
-
const clean = s.replace(/[^A-Za-z0-9+/=]/g, "");
|
|
7329
|
-
const len = clean.length;
|
|
7330
|
-
if (len % 4 !== 0) {
|
|
7331
|
-
throw new Error("invalid base64 input");
|
|
7332
|
-
}
|
|
7333
|
-
const padding = clean.endsWith("==") ? 2 : clean.endsWith("=") ? 1 : 0;
|
|
7334
|
-
const outLen = len / 4 * 3 - padding;
|
|
7335
|
-
const out = new Uint8Array(outLen);
|
|
7336
|
-
const lookup = {};
|
|
7337
|
-
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
7338
|
-
for (let i = 0; i < chars.length; i++) lookup[chars[i]] = i;
|
|
7339
|
-
let outIdx = 0;
|
|
7340
|
-
for (let i = 0; i < len; i += 4) {
|
|
7341
|
-
const v0 = lookup[clean[i]] ?? 0;
|
|
7342
|
-
const v1 = lookup[clean[i + 1]] ?? 0;
|
|
7343
|
-
const v2 = clean[i + 2] === "=" ? 0 : lookup[clean[i + 2]] ?? 0;
|
|
7344
|
-
const v3 = clean[i + 3] === "=" ? 0 : lookup[clean[i + 3]] ?? 0;
|
|
7345
|
-
const b0 = v0 << 2 | v1 >> 4;
|
|
7346
|
-
const b1 = (v1 & 15) << 4 | v2 >> 2;
|
|
7347
|
-
const b2 = (v2 & 3) << 6 | v3;
|
|
7348
|
-
if (outIdx < outLen) out[outIdx++] = b0;
|
|
7349
|
-
if (outIdx < outLen) out[outIdx++] = b1;
|
|
7350
|
-
if (outIdx < outLen) out[outIdx++] = b2;
|
|
7351
|
-
}
|
|
7352
|
-
return out;
|
|
7353
|
-
}
|
|
7354
|
-
function utf8Encode(s) {
|
|
7355
|
-
return new TextEncoder().encode(s);
|
|
7356
|
-
}
|
|
7357
|
-
function utf8Decode(b) {
|
|
7358
|
-
return new TextDecoder().decode(b);
|
|
7359
|
-
}
|
|
7360
|
-
function canonicalHashHex(sha256, value) {
|
|
7361
|
-
const canonical = canonicalize(value);
|
|
7362
|
-
return hexEncode2(sha256(utf8Encode(canonical)));
|
|
7363
|
-
}
|
|
7364
|
-
|
|
7365
7505
|
// src/encryption/types.ts
|
|
7366
7506
|
var DEFAULT_ENCRYPTION_ALG = "x25519-aes256gcm/v1";
|
|
7367
7507
|
var ENVELOPE_VERSION = 1;
|
|
@@ -7571,7 +7711,7 @@ function encryptToNetwork(crypto2, input) {
|
|
|
7571
7711
|
const symmetricKey = crypto2.randomBytes(32);
|
|
7572
7712
|
const ciphertext = crypto2.authEncrypt(symmetricKey, input.plaintext, input.aad);
|
|
7573
7713
|
const wrapped = crypto2.sealToNetworkKey(input.networkPublicKey, symmetricKey);
|
|
7574
|
-
const encryptedSymmetricKey =
|
|
7714
|
+
const encryptedSymmetricKey = base64Encode(wrapped);
|
|
7575
7715
|
const encryptedSymmetricKeyHash = canonicalHashHex(
|
|
7576
7716
|
crypto2.sha256,
|
|
7577
7717
|
encryptedSymmetricKey
|
|
@@ -7583,8 +7723,8 @@ function encryptToNetwork(crypto2, input) {
|
|
|
7583
7723
|
keyVersion,
|
|
7584
7724
|
encryptedSymmetricKey,
|
|
7585
7725
|
encryptedSymmetricKeyHash,
|
|
7586
|
-
ciphertext:
|
|
7587
|
-
...input.aad !== void 0 ? { aad:
|
|
7726
|
+
ciphertext: base64Encode(ciphertext),
|
|
7727
|
+
...input.aad !== void 0 ? { aad: base64Encode(input.aad) } : {},
|
|
7588
7728
|
...input.metadata !== void 0 ? { metadata: input.metadata } : {}
|
|
7589
7729
|
};
|
|
7590
7730
|
return { envelope, symmetricKey };
|
|
@@ -7658,8 +7798,8 @@ function validateEnvelope(crypto2, envelope) {
|
|
|
7658
7798
|
return { ok: true, data: e };
|
|
7659
7799
|
}
|
|
7660
7800
|
function decryptEnvelopeWithKey(crypto2, envelope, symmetricKey) {
|
|
7661
|
-
const ciphertext =
|
|
7662
|
-
const aad = envelope.aad !== void 0 ?
|
|
7801
|
+
const ciphertext = base64Decode(envelope.ciphertext);
|
|
7802
|
+
const aad = envelope.aad !== void 0 ? base64Decode(envelope.aad) : void 0;
|
|
7663
7803
|
return crypto2.authDecrypt(symmetricKey, ciphertext, aad);
|
|
7664
7804
|
}
|
|
7665
7805
|
|
|
@@ -7677,7 +7817,7 @@ function buildCanonicalDecryptRequest(input) {
|
|
|
7677
7817
|
return { canonicalBody, bodyHash, receiverPublicKeyHash };
|
|
7678
7818
|
}
|
|
7679
7819
|
function buildDecryptFacts(input) {
|
|
7680
|
-
const bodyHash = input.canonicalBody !== void 0 ?
|
|
7820
|
+
const bodyHash = input.canonicalBody !== void 0 ? hexEncode(input.crypto.sha256(utf8Encode(input.canonicalBody))) : canonicalHashHex(
|
|
7681
7821
|
input.crypto.sha256,
|
|
7682
7822
|
input.body
|
|
7683
7823
|
);
|
|
@@ -7960,7 +8100,7 @@ function verifyDecryptResponse(input) {
|
|
|
7960
8100
|
})
|
|
7961
8101
|
};
|
|
7962
8102
|
}
|
|
7963
|
-
const expectedRequestHash =
|
|
8103
|
+
const expectedRequestHash = hexEncode(
|
|
7964
8104
|
crypto2.sha256(utf8Encode(`${invocationCid}${requestBodyHash}`))
|
|
7965
8105
|
);
|
|
7966
8106
|
if (response.requestHash !== expectedRequestHash) {
|
|
@@ -7984,7 +8124,7 @@ function verifyDecryptResponse(input) {
|
|
|
7984
8124
|
const signedBytes = new TextEncoder().encode(
|
|
7985
8125
|
canonicalSignedResponse(response)
|
|
7986
8126
|
);
|
|
7987
|
-
const signatureBytes =
|
|
8127
|
+
const signatureBytes = base64Decode(response.nodeSignature);
|
|
7988
8128
|
if (!crypto2.verifyNodeSignature(response.nodeId, signedBytes, signatureBytes)) {
|
|
7989
8129
|
return {
|
|
7990
8130
|
ok: false,
|
|
@@ -7996,7 +8136,7 @@ function verifyDecryptResponse(input) {
|
|
|
7996
8136
|
return { ok: true, data: response };
|
|
7997
8137
|
}
|
|
7998
8138
|
function openWrappedKey(crypto2, receiverPrivateKey, response) {
|
|
7999
|
-
const wrapped =
|
|
8139
|
+
const wrapped = base64Decode(response.wrappedKey);
|
|
8000
8140
|
return crypto2.openWithReceiverKey(receiverPrivateKey, wrapped);
|
|
8001
8141
|
}
|
|
8002
8142
|
|
|
@@ -8040,7 +8180,7 @@ var EncryptionService = class extends BaseService {
|
|
|
8040
8180
|
const usable = ensureNetworkUsableForDecrypt(discovered.data);
|
|
8041
8181
|
if (!usable.ok) return usable;
|
|
8042
8182
|
const descriptor = usable.data;
|
|
8043
|
-
const networkPublicKey =
|
|
8183
|
+
const networkPublicKey = base64Decode(descriptor.publicEncryptionKey);
|
|
8044
8184
|
const result = encryptToNetwork(this.crypto, {
|
|
8045
8185
|
// Discovery returns the node's canonical network identifier. Persist
|
|
8046
8186
|
// that spelling in the envelope so a later response can be compared
|
|
@@ -8068,7 +8208,7 @@ var EncryptionService = class extends BaseService {
|
|
|
8068
8208
|
this.assertActive();
|
|
8069
8209
|
const validated = validateEnvelope(this.crypto, envelope);
|
|
8070
8210
|
if (!validated.ok) return validated;
|
|
8071
|
-
if (options?.aad !== void 0 && validated.data.aad !==
|
|
8211
|
+
if (options?.aad !== void 0 && validated.data.aad !== base64Encode(options.aad)) {
|
|
8072
8212
|
return encErr(
|
|
8073
8213
|
encryptionError({
|
|
8074
8214
|
code: "INVALID_INPUT",
|
|
@@ -8096,7 +8236,7 @@ var EncryptionService = class extends BaseService {
|
|
|
8096
8236
|
);
|
|
8097
8237
|
}
|
|
8098
8238
|
const receiverKey = generateRandomReceiverKey({ crypto: this.crypto });
|
|
8099
|
-
const receiverPublicKey =
|
|
8239
|
+
const receiverPublicKey = base64Encode(receiverKey.publicKey);
|
|
8100
8240
|
const receiverPublicKeyHash = canonicalHashHex(
|
|
8101
8241
|
this.crypto.sha256,
|
|
8102
8242
|
receiverPublicKey
|
|
@@ -8272,8 +8412,8 @@ export {
|
|
|
8272
8412
|
authRequiredError,
|
|
8273
8413
|
authUnauthorizedError,
|
|
8274
8414
|
authorizationVerdictOf,
|
|
8275
|
-
|
|
8276
|
-
|
|
8415
|
+
base64Decode,
|
|
8416
|
+
base64Encode,
|
|
8277
8417
|
buildCanonicalDecryptRequest,
|
|
8278
8418
|
buildDecryptAttenuation,
|
|
8279
8419
|
buildDecryptFacts,
|
|
@@ -8303,13 +8443,14 @@ export {
|
|
|
8303
8443
|
generateRandomReceiverKey,
|
|
8304
8444
|
getTinyCloudDebugLogs,
|
|
8305
8445
|
hexDecode,
|
|
8306
|
-
|
|
8446
|
+
hexEncode,
|
|
8307
8447
|
installTinyCloudDebugGlobals,
|
|
8308
8448
|
isNetworkId,
|
|
8309
8449
|
isStorageFullError,
|
|
8310
8450
|
kvPrefixCovers,
|
|
8311
8451
|
networkDiscoveryKey,
|
|
8312
8452
|
networkError,
|
|
8453
|
+
nodeRetryError,
|
|
8313
8454
|
notFoundError,
|
|
8314
8455
|
ok,
|
|
8315
8456
|
openWrappedKey,
|