@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.cjs
CHANGED
|
@@ -84,8 +84,8 @@ __export(src_exports, {
|
|
|
84
84
|
authRequiredError: () => authRequiredError,
|
|
85
85
|
authUnauthorizedError: () => authUnauthorizedError,
|
|
86
86
|
authorizationVerdictOf: () => authorizationVerdictOf,
|
|
87
|
-
base64Decode: () =>
|
|
88
|
-
base64Encode: () =>
|
|
87
|
+
base64Decode: () => base64Decode,
|
|
88
|
+
base64Encode: () => base64Encode,
|
|
89
89
|
buildCanonicalDecryptRequest: () => buildCanonicalDecryptRequest,
|
|
90
90
|
buildDecryptAttenuation: () => buildDecryptAttenuation,
|
|
91
91
|
buildDecryptFacts: () => buildDecryptFacts,
|
|
@@ -115,13 +115,14 @@ __export(src_exports, {
|
|
|
115
115
|
generateRandomReceiverKey: () => generateRandomReceiverKey,
|
|
116
116
|
getTinyCloudDebugLogs: () => getTinyCloudDebugLogs,
|
|
117
117
|
hexDecode: () => hexDecode,
|
|
118
|
-
hexEncode: () =>
|
|
118
|
+
hexEncode: () => hexEncode,
|
|
119
119
|
installTinyCloudDebugGlobals: () => installTinyCloudDebugGlobals,
|
|
120
120
|
isNetworkId: () => isNetworkId,
|
|
121
121
|
isStorageFullError: () => isStorageFullError,
|
|
122
122
|
kvPrefixCovers: () => kvPrefixCovers,
|
|
123
123
|
networkDiscoveryKey: () => networkDiscoveryKey,
|
|
124
124
|
networkError: () => networkError,
|
|
125
|
+
nodeRetryError: () => nodeRetryError,
|
|
125
126
|
notFoundError: () => notFoundError,
|
|
126
127
|
ok: () => ok,
|
|
127
128
|
openWrappedKey: () => openWrappedKey,
|
|
@@ -179,6 +180,14 @@ var ErrorCodes = {
|
|
|
179
180
|
ABORTED: "ABORTED",
|
|
180
181
|
INVALID_INPUT: "INVALID_INPUT",
|
|
181
182
|
PERMISSION_DENIED: "PERMISSION_DENIED",
|
|
183
|
+
// Node retry signals (tinycloud-node #347). `meta.retryable` says whether a
|
|
184
|
+
// retry is safe; `meta.retryAfterMs` carries the node's Retry-After.
|
|
185
|
+
/** HTTP 503 `sql_busy:<stage>` / `unavailable_retryable`: nothing was applied; retry after `meta.retryAfterMs`. */
|
|
186
|
+
NODE_BUSY: "NODE_BUSY",
|
|
187
|
+
/** HTTP 503 `request_timeout`: the node's deadline passed before any write began; nothing was applied. */
|
|
188
|
+
REQUEST_TIMEOUT: "REQUEST_TIMEOUT",
|
|
189
|
+
/** HTTP 500 `outcome_unknown`: a write may have been applied; read back before retrying a non-idempotent operation. */
|
|
190
|
+
OUTCOME_UNKNOWN: "OUTCOME_UNKNOWN",
|
|
182
191
|
// KV-specific errors
|
|
183
192
|
KV_NOT_FOUND: "KV_NOT_FOUND",
|
|
184
193
|
KV_WRITE_FAILED: "KV_WRITE_FAILED",
|
|
@@ -1123,6 +1132,42 @@ var ServiceContext = class {
|
|
|
1123
1132
|
}
|
|
1124
1133
|
};
|
|
1125
1134
|
|
|
1135
|
+
// src/responseErrors.ts
|
|
1136
|
+
function parseServiceErrorBody(errorText) {
|
|
1137
|
+
try {
|
|
1138
|
+
return JSON.parse(errorText);
|
|
1139
|
+
} catch {
|
|
1140
|
+
return {};
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
function formatServiceResponseError(serviceLabel, operation, status, errorText, parsed) {
|
|
1144
|
+
if (parsed.message) {
|
|
1145
|
+
return compactMessage(parsed.message);
|
|
1146
|
+
}
|
|
1147
|
+
if (looksLikeHtml(errorText)) {
|
|
1148
|
+
if (status === 524 || /524\s*[:-]/i.test(errorText) || /a timeout occurred/i.test(errorText)) {
|
|
1149
|
+
return `${serviceLabel} ${operation} failed: upstream request timed out. Please retry.`;
|
|
1150
|
+
}
|
|
1151
|
+
return `${serviceLabel} ${operation} failed: upstream service returned an HTML error page (${status}).`;
|
|
1152
|
+
}
|
|
1153
|
+
return `${serviceLabel} ${operation} failed: ${status} - ${compactMessage(errorText)}`;
|
|
1154
|
+
}
|
|
1155
|
+
function responseErrorMeta(status, statusText, errorText) {
|
|
1156
|
+
const meta = { status, statusText };
|
|
1157
|
+
const snippet = compactMessage(errorText);
|
|
1158
|
+
if (snippet) {
|
|
1159
|
+
meta.responseSnippet = snippet.slice(0, 300);
|
|
1160
|
+
}
|
|
1161
|
+
return meta;
|
|
1162
|
+
}
|
|
1163
|
+
function looksLikeHtml(text) {
|
|
1164
|
+
return /<!doctype html/i.test(text) || /<html[\s>]/i.test(text);
|
|
1165
|
+
}
|
|
1166
|
+
function compactMessage(message) {
|
|
1167
|
+
const compact = message.replace(/\s+/g, " ").trim();
|
|
1168
|
+
return compact.length > 500 ? `${compact.slice(0, 500)}...` : compact;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1126
1171
|
// src/errors.ts
|
|
1127
1172
|
var import_bootstrap = require("@tinycloud/bootstrap");
|
|
1128
1173
|
|
|
@@ -1282,6 +1327,30 @@ function networkError(service, message, cause) {
|
|
|
1282
1327
|
cause
|
|
1283
1328
|
};
|
|
1284
1329
|
}
|
|
1330
|
+
function nodeRetryError(service, message, response, errorText) {
|
|
1331
|
+
const body = errorText.trim();
|
|
1332
|
+
const [, token, stage] = /^([a-z_]+)(?::\s*([a-z_]+):)?/.exec(body) ?? [];
|
|
1333
|
+
let code;
|
|
1334
|
+
if (response.status === 503 && (token === "sql_busy" || token === "unavailable_retryable")) {
|
|
1335
|
+
code = ErrorCodes.NODE_BUSY;
|
|
1336
|
+
} else if (response.status === 503 && token === "request_timeout") {
|
|
1337
|
+
code = ErrorCodes.REQUEST_TIMEOUT;
|
|
1338
|
+
} else if (response.status === 500 && token === "outcome_unknown") {
|
|
1339
|
+
code = ErrorCodes.OUTCOME_UNKNOWN;
|
|
1340
|
+
} else {
|
|
1341
|
+
return void 0;
|
|
1342
|
+
}
|
|
1343
|
+
const retryable = code !== ErrorCodes.OUTCOME_UNKNOWN;
|
|
1344
|
+
const retryAfter = response.headers.get("retry-after")?.trim();
|
|
1345
|
+
return serviceError(code, message, service, {
|
|
1346
|
+
meta: {
|
|
1347
|
+
...responseErrorMeta(response.status, response.statusText, errorText),
|
|
1348
|
+
retryable,
|
|
1349
|
+
...token === "sql_busy" && stage ? { stage } : {},
|
|
1350
|
+
...retryable && retryAfter && /^\d{1,9}$/.test(retryAfter) ? { retryAfterMs: Number(retryAfter) * 1e3 } : {}
|
|
1351
|
+
}
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1285
1354
|
var RequestTimeoutError = class extends Error {
|
|
1286
1355
|
constructor(timeoutMs) {
|
|
1287
1356
|
super(`Request timed out after ${timeoutMs}ms.`);
|
|
@@ -2620,9 +2689,12 @@ var KVService = class extends BaseService {
|
|
|
2620
2689
|
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2621
2690
|
));
|
|
2622
2691
|
}
|
|
2692
|
+
const message = `Failed to get key ${JSON.stringify(key)}: ${response.status} - ${errorText}`;
|
|
2693
|
+
const retryError = nodeRetryError("kv", message, response, errorText);
|
|
2694
|
+
if (retryError) return err(retryError);
|
|
2623
2695
|
return err(serviceError(
|
|
2624
2696
|
ErrorCodes.NETWORK_ERROR,
|
|
2625
|
-
|
|
2697
|
+
message,
|
|
2626
2698
|
"kv",
|
|
2627
2699
|
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2628
2700
|
));
|
|
@@ -2704,19 +2776,14 @@ var KVService = class extends BaseService {
|
|
|
2704
2776
|
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2705
2777
|
));
|
|
2706
2778
|
}
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
`Concurrent KV update conflicted for key ${JSON.stringify(key)}`,
|
|
2711
|
-
"kv",
|
|
2712
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2713
|
-
));
|
|
2714
|
-
}
|
|
2779
|
+
const message = `Failed to put key ${JSON.stringify(key)}: ${response.status} - ${errorText}`;
|
|
2780
|
+
const retryError = nodeRetryError("kv", message, response, errorText);
|
|
2781
|
+
if (retryError) return err(retryError);
|
|
2715
2782
|
const quotaError = this.handleQuotaErrorResponse(response, errorText, key);
|
|
2716
2783
|
if (quotaError) return quotaError;
|
|
2717
2784
|
return err(serviceError(
|
|
2718
2785
|
ErrorCodes.KV_WRITE_FAILED,
|
|
2719
|
-
|
|
2786
|
+
message,
|
|
2720
2787
|
"kv",
|
|
2721
2788
|
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2722
2789
|
));
|
|
@@ -3016,9 +3083,12 @@ var KVService = class extends BaseService {
|
|
|
3016
3083
|
);
|
|
3017
3084
|
}
|
|
3018
3085
|
const errorText = await response.text();
|
|
3086
|
+
const message = `Failed to list keys: ${response.status} - ${errorText}`;
|
|
3087
|
+
const retryError = nodeRetryError("kv", message, response, errorText);
|
|
3088
|
+
if (retryError) return err(retryError);
|
|
3019
3089
|
return err(serviceError(
|
|
3020
3090
|
ErrorCodes.NETWORK_ERROR,
|
|
3021
|
-
|
|
3091
|
+
message,
|
|
3022
3092
|
"kv",
|
|
3023
3093
|
{ meta: { status: response.status, statusText: response.statusText } }
|
|
3024
3094
|
));
|
|
@@ -3269,17 +3339,12 @@ var KVService = class extends BaseService {
|
|
|
3269
3339
|
{ meta: { status: response.status, statusText: response.statusText } }
|
|
3270
3340
|
));
|
|
3271
3341
|
}
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
`Concurrent KV delete conflicted for key ${JSON.stringify(key)}`,
|
|
3276
|
-
"kv",
|
|
3277
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
3278
|
-
));
|
|
3279
|
-
}
|
|
3342
|
+
const message = `Failed to delete key ${JSON.stringify(key)}: ${response.status} - ${errorText}`;
|
|
3343
|
+
const retryError = nodeRetryError("kv", message, response, errorText);
|
|
3344
|
+
if (retryError) return err(retryError);
|
|
3280
3345
|
return err(serviceError(
|
|
3281
3346
|
ErrorCodes.NETWORK_ERROR,
|
|
3282
|
-
|
|
3347
|
+
message,
|
|
3283
3348
|
"kv",
|
|
3284
3349
|
{ meta: { status: response.status, statusText: response.statusText } }
|
|
3285
3350
|
));
|
|
@@ -3320,10 +3385,13 @@ var KVService = class extends BaseService {
|
|
|
3320
3385
|
return await this.classifyNotFound(response, key);
|
|
3321
3386
|
}
|
|
3322
3387
|
const errorText = await response.text();
|
|
3388
|
+
const message = `Failed to get metadata for key ${JSON.stringify(key)}: ${response.status} - ${errorText}`;
|
|
3389
|
+
const retryError = nodeRetryError("kv", message, response, errorText);
|
|
3390
|
+
if (retryError) return err(retryError);
|
|
3323
3391
|
return err(
|
|
3324
3392
|
serviceError(
|
|
3325
3393
|
ErrorCodes.NETWORK_ERROR,
|
|
3326
|
-
|
|
3394
|
+
message,
|
|
3327
3395
|
"kv",
|
|
3328
3396
|
{ meta: { status: response.status, statusText: response.statusText } }
|
|
3329
3397
|
)
|
|
@@ -3515,42 +3583,6 @@ function requiresSecretsOptIn(space, prefix) {
|
|
|
3515
3583
|
return prefix.split("/", 1)[0] === "vault";
|
|
3516
3584
|
}
|
|
3517
3585
|
|
|
3518
|
-
// src/responseErrors.ts
|
|
3519
|
-
function parseServiceErrorBody(errorText) {
|
|
3520
|
-
try {
|
|
3521
|
-
return JSON.parse(errorText);
|
|
3522
|
-
} catch {
|
|
3523
|
-
return {};
|
|
3524
|
-
}
|
|
3525
|
-
}
|
|
3526
|
-
function formatServiceResponseError(serviceLabel, operation, status, errorText, parsed) {
|
|
3527
|
-
if (parsed.message) {
|
|
3528
|
-
return compactMessage(parsed.message);
|
|
3529
|
-
}
|
|
3530
|
-
if (looksLikeHtml(errorText)) {
|
|
3531
|
-
if (status === 524 || /524\s*[:-]/i.test(errorText) || /a timeout occurred/i.test(errorText)) {
|
|
3532
|
-
return `${serviceLabel} ${operation} failed: upstream request timed out. Please retry.`;
|
|
3533
|
-
}
|
|
3534
|
-
return `${serviceLabel} ${operation} failed: upstream service returned an HTML error page (${status}).`;
|
|
3535
|
-
}
|
|
3536
|
-
return `${serviceLabel} ${operation} failed: ${status} - ${compactMessage(errorText)}`;
|
|
3537
|
-
}
|
|
3538
|
-
function responseErrorMeta(status, statusText, errorText) {
|
|
3539
|
-
const meta = { status, statusText };
|
|
3540
|
-
const snippet = compactMessage(errorText);
|
|
3541
|
-
if (snippet) {
|
|
3542
|
-
meta.responseSnippet = snippet.slice(0, 300);
|
|
3543
|
-
}
|
|
3544
|
-
return meta;
|
|
3545
|
-
}
|
|
3546
|
-
function looksLikeHtml(text) {
|
|
3547
|
-
return /<!doctype html/i.test(text) || /<html[\s>]/i.test(text);
|
|
3548
|
-
}
|
|
3549
|
-
function compactMessage(message) {
|
|
3550
|
-
const compact = message.replace(/\s+/g, " ").trim();
|
|
3551
|
-
return compact.length > 500 ? `${compact.slice(0, 500)}...` : compact;
|
|
3552
|
-
}
|
|
3553
|
-
|
|
3554
3586
|
// src/sql/SQLMigrations.ts
|
|
3555
3587
|
var SQLMigrations = class {
|
|
3556
3588
|
constructor(service, dbName) {
|
|
@@ -3966,6 +3998,8 @@ var SQLService = class extends BaseService {
|
|
|
3966
3998
|
if (response.status === 413 && parseStorageRejection(errorText)) {
|
|
3967
3999
|
return err(storageRejectionError("sql", 413, meta, errorText));
|
|
3968
4000
|
}
|
|
4001
|
+
const retryError = nodeRetryError("sql", `SQL ${operation} failed: ${response.status} - ${errorText}`, response, errorText);
|
|
4002
|
+
if (retryError) return err(retryError);
|
|
3969
4003
|
const errorBody = parseServiceErrorBody(errorText);
|
|
3970
4004
|
const errorCode = this.mapHttpStatusToErrorCode(
|
|
3971
4005
|
response.status,
|
|
@@ -4435,6 +4469,8 @@ var DuckDbService = class extends BaseService {
|
|
|
4435
4469
|
if (response.status === 413 && parseStorageRejection(errorText)) {
|
|
4436
4470
|
return err(storageRejectionError("duckdb", 413, meta, errorText));
|
|
4437
4471
|
}
|
|
4472
|
+
const retryError = nodeRetryError("duckdb", `DuckDB ${operation} failed: ${response.status} - ${errorText}`, response, errorText);
|
|
4473
|
+
if (retryError) return err(retryError);
|
|
4438
4474
|
const errorBody = parseServiceErrorBody(errorText);
|
|
4439
4475
|
const errorCode = this.mapHttpStatusToErrorCode(
|
|
4440
4476
|
response.status,
|
|
@@ -5642,6 +5678,116 @@ var TinyCloudQuota = class {
|
|
|
5642
5678
|
}
|
|
5643
5679
|
};
|
|
5644
5680
|
|
|
5681
|
+
// src/encryption/canonical.ts
|
|
5682
|
+
function canonicalize(value) {
|
|
5683
|
+
if (value === void 0) {
|
|
5684
|
+
return "";
|
|
5685
|
+
}
|
|
5686
|
+
return stringify(value);
|
|
5687
|
+
}
|
|
5688
|
+
function stringify(value) {
|
|
5689
|
+
if (value === null) return "null";
|
|
5690
|
+
switch (typeof value) {
|
|
5691
|
+
case "boolean":
|
|
5692
|
+
case "number":
|
|
5693
|
+
return JSON.stringify(value);
|
|
5694
|
+
case "string":
|
|
5695
|
+
return JSON.stringify(value);
|
|
5696
|
+
case "object": {
|
|
5697
|
+
if (Array.isArray(value)) {
|
|
5698
|
+
return `[${value.map(stringify).join(",")}]`;
|
|
5699
|
+
}
|
|
5700
|
+
const keys = Object.keys(value).sort();
|
|
5701
|
+
const parts = [];
|
|
5702
|
+
for (const k of keys) {
|
|
5703
|
+
const v = value[k];
|
|
5704
|
+
if (v === void 0) continue;
|
|
5705
|
+
parts.push(`${JSON.stringify(k)}:${stringify(v)}`);
|
|
5706
|
+
}
|
|
5707
|
+
return `{${parts.join(",")}}`;
|
|
5708
|
+
}
|
|
5709
|
+
default:
|
|
5710
|
+
throw new TypeError(
|
|
5711
|
+
`canonicalize: unsupported value type ${typeof value}`
|
|
5712
|
+
);
|
|
5713
|
+
}
|
|
5714
|
+
}
|
|
5715
|
+
var HEX = "0123456789abcdef";
|
|
5716
|
+
function hexEncode(bytes) {
|
|
5717
|
+
let out = "";
|
|
5718
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
5719
|
+
const b = bytes[i];
|
|
5720
|
+
out += HEX[b >> 4 & 15] + HEX[b & 15];
|
|
5721
|
+
}
|
|
5722
|
+
return out;
|
|
5723
|
+
}
|
|
5724
|
+
function hexDecode(hex) {
|
|
5725
|
+
if (hex.length % 2 !== 0) {
|
|
5726
|
+
throw new Error("hex string must have even length");
|
|
5727
|
+
}
|
|
5728
|
+
const out = new Uint8Array(hex.length / 2);
|
|
5729
|
+
for (let i = 0; i < out.length; i++) {
|
|
5730
|
+
const hi = parseInt(hex[i * 2], 16);
|
|
5731
|
+
const lo = parseInt(hex[i * 2 + 1], 16);
|
|
5732
|
+
if (Number.isNaN(hi) || Number.isNaN(lo)) {
|
|
5733
|
+
throw new Error("invalid hex character");
|
|
5734
|
+
}
|
|
5735
|
+
out[i] = hi << 4 | lo;
|
|
5736
|
+
}
|
|
5737
|
+
return out;
|
|
5738
|
+
}
|
|
5739
|
+
function base64Encode(bytes) {
|
|
5740
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
5741
|
+
let out = "";
|
|
5742
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
5743
|
+
const b0 = bytes[i];
|
|
5744
|
+
const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;
|
|
5745
|
+
const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;
|
|
5746
|
+
out += chars[b0 >> 2 & 63];
|
|
5747
|
+
out += chars[(b0 << 4 | b1 >> 4) & 63];
|
|
5748
|
+
out += i + 1 < bytes.length ? chars[(b1 << 2 | b2 >> 6) & 63] : "=";
|
|
5749
|
+
out += i + 2 < bytes.length ? chars[b2 & 63] : "=";
|
|
5750
|
+
}
|
|
5751
|
+
return out;
|
|
5752
|
+
}
|
|
5753
|
+
function base64Decode(s) {
|
|
5754
|
+
const clean = s.replace(/[^A-Za-z0-9+/=]/g, "");
|
|
5755
|
+
const len = clean.length;
|
|
5756
|
+
if (len % 4 !== 0) {
|
|
5757
|
+
throw new Error("invalid base64 input");
|
|
5758
|
+
}
|
|
5759
|
+
const padding = clean.endsWith("==") ? 2 : clean.endsWith("=") ? 1 : 0;
|
|
5760
|
+
const outLen = len / 4 * 3 - padding;
|
|
5761
|
+
const out = new Uint8Array(outLen);
|
|
5762
|
+
const lookup = {};
|
|
5763
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
5764
|
+
for (let i = 0; i < chars.length; i++) lookup[chars[i]] = i;
|
|
5765
|
+
let outIdx = 0;
|
|
5766
|
+
for (let i = 0; i < len; i += 4) {
|
|
5767
|
+
const v0 = lookup[clean[i]] ?? 0;
|
|
5768
|
+
const v1 = lookup[clean[i + 1]] ?? 0;
|
|
5769
|
+
const v2 = clean[i + 2] === "=" ? 0 : lookup[clean[i + 2]] ?? 0;
|
|
5770
|
+
const v3 = clean[i + 3] === "=" ? 0 : lookup[clean[i + 3]] ?? 0;
|
|
5771
|
+
const b0 = v0 << 2 | v1 >> 4;
|
|
5772
|
+
const b1 = (v1 & 15) << 4 | v2 >> 2;
|
|
5773
|
+
const b2 = (v2 & 3) << 6 | v3;
|
|
5774
|
+
if (outIdx < outLen) out[outIdx++] = b0;
|
|
5775
|
+
if (outIdx < outLen) out[outIdx++] = b1;
|
|
5776
|
+
if (outIdx < outLen) out[outIdx++] = b2;
|
|
5777
|
+
}
|
|
5778
|
+
return out;
|
|
5779
|
+
}
|
|
5780
|
+
function utf8Encode(s) {
|
|
5781
|
+
return new TextEncoder().encode(s);
|
|
5782
|
+
}
|
|
5783
|
+
function utf8Decode(b) {
|
|
5784
|
+
return new TextDecoder().decode(b);
|
|
5785
|
+
}
|
|
5786
|
+
function canonicalHashHex(sha256, value) {
|
|
5787
|
+
const canonical = canonicalize(value);
|
|
5788
|
+
return hexEncode(sha256(utf8Encode(canonical)));
|
|
5789
|
+
}
|
|
5790
|
+
|
|
5645
5791
|
// src/vault/types.ts
|
|
5646
5792
|
var VaultPublicSpaceKVActions = [
|
|
5647
5793
|
"tinycloud.kv/get",
|
|
@@ -5811,7 +5957,7 @@ function toBytes(str) {
|
|
|
5811
5957
|
function fromBytes(bytes) {
|
|
5812
5958
|
return new TextDecoder().decode(bytes);
|
|
5813
5959
|
}
|
|
5814
|
-
function
|
|
5960
|
+
function hexEncode2(bytes) {
|
|
5815
5961
|
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
5816
5962
|
}
|
|
5817
5963
|
function concatBytes(...arrays) {
|
|
@@ -5824,14 +5970,14 @@ function concatBytes(...arrays) {
|
|
|
5824
5970
|
}
|
|
5825
5971
|
return result;
|
|
5826
5972
|
}
|
|
5827
|
-
function
|
|
5973
|
+
function base64Encode2(bytes) {
|
|
5828
5974
|
let binary = "";
|
|
5829
5975
|
for (let i = 0; i < bytes.length; i++) {
|
|
5830
5976
|
binary += String.fromCharCode(bytes[i]);
|
|
5831
5977
|
}
|
|
5832
5978
|
return btoa(binary);
|
|
5833
5979
|
}
|
|
5834
|
-
function
|
|
5980
|
+
function base64Decode2(str) {
|
|
5835
5981
|
const binary = atob(str);
|
|
5836
5982
|
const bytes = new Uint8Array(binary.length);
|
|
5837
5983
|
for (let i = 0; i < binary.length; i++) {
|
|
@@ -5873,6 +6019,11 @@ function defaultVaultMessage(input) {
|
|
|
5873
6019
|
return input.message;
|
|
5874
6020
|
case "PERMISSION_DENIED":
|
|
5875
6021
|
return input.message;
|
|
6022
|
+
case "AAD_MISMATCH":
|
|
6023
|
+
case "ETAG_MISMATCH":
|
|
6024
|
+
case "INVALID_ETAG":
|
|
6025
|
+
case "OUTCOME_UNKNOWN":
|
|
6026
|
+
case "NODE_BUSY":
|
|
5876
6027
|
case "STORAGE_QUOTA_EXCEEDED":
|
|
5877
6028
|
case "STORAGE_LIMIT_REACHED":
|
|
5878
6029
|
return input.message;
|
|
@@ -5895,6 +6046,37 @@ function typedKvCause(error) {
|
|
|
5895
6046
|
...error.cause !== void 0 ? { cause: error.cause } : {}
|
|
5896
6047
|
});
|
|
5897
6048
|
}
|
|
6049
|
+
function canonicalAadSpaceId(spaceId) {
|
|
6050
|
+
return spaceId.replace(/0x[0-9a-fA-F]{40}/g, (address) => address.toLowerCase());
|
|
6051
|
+
}
|
|
6052
|
+
function checkEnvelopeAad(value, spaceId, key) {
|
|
6053
|
+
const e = value;
|
|
6054
|
+
if (typeof e !== "object" || e === null || Array.isArray(e) || !["networkId", "alg", "encryptedSymmetricKey", "encryptedSymmetricKeyHash", "ciphertext"].every(
|
|
6055
|
+
(field) => typeof e[field] === "string" && e[field] !== ""
|
|
6056
|
+
) || !Number.isInteger(e.keyVersion) || e.aad !== void 0 && typeof e.aad !== "string") {
|
|
6057
|
+
return "corrupt";
|
|
6058
|
+
}
|
|
6059
|
+
let aad = "";
|
|
6060
|
+
try {
|
|
6061
|
+
if (typeof e.aad === "string") aad = fromBytes(base64Decode(e.aad));
|
|
6062
|
+
} catch {
|
|
6063
|
+
}
|
|
6064
|
+
const suffix = `:${key}`;
|
|
6065
|
+
return aad.endsWith(suffix) && canonicalAadSpaceId(aad.slice(0, -suffix.length)) === canonicalAadSpaceId(`tinycloud.vault:${spaceId}`) ? "ok" : "mismatch";
|
|
6066
|
+
}
|
|
6067
|
+
var AMBIGUOUS_WRITE_STATUSES = [500, 502, 503, 504, 520, 522, 524];
|
|
6068
|
+
function ifMatchWriteError(key, error) {
|
|
6069
|
+
const status = error.meta?.status;
|
|
6070
|
+
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;
|
|
6071
|
+
if (code === void 0) return void 0;
|
|
6072
|
+
return vaultError({
|
|
6073
|
+
code,
|
|
6074
|
+
key,
|
|
6075
|
+
message: error.message,
|
|
6076
|
+
...error.meta !== void 0 ? { meta: error.meta } : {},
|
|
6077
|
+
cause: typedKvCause(error)
|
|
6078
|
+
});
|
|
6079
|
+
}
|
|
5898
6080
|
function kvWriteError(context, error) {
|
|
5899
6081
|
if (error.code === ErrorCodes.STORAGE_QUOTA_EXCEEDED || error.code === ErrorCodes.STORAGE_LIMIT_REACHED) {
|
|
5900
6082
|
return vaultError({ code: error.code, message: error.message, meta: error.meta });
|
|
@@ -6081,7 +6263,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6081
6263
|
}
|
|
6082
6264
|
if (existingPubKey) {
|
|
6083
6265
|
this.encryptionIdentity = {
|
|
6084
|
-
publicKey:
|
|
6266
|
+
publicKey: base64Decode2(existingPubKey),
|
|
6085
6267
|
privateKey: new Uint8Array(0)
|
|
6086
6268
|
// private key not available without signing
|
|
6087
6269
|
};
|
|
@@ -6106,7 +6288,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6106
6288
|
);
|
|
6107
6289
|
this.encryptionIdentity = this.crypto.x25519FromSeed(seed);
|
|
6108
6290
|
try {
|
|
6109
|
-
const pubKeyB64 =
|
|
6291
|
+
const pubKeyB64 = base64Encode2(this.encryptionIdentity.publicKey);
|
|
6110
6292
|
await this.tc.ensurePublicSpace();
|
|
6111
6293
|
await this.tc.publicKV.put(".well-known/vault-pubkey", pubKeyB64);
|
|
6112
6294
|
await this.tc.publicKV.put(".well-known/vault-version", CURRENT_VAULT_VERSION);
|
|
@@ -6172,6 +6354,21 @@ var DataVaultService = class extends BaseService {
|
|
|
6172
6354
|
message: "Authentication required"
|
|
6173
6355
|
});
|
|
6174
6356
|
}
|
|
6357
|
+
if (options?.ifMatch !== void 0) {
|
|
6358
|
+
if (options.ifNoneMatch !== void 0) {
|
|
6359
|
+
return vaultError({
|
|
6360
|
+
code: "STORAGE_ERROR",
|
|
6361
|
+
cause: new Error("ifMatch cannot be combined with ifNoneMatch")
|
|
6362
|
+
});
|
|
6363
|
+
}
|
|
6364
|
+
if (!/^"[^"\s]+"$/.test(options.ifMatch)) {
|
|
6365
|
+
return vaultError({
|
|
6366
|
+
code: "INVALID_ETAG",
|
|
6367
|
+
key,
|
|
6368
|
+
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"
|
|
6369
|
+
});
|
|
6370
|
+
}
|
|
6371
|
+
}
|
|
6175
6372
|
try {
|
|
6176
6373
|
const { plaintext, contentType } = this.serializeValue(value, options);
|
|
6177
6374
|
const metadata = {
|
|
@@ -6194,7 +6391,8 @@ var DataVaultService = class extends BaseService {
|
|
|
6194
6391
|
}
|
|
6195
6392
|
const valuePath = `vault/${key}`;
|
|
6196
6393
|
const valueBody = JSON.stringify(envelopeResult.data);
|
|
6197
|
-
const
|
|
6394
|
+
const putOptions = options?.ifMatch !== void 0 ? { ifMatch: options.ifMatch } : options?.ifNoneMatch !== void 0 ? { ifNoneMatch: options.ifNoneMatch } : void 0;
|
|
6395
|
+
const valuePutResult = putOptions !== void 0 ? await this.tc.kv.put(valuePath, valueBody, putOptions) : await this.tc.kv.put(valuePath, valueBody);
|
|
6198
6396
|
if (!valuePutResult.ok) {
|
|
6199
6397
|
if (options?.ifNoneMatch !== void 0 && valuePutResult.error.code === ErrorCodes.KV_PRECONDITION_FAILED) {
|
|
6200
6398
|
return vaultError({
|
|
@@ -6204,6 +6402,10 @@ var DataVaultService = class extends BaseService {
|
|
|
6204
6402
|
cause: typedKvCause(valuePutResult.error)
|
|
6205
6403
|
});
|
|
6206
6404
|
}
|
|
6405
|
+
if (options?.ifMatch !== void 0) {
|
|
6406
|
+
const classified = ifMatchWriteError(key, valuePutResult.error);
|
|
6407
|
+
if (classified !== void 0) return classified;
|
|
6408
|
+
}
|
|
6207
6409
|
return kvWriteError("Failed to store encrypted value", valuePutResult.error);
|
|
6208
6410
|
}
|
|
6209
6411
|
return { ok: true, data: void 0 };
|
|
@@ -6238,6 +6440,19 @@ var DataVaultService = class extends BaseService {
|
|
|
6238
6440
|
}
|
|
6239
6441
|
const rawEnvelope = unwrapKVData(valueResult.data);
|
|
6240
6442
|
const envelope = typeof rawEnvelope === "string" ? JSON.parse(rawEnvelope) : rawEnvelope;
|
|
6443
|
+
if (options?.expectAad === true) {
|
|
6444
|
+
const verdict = checkEnvelopeAad(envelope, this.vaultConfig.spaceId, key);
|
|
6445
|
+
if (verdict === "corrupt") {
|
|
6446
|
+
return vaultError({ code: "DECRYPTION_FAILED", message: "envelope is malformed" });
|
|
6447
|
+
}
|
|
6448
|
+
if (verdict === "mismatch") {
|
|
6449
|
+
return vaultError({
|
|
6450
|
+
code: "AAD_MISMATCH",
|
|
6451
|
+
key,
|
|
6452
|
+
message: `Envelope at ${key} is not bound to this space and key`
|
|
6453
|
+
});
|
|
6454
|
+
}
|
|
6455
|
+
}
|
|
6241
6456
|
const proof = await this.decryptCapabilityProof();
|
|
6242
6457
|
const plaintextResult = await config.service.decryptEnvelope(envelope, proof);
|
|
6243
6458
|
if (!plaintextResult.ok) {
|
|
@@ -6254,7 +6469,11 @@ var DataVaultService = class extends BaseService {
|
|
|
6254
6469
|
contentType,
|
|
6255
6470
|
options
|
|
6256
6471
|
);
|
|
6257
|
-
|
|
6472
|
+
const etag = options?.source === "network" ? valueResult.data.headers?.etag : void 0;
|
|
6473
|
+
return {
|
|
6474
|
+
ok: true,
|
|
6475
|
+
data: { value, metadata, keyId, ...etag === void 0 ? {} : { etag } }
|
|
6476
|
+
};
|
|
6258
6477
|
} catch (error) {
|
|
6259
6478
|
return vaultError({
|
|
6260
6479
|
code: "STORAGE_ERROR",
|
|
@@ -6300,6 +6519,12 @@ var DataVaultService = class extends BaseService {
|
|
|
6300
6519
|
} catch {
|
|
6301
6520
|
return { status: "corrupt_envelope" };
|
|
6302
6521
|
}
|
|
6522
|
+
if (options?.expectAad === true) {
|
|
6523
|
+
const verdict = checkEnvelopeAad(envelope, this.vaultConfig.spaceId, key);
|
|
6524
|
+
if (verdict !== "ok") {
|
|
6525
|
+
return { status: verdict === "corrupt" ? "corrupt_envelope" : "aad_mismatch" };
|
|
6526
|
+
}
|
|
6527
|
+
}
|
|
6303
6528
|
let plaintextResult;
|
|
6304
6529
|
try {
|
|
6305
6530
|
const proof = await this.decryptCapabilityProof();
|
|
@@ -6331,7 +6556,11 @@ var DataVaultService = class extends BaseService {
|
|
|
6331
6556
|
contentType,
|
|
6332
6557
|
options
|
|
6333
6558
|
);
|
|
6334
|
-
|
|
6559
|
+
const etag = valueResult.data.headers?.etag;
|
|
6560
|
+
return {
|
|
6561
|
+
status: "ok",
|
|
6562
|
+
entry: { value, metadata, keyId, ...etag === void 0 ? {} : { etag } }
|
|
6563
|
+
};
|
|
6335
6564
|
} catch {
|
|
6336
6565
|
return { status: "invalid_payload" };
|
|
6337
6566
|
}
|
|
@@ -6381,6 +6610,14 @@ var DataVaultService = class extends BaseService {
|
|
|
6381
6610
|
)
|
|
6382
6611
|
});
|
|
6383
6612
|
}
|
|
6613
|
+
if (options?.ifMatch !== void 0) {
|
|
6614
|
+
return vaultError({
|
|
6615
|
+
code: "STORAGE_ERROR",
|
|
6616
|
+
cause: new Error(
|
|
6617
|
+
"ifMatch is only supported by network-encrypted vaults"
|
|
6618
|
+
)
|
|
6619
|
+
});
|
|
6620
|
+
}
|
|
6384
6621
|
if (!this._isUnlocked || !this.masterKey) {
|
|
6385
6622
|
return vaultError({
|
|
6386
6623
|
code: "VAULT_LOCKED",
|
|
@@ -6406,7 +6643,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6406
6643
|
}
|
|
6407
6644
|
const contentType = options?.contentType ?? (value instanceof Uint8Array ? "application/octet-stream" : "application/json");
|
|
6408
6645
|
const entryKey = this.crypto.randomBytes(32);
|
|
6409
|
-
const keyId =
|
|
6646
|
+
const keyId = hexEncode2(this.crypto.sha256(entryKey)).slice(0, 16);
|
|
6410
6647
|
const encrypted = this.crypto.encrypt(entryKey, plaintext);
|
|
6411
6648
|
const keyBlob = this.crypto.encrypt(this.masterKey, entryKey);
|
|
6412
6649
|
const metadata = {
|
|
@@ -6424,7 +6661,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6424
6661
|
...metadata
|
|
6425
6662
|
});
|
|
6426
6663
|
const keyPayload = JSON.stringify({
|
|
6427
|
-
key:
|
|
6664
|
+
key: base64Encode2(keyBlob),
|
|
6428
6665
|
metadata: keyMetadata
|
|
6429
6666
|
});
|
|
6430
6667
|
const keyPutResult = await this.tc.kv.put(
|
|
@@ -6435,7 +6672,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6435
6672
|
return kvWriteError("Failed to store key blob", keyPutResult.error);
|
|
6436
6673
|
}
|
|
6437
6674
|
const valuePayload = JSON.stringify({
|
|
6438
|
-
data:
|
|
6675
|
+
data: base64Encode2(encrypted),
|
|
6439
6676
|
metadata
|
|
6440
6677
|
});
|
|
6441
6678
|
const valuePutResult = await this.tc.kv.put(
|
|
@@ -6466,6 +6703,12 @@ var DataVaultService = class extends BaseService {
|
|
|
6466
6703
|
if (this.usesNetworkEncryption) {
|
|
6467
6704
|
return this.getNetworkEncrypted(key, options);
|
|
6468
6705
|
}
|
|
6706
|
+
if (options?.expectAad === true) {
|
|
6707
|
+
return vaultError({
|
|
6708
|
+
code: "STORAGE_ERROR",
|
|
6709
|
+
cause: new Error("expectAad is only supported by network-encrypted vaults")
|
|
6710
|
+
});
|
|
6711
|
+
}
|
|
6469
6712
|
if (!this._isUnlocked || !this.masterKey) {
|
|
6470
6713
|
return vaultError({
|
|
6471
6714
|
code: "VAULT_LOCKED",
|
|
@@ -6487,7 +6730,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6487
6730
|
return vaultError({ code: "KEY_NOT_FOUND", key });
|
|
6488
6731
|
}
|
|
6489
6732
|
const keyEnvelope = JSON.parse(keyResult.data.data);
|
|
6490
|
-
const keyBlobBytes =
|
|
6733
|
+
const keyBlobBytes = base64Decode2(keyEnvelope.key);
|
|
6491
6734
|
const entryKey = this.crypto.decrypt(this.masterKey, keyBlobBytes);
|
|
6492
6735
|
const valueResult = await this.tc.kv.get(`vault/${key}`, {
|
|
6493
6736
|
raw: true,
|
|
@@ -6497,7 +6740,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6497
6740
|
return vaultError({ code: "KEY_NOT_FOUND", key });
|
|
6498
6741
|
}
|
|
6499
6742
|
const valueEnvelope = JSON.parse(valueResult.data.data);
|
|
6500
|
-
const encryptedBytes =
|
|
6743
|
+
const encryptedBytes = base64Decode2(valueEnvelope.data);
|
|
6501
6744
|
const plaintext = this.crypto.decrypt(entryKey, encryptedBytes);
|
|
6502
6745
|
const metadata = valueEnvelope.metadata ?? {};
|
|
6503
6746
|
const contentType = metadata[VaultHeaders.CONTENT_TYPE] ?? "application/json";
|
|
@@ -6745,7 +6988,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6745
6988
|
return vaultError({ code: "KEY_NOT_FOUND", key });
|
|
6746
6989
|
}
|
|
6747
6990
|
const keyEnvelope = JSON.parse(keyResult.data.data);
|
|
6748
|
-
const keyBlobBytes =
|
|
6991
|
+
const keyBlobBytes = base64Decode2(keyEnvelope.key);
|
|
6749
6992
|
const entryKey = this.crypto.decrypt(this.masterKey, keyBlobBytes);
|
|
6750
6993
|
const ephemeralSeed = this.crypto.randomBytes(32);
|
|
6751
6994
|
const ephemeralKeyPair = this.crypto.x25519FromSeed(ephemeralSeed);
|
|
@@ -6764,7 +7007,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6764
7007
|
encryptedGrant
|
|
6765
7008
|
);
|
|
6766
7009
|
const grantPayload = JSON.stringify({
|
|
6767
|
-
grant:
|
|
7010
|
+
grant: base64Encode2(grantBlob),
|
|
6768
7011
|
spaceId: this.vaultConfig.spaceId,
|
|
6769
7012
|
metadata: {
|
|
6770
7013
|
[VaultHeaders.GRANT_VERSION]: "1",
|
|
@@ -6804,6 +7047,14 @@ var DataVaultService = class extends BaseService {
|
|
|
6804
7047
|
*/
|
|
6805
7048
|
async getShared(grantorDID, key, options) {
|
|
6806
7049
|
return this.withTelemetry("getShared", key, async () => {
|
|
7050
|
+
if (options?.expectAad === true) {
|
|
7051
|
+
return vaultError({
|
|
7052
|
+
code: "STORAGE_ERROR",
|
|
7053
|
+
cause: new Error(
|
|
7054
|
+
"expectAad is not supported by getShared: the AAD binds the grantor's space, which this vault does not know"
|
|
7055
|
+
)
|
|
7056
|
+
});
|
|
7057
|
+
}
|
|
6807
7058
|
if (this.usesNetworkEncryption) {
|
|
6808
7059
|
const grantorKV = options?.kv;
|
|
6809
7060
|
if (!grantorKV) {
|
|
@@ -6898,7 +7149,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6898
7149
|
});
|
|
6899
7150
|
}
|
|
6900
7151
|
const grantEnvelope = typeof grantResult.data?.data === "string" ? JSON.parse(grantResult.data.data) : grantResult.data?.data;
|
|
6901
|
-
const grantBlobBytes =
|
|
7152
|
+
const grantBlobBytes = base64Decode2(grantEnvelope.grant);
|
|
6902
7153
|
const ephemeralPubKey = grantBlobBytes.slice(0, 32);
|
|
6903
7154
|
const encryptedGrant = grantBlobBytes.slice(32);
|
|
6904
7155
|
const sharedSecret = this.crypto.x25519Dh(
|
|
@@ -6921,7 +7172,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6921
7172
|
});
|
|
6922
7173
|
}
|
|
6923
7174
|
const valueEnvelope = typeof valueResult.data?.data === "string" ? JSON.parse(valueResult.data.data) : valueResult.data?.data;
|
|
6924
|
-
const encryptedBytes =
|
|
7175
|
+
const encryptedBytes = base64Decode2(valueEnvelope.data);
|
|
6925
7176
|
const plaintext = this.crypto.decrypt(entryKey, encryptedBytes);
|
|
6926
7177
|
const metadata = valueEnvelope.metadata ?? {};
|
|
6927
7178
|
const contentType = metadata[VaultHeaders.CONTENT_TYPE] ?? "application/json";
|
|
@@ -6975,7 +7226,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6975
7226
|
if (!result.ok) {
|
|
6976
7227
|
return vaultError({ code: "PUBLIC_KEY_NOT_FOUND", did });
|
|
6977
7228
|
}
|
|
6978
|
-
const pubKeyBytes =
|
|
7229
|
+
const pubKeyBytes = base64Decode2(result.data);
|
|
6979
7230
|
return { ok: true, data: pubKeyBytes };
|
|
6980
7231
|
} catch (error) {
|
|
6981
7232
|
return vaultError({ code: "PUBLIC_KEY_NOT_FOUND", did });
|
|
@@ -7094,7 +7345,7 @@ var DataVaultService = class extends BaseService {
|
|
|
7094
7345
|
}
|
|
7095
7346
|
const currentEntry = getResult.data;
|
|
7096
7347
|
const newEntryKey = this.crypto.randomBytes(32);
|
|
7097
|
-
const newKeyId =
|
|
7348
|
+
const newKeyId = hexEncode2(this.crypto.sha256(newEntryKey)).slice(
|
|
7098
7349
|
0,
|
|
7099
7350
|
16
|
|
7100
7351
|
);
|
|
@@ -7111,7 +7362,7 @@ var DataVaultService = class extends BaseService {
|
|
|
7111
7362
|
[VaultHeaders.KEY_ID]: newKeyId
|
|
7112
7363
|
};
|
|
7113
7364
|
const keyPayload = JSON.stringify({
|
|
7114
|
-
key:
|
|
7365
|
+
key: base64Encode2(newKeyBlob),
|
|
7115
7366
|
metadata: JSON.stringify({
|
|
7116
7367
|
keyId: newKeyId,
|
|
7117
7368
|
...metadata
|
|
@@ -7125,7 +7376,7 @@ var DataVaultService = class extends BaseService {
|
|
|
7125
7376
|
return kvWriteError("Failed to store rotated key blob", keyPutResult.error);
|
|
7126
7377
|
}
|
|
7127
7378
|
const valuePayload = JSON.stringify({
|
|
7128
|
-
data:
|
|
7379
|
+
data: base64Encode2(encrypted),
|
|
7129
7380
|
metadata
|
|
7130
7381
|
});
|
|
7131
7382
|
const valuePutResult = await this.tc.kv.put(
|
|
@@ -7417,116 +7668,6 @@ var SecretsService = class {
|
|
|
7417
7668
|
}
|
|
7418
7669
|
};
|
|
7419
7670
|
|
|
7420
|
-
// src/encryption/canonical.ts
|
|
7421
|
-
function canonicalize(value) {
|
|
7422
|
-
if (value === void 0) {
|
|
7423
|
-
return "";
|
|
7424
|
-
}
|
|
7425
|
-
return stringify(value);
|
|
7426
|
-
}
|
|
7427
|
-
function stringify(value) {
|
|
7428
|
-
if (value === null) return "null";
|
|
7429
|
-
switch (typeof value) {
|
|
7430
|
-
case "boolean":
|
|
7431
|
-
case "number":
|
|
7432
|
-
return JSON.stringify(value);
|
|
7433
|
-
case "string":
|
|
7434
|
-
return JSON.stringify(value);
|
|
7435
|
-
case "object": {
|
|
7436
|
-
if (Array.isArray(value)) {
|
|
7437
|
-
return `[${value.map(stringify).join(",")}]`;
|
|
7438
|
-
}
|
|
7439
|
-
const keys = Object.keys(value).sort();
|
|
7440
|
-
const parts = [];
|
|
7441
|
-
for (const k of keys) {
|
|
7442
|
-
const v = value[k];
|
|
7443
|
-
if (v === void 0) continue;
|
|
7444
|
-
parts.push(`${JSON.stringify(k)}:${stringify(v)}`);
|
|
7445
|
-
}
|
|
7446
|
-
return `{${parts.join(",")}}`;
|
|
7447
|
-
}
|
|
7448
|
-
default:
|
|
7449
|
-
throw new TypeError(
|
|
7450
|
-
`canonicalize: unsupported value type ${typeof value}`
|
|
7451
|
-
);
|
|
7452
|
-
}
|
|
7453
|
-
}
|
|
7454
|
-
var HEX = "0123456789abcdef";
|
|
7455
|
-
function hexEncode2(bytes) {
|
|
7456
|
-
let out = "";
|
|
7457
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
7458
|
-
const b = bytes[i];
|
|
7459
|
-
out += HEX[b >> 4 & 15] + HEX[b & 15];
|
|
7460
|
-
}
|
|
7461
|
-
return out;
|
|
7462
|
-
}
|
|
7463
|
-
function hexDecode(hex) {
|
|
7464
|
-
if (hex.length % 2 !== 0) {
|
|
7465
|
-
throw new Error("hex string must have even length");
|
|
7466
|
-
}
|
|
7467
|
-
const out = new Uint8Array(hex.length / 2);
|
|
7468
|
-
for (let i = 0; i < out.length; i++) {
|
|
7469
|
-
const hi = parseInt(hex[i * 2], 16);
|
|
7470
|
-
const lo = parseInt(hex[i * 2 + 1], 16);
|
|
7471
|
-
if (Number.isNaN(hi) || Number.isNaN(lo)) {
|
|
7472
|
-
throw new Error("invalid hex character");
|
|
7473
|
-
}
|
|
7474
|
-
out[i] = hi << 4 | lo;
|
|
7475
|
-
}
|
|
7476
|
-
return out;
|
|
7477
|
-
}
|
|
7478
|
-
function base64Encode2(bytes) {
|
|
7479
|
-
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
7480
|
-
let out = "";
|
|
7481
|
-
for (let i = 0; i < bytes.length; i += 3) {
|
|
7482
|
-
const b0 = bytes[i];
|
|
7483
|
-
const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;
|
|
7484
|
-
const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;
|
|
7485
|
-
out += chars[b0 >> 2 & 63];
|
|
7486
|
-
out += chars[(b0 << 4 | b1 >> 4) & 63];
|
|
7487
|
-
out += i + 1 < bytes.length ? chars[(b1 << 2 | b2 >> 6) & 63] : "=";
|
|
7488
|
-
out += i + 2 < bytes.length ? chars[b2 & 63] : "=";
|
|
7489
|
-
}
|
|
7490
|
-
return out;
|
|
7491
|
-
}
|
|
7492
|
-
function base64Decode2(s) {
|
|
7493
|
-
const clean = s.replace(/[^A-Za-z0-9+/=]/g, "");
|
|
7494
|
-
const len = clean.length;
|
|
7495
|
-
if (len % 4 !== 0) {
|
|
7496
|
-
throw new Error("invalid base64 input");
|
|
7497
|
-
}
|
|
7498
|
-
const padding = clean.endsWith("==") ? 2 : clean.endsWith("=") ? 1 : 0;
|
|
7499
|
-
const outLen = len / 4 * 3 - padding;
|
|
7500
|
-
const out = new Uint8Array(outLen);
|
|
7501
|
-
const lookup = {};
|
|
7502
|
-
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
7503
|
-
for (let i = 0; i < chars.length; i++) lookup[chars[i]] = i;
|
|
7504
|
-
let outIdx = 0;
|
|
7505
|
-
for (let i = 0; i < len; i += 4) {
|
|
7506
|
-
const v0 = lookup[clean[i]] ?? 0;
|
|
7507
|
-
const v1 = lookup[clean[i + 1]] ?? 0;
|
|
7508
|
-
const v2 = clean[i + 2] === "=" ? 0 : lookup[clean[i + 2]] ?? 0;
|
|
7509
|
-
const v3 = clean[i + 3] === "=" ? 0 : lookup[clean[i + 3]] ?? 0;
|
|
7510
|
-
const b0 = v0 << 2 | v1 >> 4;
|
|
7511
|
-
const b1 = (v1 & 15) << 4 | v2 >> 2;
|
|
7512
|
-
const b2 = (v2 & 3) << 6 | v3;
|
|
7513
|
-
if (outIdx < outLen) out[outIdx++] = b0;
|
|
7514
|
-
if (outIdx < outLen) out[outIdx++] = b1;
|
|
7515
|
-
if (outIdx < outLen) out[outIdx++] = b2;
|
|
7516
|
-
}
|
|
7517
|
-
return out;
|
|
7518
|
-
}
|
|
7519
|
-
function utf8Encode(s) {
|
|
7520
|
-
return new TextEncoder().encode(s);
|
|
7521
|
-
}
|
|
7522
|
-
function utf8Decode(b) {
|
|
7523
|
-
return new TextDecoder().decode(b);
|
|
7524
|
-
}
|
|
7525
|
-
function canonicalHashHex(sha256, value) {
|
|
7526
|
-
const canonical = canonicalize(value);
|
|
7527
|
-
return hexEncode2(sha256(utf8Encode(canonical)));
|
|
7528
|
-
}
|
|
7529
|
-
|
|
7530
7671
|
// src/encryption/types.ts
|
|
7531
7672
|
var DEFAULT_ENCRYPTION_ALG = "x25519-aes256gcm/v1";
|
|
7532
7673
|
var ENVELOPE_VERSION = 1;
|
|
@@ -7736,7 +7877,7 @@ function encryptToNetwork(crypto2, input) {
|
|
|
7736
7877
|
const symmetricKey = crypto2.randomBytes(32);
|
|
7737
7878
|
const ciphertext = crypto2.authEncrypt(symmetricKey, input.plaintext, input.aad);
|
|
7738
7879
|
const wrapped = crypto2.sealToNetworkKey(input.networkPublicKey, symmetricKey);
|
|
7739
|
-
const encryptedSymmetricKey =
|
|
7880
|
+
const encryptedSymmetricKey = base64Encode(wrapped);
|
|
7740
7881
|
const encryptedSymmetricKeyHash = canonicalHashHex(
|
|
7741
7882
|
crypto2.sha256,
|
|
7742
7883
|
encryptedSymmetricKey
|
|
@@ -7748,8 +7889,8 @@ function encryptToNetwork(crypto2, input) {
|
|
|
7748
7889
|
keyVersion,
|
|
7749
7890
|
encryptedSymmetricKey,
|
|
7750
7891
|
encryptedSymmetricKeyHash,
|
|
7751
|
-
ciphertext:
|
|
7752
|
-
...input.aad !== void 0 ? { aad:
|
|
7892
|
+
ciphertext: base64Encode(ciphertext),
|
|
7893
|
+
...input.aad !== void 0 ? { aad: base64Encode(input.aad) } : {},
|
|
7753
7894
|
...input.metadata !== void 0 ? { metadata: input.metadata } : {}
|
|
7754
7895
|
};
|
|
7755
7896
|
return { envelope, symmetricKey };
|
|
@@ -7823,8 +7964,8 @@ function validateEnvelope(crypto2, envelope) {
|
|
|
7823
7964
|
return { ok: true, data: e };
|
|
7824
7965
|
}
|
|
7825
7966
|
function decryptEnvelopeWithKey(crypto2, envelope, symmetricKey) {
|
|
7826
|
-
const ciphertext =
|
|
7827
|
-
const aad = envelope.aad !== void 0 ?
|
|
7967
|
+
const ciphertext = base64Decode(envelope.ciphertext);
|
|
7968
|
+
const aad = envelope.aad !== void 0 ? base64Decode(envelope.aad) : void 0;
|
|
7828
7969
|
return crypto2.authDecrypt(symmetricKey, ciphertext, aad);
|
|
7829
7970
|
}
|
|
7830
7971
|
|
|
@@ -7842,7 +7983,7 @@ function buildCanonicalDecryptRequest(input) {
|
|
|
7842
7983
|
return { canonicalBody, bodyHash, receiverPublicKeyHash };
|
|
7843
7984
|
}
|
|
7844
7985
|
function buildDecryptFacts(input) {
|
|
7845
|
-
const bodyHash = input.canonicalBody !== void 0 ?
|
|
7986
|
+
const bodyHash = input.canonicalBody !== void 0 ? hexEncode(input.crypto.sha256(utf8Encode(input.canonicalBody))) : canonicalHashHex(
|
|
7846
7987
|
input.crypto.sha256,
|
|
7847
7988
|
input.body
|
|
7848
7989
|
);
|
|
@@ -8125,7 +8266,7 @@ function verifyDecryptResponse(input) {
|
|
|
8125
8266
|
})
|
|
8126
8267
|
};
|
|
8127
8268
|
}
|
|
8128
|
-
const expectedRequestHash =
|
|
8269
|
+
const expectedRequestHash = hexEncode(
|
|
8129
8270
|
crypto2.sha256(utf8Encode(`${invocationCid}${requestBodyHash}`))
|
|
8130
8271
|
);
|
|
8131
8272
|
if (response.requestHash !== expectedRequestHash) {
|
|
@@ -8149,7 +8290,7 @@ function verifyDecryptResponse(input) {
|
|
|
8149
8290
|
const signedBytes = new TextEncoder().encode(
|
|
8150
8291
|
canonicalSignedResponse(response)
|
|
8151
8292
|
);
|
|
8152
|
-
const signatureBytes =
|
|
8293
|
+
const signatureBytes = base64Decode(response.nodeSignature);
|
|
8153
8294
|
if (!crypto2.verifyNodeSignature(response.nodeId, signedBytes, signatureBytes)) {
|
|
8154
8295
|
return {
|
|
8155
8296
|
ok: false,
|
|
@@ -8161,7 +8302,7 @@ function verifyDecryptResponse(input) {
|
|
|
8161
8302
|
return { ok: true, data: response };
|
|
8162
8303
|
}
|
|
8163
8304
|
function openWrappedKey(crypto2, receiverPrivateKey, response) {
|
|
8164
|
-
const wrapped =
|
|
8305
|
+
const wrapped = base64Decode(response.wrappedKey);
|
|
8165
8306
|
return crypto2.openWithReceiverKey(receiverPrivateKey, wrapped);
|
|
8166
8307
|
}
|
|
8167
8308
|
|
|
@@ -8205,7 +8346,7 @@ var EncryptionService = class extends BaseService {
|
|
|
8205
8346
|
const usable = ensureNetworkUsableForDecrypt(discovered.data);
|
|
8206
8347
|
if (!usable.ok) return usable;
|
|
8207
8348
|
const descriptor = usable.data;
|
|
8208
|
-
const networkPublicKey =
|
|
8349
|
+
const networkPublicKey = base64Decode(descriptor.publicEncryptionKey);
|
|
8209
8350
|
const result = encryptToNetwork(this.crypto, {
|
|
8210
8351
|
// Discovery returns the node's canonical network identifier. Persist
|
|
8211
8352
|
// that spelling in the envelope so a later response can be compared
|
|
@@ -8233,7 +8374,7 @@ var EncryptionService = class extends BaseService {
|
|
|
8233
8374
|
this.assertActive();
|
|
8234
8375
|
const validated = validateEnvelope(this.crypto, envelope);
|
|
8235
8376
|
if (!validated.ok) return validated;
|
|
8236
|
-
if (options?.aad !== void 0 && validated.data.aad !==
|
|
8377
|
+
if (options?.aad !== void 0 && validated.data.aad !== base64Encode(options.aad)) {
|
|
8237
8378
|
return encErr(
|
|
8238
8379
|
encryptionError({
|
|
8239
8380
|
code: "INVALID_INPUT",
|
|
@@ -8261,7 +8402,7 @@ var EncryptionService = class extends BaseService {
|
|
|
8261
8402
|
);
|
|
8262
8403
|
}
|
|
8263
8404
|
const receiverKey = generateRandomReceiverKey({ crypto: this.crypto });
|
|
8264
|
-
const receiverPublicKey =
|
|
8405
|
+
const receiverPublicKey = base64Encode(receiverKey.publicKey);
|
|
8265
8406
|
const receiverPublicKeyHash = canonicalHashHex(
|
|
8266
8407
|
this.crypto.sha256,
|
|
8267
8408
|
receiverPublicKey
|
|
@@ -8476,6 +8617,7 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
|
|
|
8476
8617
|
kvPrefixCovers,
|
|
8477
8618
|
networkDiscoveryKey,
|
|
8478
8619
|
networkError,
|
|
8620
|
+
nodeRetryError,
|
|
8479
8621
|
notFoundError,
|
|
8480
8622
|
ok,
|
|
8481
8623
|
openWrappedKey,
|