@tinycloud/sdk-core 2.2.0-beta.1 → 2.2.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/index.cjs +775 -57
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +204 -20
- package/dist/index.d.ts +204 -20
- package/dist/index.js +732 -33
- package/dist/index.js.map +1 -1
- package/package.json +9 -3
package/dist/index.js
CHANGED
|
@@ -154,6 +154,7 @@ var Space = class {
|
|
|
154
154
|
this._id = config.id;
|
|
155
155
|
this._name = config.name;
|
|
156
156
|
this._kv = config.createKV(config.id);
|
|
157
|
+
this._vault = config.createVault(config.id);
|
|
157
158
|
this._delegations = config.createDelegations(config.id);
|
|
158
159
|
this._sharing = config.createSharing(config.id);
|
|
159
160
|
this._getInfo = config.getInfo;
|
|
@@ -176,6 +177,12 @@ var Space = class {
|
|
|
176
177
|
get kv() {
|
|
177
178
|
return this._kv;
|
|
178
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* Data Vault operations scoped to this space.
|
|
182
|
+
*/
|
|
183
|
+
get vault() {
|
|
184
|
+
return this._vault;
|
|
185
|
+
}
|
|
179
186
|
/**
|
|
180
187
|
* Delegation operations scoped to this space.
|
|
181
188
|
*/
|
|
@@ -559,6 +566,8 @@ var SpaceConfigSchema = z4.object({
|
|
|
559
566
|
name: z4.string(),
|
|
560
567
|
/** Factory function to create a space-scoped KV service */
|
|
561
568
|
createKV: z4.function(),
|
|
569
|
+
/** Factory function to create a space-scoped Data Vault service */
|
|
570
|
+
createVault: z4.function(),
|
|
562
571
|
/** Factory function to create space-scoped delegations */
|
|
563
572
|
createDelegations: z4.function(),
|
|
564
573
|
/** Factory function to create space-scoped sharing */
|
|
@@ -579,6 +588,8 @@ var SpaceServiceConfigSchema = z4.object({
|
|
|
579
588
|
capabilityRegistry: z4.unknown().optional(),
|
|
580
589
|
/** Factory function to create a space-scoped KV service */
|
|
581
590
|
createKVService: z4.function().optional(),
|
|
591
|
+
/** Factory function to create a space-scoped Data Vault service */
|
|
592
|
+
createVaultService: z4.function().optional(),
|
|
582
593
|
/** User's PKH DID (derived from address or provided explicitly) */
|
|
583
594
|
userDid: z4.string().optional(),
|
|
584
595
|
/** Optional SharingService for v2 sharing links (client-side) */
|
|
@@ -717,6 +728,20 @@ function validateServerSpaceInfoResponse(data) {
|
|
|
717
728
|
return { ok: true, data: result.data };
|
|
718
729
|
}
|
|
719
730
|
|
|
731
|
+
// src/expiry.ts
|
|
732
|
+
var EPHEMERAL_MS = 60 * 60 * 1e3;
|
|
733
|
+
var SESSION_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
734
|
+
var SHARE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
735
|
+
var APP_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
736
|
+
var MAX_MS = 10 * 365 * 24 * 60 * 60 * 1e3;
|
|
737
|
+
var EXPIRY = {
|
|
738
|
+
EPHEMERAL_MS,
|
|
739
|
+
SESSION_MS,
|
|
740
|
+
SHARE_MS,
|
|
741
|
+
APP_MS,
|
|
742
|
+
MAX_MS
|
|
743
|
+
};
|
|
744
|
+
|
|
720
745
|
// src/spaces/SpaceService.ts
|
|
721
746
|
var SERVICE_NAME = "space";
|
|
722
747
|
var SpaceErrorCodes = {
|
|
@@ -793,7 +818,7 @@ function transformServerDelegations(validatedData, defaultSpaceId) {
|
|
|
793
818
|
spaceId,
|
|
794
819
|
path,
|
|
795
820
|
actions,
|
|
796
|
-
expiry: info.expiry ? new Date(info.expiry) : new Date(Date.now() +
|
|
821
|
+
expiry: info.expiry ? new Date(info.expiry) : new Date(Date.now() + EXPIRY.SHARE_MS),
|
|
797
822
|
isRevoked: false,
|
|
798
823
|
createdAt: info.issued_at ? new Date(info.issued_at) : void 0,
|
|
799
824
|
parentCid: firstStringParent
|
|
@@ -820,6 +845,7 @@ var SpaceService = class {
|
|
|
820
845
|
this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
821
846
|
this.capabilityRegistry = config.capabilityRegistry;
|
|
822
847
|
this.createKVServiceFn = config.createKVService;
|
|
848
|
+
this.createVaultServiceFn = config.createVaultService;
|
|
823
849
|
this._userDid = config.userDid;
|
|
824
850
|
this.sharingService = config.sharingService;
|
|
825
851
|
this.createDelegationFn = config.createDelegation;
|
|
@@ -834,6 +860,7 @@ var SpaceService = class {
|
|
|
834
860
|
if (config.fetch) this.fetchFn = config.fetch;
|
|
835
861
|
if (config.capabilityRegistry) this.capabilityRegistry = config.capabilityRegistry;
|
|
836
862
|
if (config.createKVService) this.createKVServiceFn = config.createKVService;
|
|
863
|
+
if (config.createVaultService) this.createVaultServiceFn = config.createVaultService;
|
|
837
864
|
if (config.userDid !== void 0) this._userDid = config.userDid;
|
|
838
865
|
if (config.sharingService) this.sharingService = config.sharingService;
|
|
839
866
|
if (config.createDelegation) this.createDelegationFn = config.createDelegation;
|
|
@@ -1116,6 +1143,7 @@ var SpaceService = class {
|
|
|
1116
1143
|
id: spaceId,
|
|
1117
1144
|
name,
|
|
1118
1145
|
createKV: this.createSpaceScopedKV.bind(this),
|
|
1146
|
+
createVault: this.createSpaceScopedVault.bind(this),
|
|
1119
1147
|
createDelegations: this.createSpaceScopedDelegations.bind(this),
|
|
1120
1148
|
createSharing: this.createSpaceScopedSharing.bind(this),
|
|
1121
1149
|
getInfo: this.getSpaceInfo.bind(this)
|
|
@@ -1245,6 +1273,21 @@ var SpaceService = class {
|
|
|
1245
1273
|
}
|
|
1246
1274
|
});
|
|
1247
1275
|
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Create a space-scoped Data Vault service.
|
|
1278
|
+
*/
|
|
1279
|
+
createSpaceScopedVault(spaceId) {
|
|
1280
|
+
if (this.createVaultServiceFn) {
|
|
1281
|
+
return this.createVaultServiceFn(spaceId);
|
|
1282
|
+
}
|
|
1283
|
+
return new Proxy({}, {
|
|
1284
|
+
get: () => {
|
|
1285
|
+
throw new Error(
|
|
1286
|
+
"Vault service factory not configured. Provide createVaultService in SpaceServiceConfig."
|
|
1287
|
+
);
|
|
1288
|
+
}
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1248
1291
|
/**
|
|
1249
1292
|
* Create space-scoped delegation operations.
|
|
1250
1293
|
*/
|
|
@@ -1989,7 +2032,11 @@ import {
|
|
|
1989
2032
|
DataVaultService,
|
|
1990
2033
|
VaultHeaders,
|
|
1991
2034
|
VaultPublicSpaceKVActions,
|
|
1992
|
-
createVaultCrypto
|
|
2035
|
+
createVaultCrypto,
|
|
2036
|
+
SecretsService,
|
|
2037
|
+
SECRET_NAME_RE as SECRET_NAME_RE2,
|
|
2038
|
+
canonicalizeSecretScope,
|
|
2039
|
+
resolveSecretPath as resolveSecretPath2
|
|
1993
2040
|
} from "@tinycloud/sdk-services";
|
|
1994
2041
|
|
|
1995
2042
|
// src/space.ts
|
|
@@ -2179,7 +2226,7 @@ var DelegationManager = class {
|
|
|
2179
2226
|
spaceId: this.session.spaceId,
|
|
2180
2227
|
path: params.path,
|
|
2181
2228
|
actions: params.actions,
|
|
2182
|
-
expiry: params.expiry ?? new Date(Date.now() +
|
|
2229
|
+
expiry: params.expiry ?? new Date(Date.now() + EXPIRY.SHARE_MS),
|
|
2183
2230
|
isRevoked: false,
|
|
2184
2231
|
allowSubDelegation: !(params.disableSubDelegation ?? false),
|
|
2185
2232
|
createdAt: /* @__PURE__ */ new Date()
|
|
@@ -2658,6 +2705,7 @@ function validateEncodedShareData(data) {
|
|
|
2658
2705
|
|
|
2659
2706
|
// src/manifest.ts
|
|
2660
2707
|
import ms from "ms";
|
|
2708
|
+
import { resolveSecretPath, SECRET_NAME_RE } from "@tinycloud/sdk-services";
|
|
2661
2709
|
var ManifestValidationError = class extends Error {
|
|
2662
2710
|
constructor(message) {
|
|
2663
2711
|
super(`Manifest validation failed: ${message}`);
|
|
@@ -2670,6 +2718,8 @@ var DEFAULT_MANIFEST_VERSION = 1;
|
|
|
2670
2718
|
var DEFAULT_MANIFEST_SPACE = "applications";
|
|
2671
2719
|
var ACCOUNT_REGISTRY_SPACE = "account";
|
|
2672
2720
|
var ACCOUNT_REGISTRY_PATH = "applications/";
|
|
2721
|
+
var SECRETS_SPACE = "secrets";
|
|
2722
|
+
var VAULT_PERMISSION_SERVICE = "tinycloud.vault";
|
|
2673
2723
|
var SERVICE_SHORT_TO_LONG = Object.freeze({
|
|
2674
2724
|
kv: "tinycloud.kv",
|
|
2675
2725
|
sql: "tinycloud.sql",
|
|
@@ -2694,12 +2744,6 @@ var DEFAULT_STANDARD_ENTRIES = [
|
|
|
2694
2744
|
space: DEFAULT_MANIFEST_SPACE,
|
|
2695
2745
|
path: "/",
|
|
2696
2746
|
actions: ["read", "write"]
|
|
2697
|
-
},
|
|
2698
|
-
{
|
|
2699
|
-
service: "tinycloud.capabilities",
|
|
2700
|
-
space: DEFAULT_MANIFEST_SPACE,
|
|
2701
|
-
path: "/",
|
|
2702
|
-
actions: ["read"]
|
|
2703
2747
|
}
|
|
2704
2748
|
];
|
|
2705
2749
|
var DEFAULT_ADMIN_ENTRIES = [
|
|
@@ -2714,12 +2758,6 @@ var DEFAULT_ADMIN_ENTRIES = [
|
|
|
2714
2758
|
space: DEFAULT_MANIFEST_SPACE,
|
|
2715
2759
|
path: "/",
|
|
2716
2760
|
actions: ["read", "write", "ddl"]
|
|
2717
|
-
},
|
|
2718
|
-
{
|
|
2719
|
-
service: "tinycloud.capabilities",
|
|
2720
|
-
space: DEFAULT_MANIFEST_SPACE,
|
|
2721
|
-
path: "/",
|
|
2722
|
-
actions: ["read", "admin"]
|
|
2723
2761
|
}
|
|
2724
2762
|
];
|
|
2725
2763
|
var DEFAULT_ALL_ENTRIES = [
|
|
@@ -2740,12 +2778,6 @@ var DEFAULT_ALL_ENTRIES = [
|
|
|
2740
2778
|
space: DEFAULT_MANIFEST_SPACE,
|
|
2741
2779
|
path: "/",
|
|
2742
2780
|
actions: ["read", "write"]
|
|
2743
|
-
},
|
|
2744
|
-
{
|
|
2745
|
-
service: "tinycloud.capabilities",
|
|
2746
|
-
space: DEFAULT_MANIFEST_SPACE,
|
|
2747
|
-
path: "/",
|
|
2748
|
-
actions: ["read", "admin"]
|
|
2749
2781
|
}
|
|
2750
2782
|
];
|
|
2751
2783
|
function parseExpiry(duration) {
|
|
@@ -2770,6 +2802,20 @@ function expandActionShortNames(service, actions) {
|
|
|
2770
2802
|
return `${service}/${a}`;
|
|
2771
2803
|
});
|
|
2772
2804
|
}
|
|
2805
|
+
function expandPermissionEntry(entry) {
|
|
2806
|
+
if (entry.service !== VAULT_PERMISSION_SERVICE) {
|
|
2807
|
+
return [
|
|
2808
|
+
{
|
|
2809
|
+
...entry,
|
|
2810
|
+
actions: expandActionShortNames(entry.service, entry.actions)
|
|
2811
|
+
}
|
|
2812
|
+
];
|
|
2813
|
+
}
|
|
2814
|
+
return expandVaultPermissionEntry(entry);
|
|
2815
|
+
}
|
|
2816
|
+
function expandPermissionEntries(entries) {
|
|
2817
|
+
return entries.flatMap(expandPermissionEntry);
|
|
2818
|
+
}
|
|
2773
2819
|
function applyPrefix(prefix, path, skipPrefix) {
|
|
2774
2820
|
if (skipPrefix) {
|
|
2775
2821
|
return path;
|
|
@@ -2841,8 +2887,49 @@ function validateManifest(input) {
|
|
|
2841
2887
|
(p, i) => validatePermissionEntry(p, `permissions[${i}]`)
|
|
2842
2888
|
);
|
|
2843
2889
|
}
|
|
2890
|
+
if (m.secrets !== void 0) {
|
|
2891
|
+
validateManifestSecrets(m.secrets);
|
|
2892
|
+
}
|
|
2844
2893
|
return m;
|
|
2845
2894
|
}
|
|
2895
|
+
function validateManifestSecrets(secrets) {
|
|
2896
|
+
if (secrets === null || typeof secrets !== "object" || Array.isArray(secrets)) {
|
|
2897
|
+
throw new ManifestValidationError("manifest.secrets must be an object");
|
|
2898
|
+
}
|
|
2899
|
+
for (const [name, spec] of Object.entries(secrets)) {
|
|
2900
|
+
if (!SECRET_NAME_RE.test(name)) {
|
|
2901
|
+
throw new ManifestValidationError(
|
|
2902
|
+
`manifest.secrets.${name} must match ${SECRET_NAME_RE.source}`
|
|
2903
|
+
);
|
|
2904
|
+
}
|
|
2905
|
+
try {
|
|
2906
|
+
resolveSecretPath(
|
|
2907
|
+
secretNameFromSpec(name, spec),
|
|
2908
|
+
{ scope: secretScopeFromSpec(spec) }
|
|
2909
|
+
);
|
|
2910
|
+
} catch (error) {
|
|
2911
|
+
throw new ManifestValidationError(
|
|
2912
|
+
`manifest.secrets.${name}: ${error instanceof Error ? error.message : String(error)}`
|
|
2913
|
+
);
|
|
2914
|
+
}
|
|
2915
|
+
const actions = secretActionsFromSpec(name, spec);
|
|
2916
|
+
if (actions.length === 0) {
|
|
2917
|
+
throw new ManifestValidationError(
|
|
2918
|
+
`manifest.secrets.${name} actions must be non-empty`
|
|
2919
|
+
);
|
|
2920
|
+
}
|
|
2921
|
+
for (const action of actions) {
|
|
2922
|
+
if (typeof action !== "string" || action.length === 0) {
|
|
2923
|
+
throw new ManifestValidationError(
|
|
2924
|
+
`manifest.secrets.${name} actions must be non-empty strings`
|
|
2925
|
+
);
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
if (spec !== null && typeof spec === "object" && !Array.isArray(spec) && spec.expiry !== void 0) {
|
|
2929
|
+
parseExpiry(spec.expiry);
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2846
2933
|
function validatePermissionEntry(p, path) {
|
|
2847
2934
|
if (p === null || typeof p !== "object") {
|
|
2848
2935
|
throw new ManifestValidationError(`${path} must be an object`);
|
|
@@ -2866,6 +2953,16 @@ function validatePermissionEntry(p, path) {
|
|
|
2866
2953
|
`${path}.actions must be a non-empty array`
|
|
2867
2954
|
);
|
|
2868
2955
|
}
|
|
2956
|
+
for (const action of entry.actions) {
|
|
2957
|
+
if (typeof action !== "string" || action.length === 0) {
|
|
2958
|
+
throw new ManifestValidationError(
|
|
2959
|
+
`${path}.actions must contain non-empty strings`
|
|
2960
|
+
);
|
|
2961
|
+
}
|
|
2962
|
+
if (entry.service === VAULT_PERMISSION_SERVICE) {
|
|
2963
|
+
vaultActionExpansion(action);
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2869
2966
|
if (entry.expiry !== void 0) {
|
|
2870
2967
|
parseExpiry(entry.expiry);
|
|
2871
2968
|
}
|
|
@@ -2895,7 +2992,8 @@ function defaultEntriesForTier(tier) {
|
|
|
2895
2992
|
service: e.service,
|
|
2896
2993
|
space: e.space,
|
|
2897
2994
|
path: e.path,
|
|
2898
|
-
actions: [...e.actions]
|
|
2995
|
+
actions: [...e.actions],
|
|
2996
|
+
...e.skipPrefix !== void 0 ? { skipPrefix: e.skipPrefix } : {}
|
|
2899
2997
|
}));
|
|
2900
2998
|
}
|
|
2901
2999
|
function resolveManifest(input) {
|
|
@@ -2907,9 +3005,14 @@ function resolveManifest(input) {
|
|
|
2907
3005
|
const tier = normalizeDefaults(manifest.defaults);
|
|
2908
3006
|
const defaultEntries = defaultEntriesForTier(tier);
|
|
2909
3007
|
const explicitEntries = manifest.permissions ?? [];
|
|
2910
|
-
const
|
|
2911
|
-
const
|
|
2912
|
-
|
|
3008
|
+
const secretEntries = secretEntriesForManifest(manifest.secrets);
|
|
3009
|
+
const allEntries = [
|
|
3010
|
+
...defaultEntries,
|
|
3011
|
+
...explicitEntries,
|
|
3012
|
+
...secretEntries
|
|
3013
|
+
];
|
|
3014
|
+
const resources = withCapabilitiesReadForSpaces(
|
|
3015
|
+
allEntries.flatMap((entry) => resolveEntry(entry, prefix, expiryMs, space))
|
|
2913
3016
|
);
|
|
2914
3017
|
const additionalDelegates = manifest.did === void 0 ? [] : [
|
|
2915
3018
|
{
|
|
@@ -2929,25 +3032,191 @@ function resolveManifest(input) {
|
|
|
2929
3032
|
additionalDelegates
|
|
2930
3033
|
};
|
|
2931
3034
|
}
|
|
3035
|
+
function normalizeSecretActions(actions) {
|
|
3036
|
+
const out = [];
|
|
3037
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3038
|
+
const add = (action) => {
|
|
3039
|
+
if (!seen.has(action)) {
|
|
3040
|
+
out.push(action);
|
|
3041
|
+
seen.add(action);
|
|
3042
|
+
}
|
|
3043
|
+
};
|
|
3044
|
+
for (const action of actions) {
|
|
3045
|
+
if (action === "read") {
|
|
3046
|
+
add("get");
|
|
3047
|
+
continue;
|
|
3048
|
+
}
|
|
3049
|
+
if (action === "write") {
|
|
3050
|
+
add("put");
|
|
3051
|
+
continue;
|
|
3052
|
+
}
|
|
3053
|
+
if (action === "delete") {
|
|
3054
|
+
add("del");
|
|
3055
|
+
continue;
|
|
3056
|
+
}
|
|
3057
|
+
if (action === "get" || action === "put" || action === "del" || action === "list" || action === "metadata") {
|
|
3058
|
+
add(action);
|
|
3059
|
+
continue;
|
|
3060
|
+
}
|
|
3061
|
+
if (action === "tinycloud.kv/get" || action === "tinycloud.kv/put" || action === "tinycloud.kv/del" || action === "tinycloud.kv/list" || action === "tinycloud.kv/metadata") {
|
|
3062
|
+
add(action);
|
|
3063
|
+
continue;
|
|
3064
|
+
}
|
|
3065
|
+
throw new ManifestValidationError(
|
|
3066
|
+
`unknown secret action ${JSON.stringify(action)}; expected read, write, delete, list, or metadata`
|
|
3067
|
+
);
|
|
3068
|
+
}
|
|
3069
|
+
return out;
|
|
3070
|
+
}
|
|
3071
|
+
function secretNameFromSpec(fallbackName, spec) {
|
|
3072
|
+
if (spec !== null && typeof spec === "object" && !Array.isArray(spec)) {
|
|
3073
|
+
return spec.name ?? fallbackName;
|
|
3074
|
+
}
|
|
3075
|
+
return fallbackName;
|
|
3076
|
+
}
|
|
3077
|
+
function secretScopeFromSpec(spec) {
|
|
3078
|
+
if (spec !== null && typeof spec === "object" && !Array.isArray(spec)) {
|
|
3079
|
+
return spec.scope;
|
|
3080
|
+
}
|
|
3081
|
+
return void 0;
|
|
3082
|
+
}
|
|
3083
|
+
function secretActionsFromSpec(name, spec) {
|
|
3084
|
+
if (spec === true) {
|
|
3085
|
+
return ["read"];
|
|
3086
|
+
}
|
|
3087
|
+
if (typeof spec === "string") {
|
|
3088
|
+
return [spec];
|
|
3089
|
+
}
|
|
3090
|
+
if (Array.isArray(spec)) {
|
|
3091
|
+
return spec;
|
|
3092
|
+
}
|
|
3093
|
+
if (spec === null || typeof spec !== "object") {
|
|
3094
|
+
throw new ManifestValidationError(
|
|
3095
|
+
`manifest.secrets.${name} must be true, a string action, an actions array, or an object`
|
|
3096
|
+
);
|
|
3097
|
+
}
|
|
3098
|
+
if (spec.actions === void 0) {
|
|
3099
|
+
return ["read"];
|
|
3100
|
+
}
|
|
3101
|
+
if (typeof spec.actions === "string") {
|
|
3102
|
+
return [spec.actions];
|
|
3103
|
+
}
|
|
3104
|
+
if (Array.isArray(spec.actions)) {
|
|
3105
|
+
return spec.actions;
|
|
3106
|
+
}
|
|
3107
|
+
throw new ManifestValidationError(
|
|
3108
|
+
`manifest.secrets.${name}.actions must be a string or array`
|
|
3109
|
+
);
|
|
3110
|
+
}
|
|
3111
|
+
function secretEntriesForManifest(secrets) {
|
|
3112
|
+
if (secrets === void 0) {
|
|
3113
|
+
return [];
|
|
3114
|
+
}
|
|
3115
|
+
const entries = [];
|
|
3116
|
+
for (const [name, spec] of Object.entries(secrets)) {
|
|
3117
|
+
const actions = secretActionsFromSpec(name, spec);
|
|
3118
|
+
const secretPath = resolveSecretPath(
|
|
3119
|
+
secretNameFromSpec(name, spec),
|
|
3120
|
+
{ scope: secretScopeFromSpec(spec) }
|
|
3121
|
+
);
|
|
3122
|
+
const extra = spec !== true && typeof spec === "object" && !Array.isArray(spec) ? spec : {};
|
|
3123
|
+
entries.push({
|
|
3124
|
+
service: VAULT_PERMISSION_SERVICE,
|
|
3125
|
+
space: SECRETS_SPACE,
|
|
3126
|
+
path: secretPath.vaultKey,
|
|
3127
|
+
actions: normalizeSecretActions(actions),
|
|
3128
|
+
skipPrefix: true,
|
|
3129
|
+
...extra.expiry !== void 0 ? { expiry: extra.expiry } : {},
|
|
3130
|
+
...extra.description !== void 0 ? { description: extra.description } : {}
|
|
3131
|
+
});
|
|
3132
|
+
}
|
|
3133
|
+
return entries;
|
|
3134
|
+
}
|
|
2932
3135
|
function resolveEntry(entry, prefix, _inheritedExpiryMs, inheritedSpace) {
|
|
2933
3136
|
const resolvedPath = applyPrefix(
|
|
2934
3137
|
prefix,
|
|
2935
3138
|
entry.path,
|
|
2936
3139
|
entry.skipPrefix === true
|
|
2937
3140
|
);
|
|
2938
|
-
const resolvedActions = expandActionShortNames(entry.service, entry.actions);
|
|
2939
3141
|
const entryExpiryMs = entry.expiry !== void 0 ? parseExpiry(entry.expiry) : void 0;
|
|
2940
|
-
return {
|
|
2941
|
-
|
|
3142
|
+
return expandPermissionEntry({
|
|
3143
|
+
...entry,
|
|
2942
3144
|
space: entry.space ?? inheritedSpace,
|
|
2943
3145
|
path: resolvedPath,
|
|
2944
|
-
|
|
3146
|
+
skipPrefix: true
|
|
3147
|
+
}).map((expanded) => ({
|
|
3148
|
+
service: expanded.service,
|
|
3149
|
+
space: expanded.space ?? inheritedSpace,
|
|
3150
|
+
path: expanded.path,
|
|
3151
|
+
actions: expanded.actions,
|
|
2945
3152
|
// Only populate `expiryMs` when the entry had its own expiry override.
|
|
2946
3153
|
// When absent, callers use the parent (delegation or manifest) expiry
|
|
2947
3154
|
// which is carried on ResolvedDelegate.expiryMs / ResolvedCapabilities.expiryMs.
|
|
2948
3155
|
...entryExpiryMs !== void 0 ? { expiryMs: entryExpiryMs } : {},
|
|
2949
3156
|
...entry.description !== void 0 ? { description: entry.description } : {}
|
|
2950
|
-
};
|
|
3157
|
+
}));
|
|
3158
|
+
}
|
|
3159
|
+
function expandVaultPermissionEntry(entry) {
|
|
3160
|
+
const byBase = /* @__PURE__ */ new Map();
|
|
3161
|
+
for (const action of entry.actions) {
|
|
3162
|
+
const expansion = vaultActionExpansion(action);
|
|
3163
|
+
for (const base of expansion.bases) {
|
|
3164
|
+
const actions = byBase.get(base) ?? [];
|
|
3165
|
+
if (!actions.includes(expansion.action)) {
|
|
3166
|
+
actions.push(expansion.action);
|
|
3167
|
+
}
|
|
3168
|
+
byBase.set(base, actions);
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
return [...byBase.entries()].map(([base, actions]) => ({
|
|
3172
|
+
...entry,
|
|
3173
|
+
service: "tinycloud.kv",
|
|
3174
|
+
path: vaultKVPath(base, entry.path),
|
|
3175
|
+
actions,
|
|
3176
|
+
skipPrefix: true
|
|
3177
|
+
}));
|
|
3178
|
+
}
|
|
3179
|
+
function vaultActionExpansion(action) {
|
|
3180
|
+
const normalized = normalizeVaultAction(action);
|
|
3181
|
+
if (normalized === "read" || normalized === "get") {
|
|
3182
|
+
return { bases: ["keys", "vault"], action: "tinycloud.kv/get" };
|
|
3183
|
+
}
|
|
3184
|
+
if (normalized === "write" || normalized === "put") {
|
|
3185
|
+
return { bases: ["keys", "vault"], action: "tinycloud.kv/put" };
|
|
3186
|
+
}
|
|
3187
|
+
if (normalized === "delete" || normalized === "del") {
|
|
3188
|
+
return { bases: ["keys", "vault"], action: "tinycloud.kv/del" };
|
|
3189
|
+
}
|
|
3190
|
+
if (normalized === "list") {
|
|
3191
|
+
return { bases: ["vault"], action: "tinycloud.kv/list" };
|
|
3192
|
+
}
|
|
3193
|
+
if (normalized === "head") {
|
|
3194
|
+
return { bases: ["vault"], action: "tinycloud.kv/get" };
|
|
3195
|
+
}
|
|
3196
|
+
if (normalized === "metadata") {
|
|
3197
|
+
return { bases: ["vault"], action: "tinycloud.kv/metadata" };
|
|
3198
|
+
}
|
|
3199
|
+
throw new ManifestValidationError(
|
|
3200
|
+
`unknown vault action ${JSON.stringify(action)}; expected read, write, delete, get, put, del, list, head, or metadata`
|
|
3201
|
+
);
|
|
3202
|
+
}
|
|
3203
|
+
function normalizeVaultAction(action) {
|
|
3204
|
+
if (action.startsWith(`${VAULT_PERMISSION_SERVICE}/`)) {
|
|
3205
|
+
return action.slice(`${VAULT_PERMISSION_SERVICE}/`.length);
|
|
3206
|
+
}
|
|
3207
|
+
if (action.startsWith("tinycloud.kv/")) {
|
|
3208
|
+
return action.slice("tinycloud.kv/".length);
|
|
3209
|
+
}
|
|
3210
|
+
if (action.includes("/")) {
|
|
3211
|
+
throw new ManifestValidationError(
|
|
3212
|
+
`unknown vault action ${JSON.stringify(action)}; expected a tinycloud.vault or tinycloud.kv action`
|
|
3213
|
+
);
|
|
3214
|
+
}
|
|
3215
|
+
return action;
|
|
3216
|
+
}
|
|
3217
|
+
function vaultKVPath(base, path) {
|
|
3218
|
+
const normalized = path.startsWith("/") ? path.slice(1) : path;
|
|
3219
|
+
return `${base}/${normalized}`;
|
|
2951
3220
|
}
|
|
2952
3221
|
function cloneResourceCapability(entry) {
|
|
2953
3222
|
return {
|
|
@@ -2992,6 +3261,24 @@ function dedupeResources(resources) {
|
|
|
2992
3261
|
}
|
|
2993
3262
|
return [...byKey.values()];
|
|
2994
3263
|
}
|
|
3264
|
+
function capabilitiesReadPermission(space) {
|
|
3265
|
+
return {
|
|
3266
|
+
service: "tinycloud.capabilities",
|
|
3267
|
+
space,
|
|
3268
|
+
path: "",
|
|
3269
|
+
actions: ["tinycloud.capabilities/read"]
|
|
3270
|
+
};
|
|
3271
|
+
}
|
|
3272
|
+
function withCapabilitiesReadForSpaces(resources) {
|
|
3273
|
+
if (resources.length === 0) {
|
|
3274
|
+
return [];
|
|
3275
|
+
}
|
|
3276
|
+
const spaces = new Set(resources.map((resource) => resource.space));
|
|
3277
|
+
return dedupeResources([
|
|
3278
|
+
...resources,
|
|
3279
|
+
...[...spaces].map(capabilitiesReadPermission)
|
|
3280
|
+
]);
|
|
3281
|
+
}
|
|
2995
3282
|
function accountRegistryPermission() {
|
|
2996
3283
|
return {
|
|
2997
3284
|
service: "tinycloud.kv",
|
|
@@ -3019,6 +3306,7 @@ function composeManifestRequest(inputs, options = {}) {
|
|
|
3019
3306
|
if (includeAccountRegistryPermissions) {
|
|
3020
3307
|
resources.push(accountRegistryPermission());
|
|
3021
3308
|
}
|
|
3309
|
+
const resourcesWithImplicitCapabilities = withCapabilitiesReadForSpaces(resources);
|
|
3022
3310
|
const manifestsByAppId = /* @__PURE__ */ new Map();
|
|
3023
3311
|
for (const manifest of manifests) {
|
|
3024
3312
|
const current = manifestsByAppId.get(manifest.app_id);
|
|
@@ -3038,7 +3326,7 @@ function composeManifestRequest(inputs, options = {}) {
|
|
|
3038
3326
|
})) : [];
|
|
3039
3327
|
return {
|
|
3040
3328
|
manifests,
|
|
3041
|
-
resources:
|
|
3329
|
+
resources: resourcesWithImplicitCapabilities,
|
|
3042
3330
|
delegationTargets,
|
|
3043
3331
|
registryRecords,
|
|
3044
3332
|
expiryMs: Math.max(...resolved.map((entry) => entry.expiryMs)),
|
|
@@ -3117,7 +3405,7 @@ function inferShortServiceFromActionUrns(actions) {
|
|
|
3117
3405
|
return short;
|
|
3118
3406
|
}
|
|
3119
3407
|
var DEFAULT_READ_ACTIONS = ["tinycloud.kv/get", "tinycloud.kv/metadata"];
|
|
3120
|
-
var DEFAULT_EXPIRY_MS =
|
|
3408
|
+
var DEFAULT_EXPIRY_MS = EXPIRY.SHARE_MS;
|
|
3121
3409
|
var BASE64_PREFIX = "tc1:";
|
|
3122
3410
|
function createError2(code, message, cause, meta) {
|
|
3123
3411
|
return {
|
|
@@ -4250,6 +4538,394 @@ async function checkNodeInfo(host, sdkProtocol, fetchFn = globalThis.fetch.bind(
|
|
|
4250
4538
|
};
|
|
4251
4539
|
}
|
|
4252
4540
|
|
|
4541
|
+
// src/location.ts
|
|
4542
|
+
import { multiaddr } from "@multiformats/multiaddr";
|
|
4543
|
+
import { multiaddrToUri } from "@multiformats/multiaddr-to-uri";
|
|
4544
|
+
import { uriToMultiaddr } from "@multiformats/uri-to-multiaddr";
|
|
4545
|
+
import { ed25519 } from "@noble/curves/ed25519";
|
|
4546
|
+
import { bases } from "multiformats/basics";
|
|
4547
|
+
import { verifyMessage } from "viem";
|
|
4548
|
+
var DEFAULT_TINYCLOUD_LOCATION_REGISTRY_URL = "https://registry.tinycloud.xyz";
|
|
4549
|
+
var DEFAULT_TINYCLOUD_FALLBACK_HOST = "https://node.tinycloud.xyz";
|
|
4550
|
+
var LocationRecordValidationError = class extends Error {
|
|
4551
|
+
constructor(message) {
|
|
4552
|
+
super(`Location record validation failed: ${message}`);
|
|
4553
|
+
this.name = "LocationRecordValidationError";
|
|
4554
|
+
}
|
|
4555
|
+
};
|
|
4556
|
+
var CloudLocationResolutionError = class extends Error {
|
|
4557
|
+
constructor(subject, attempts) {
|
|
4558
|
+
super(`Unable to resolve TinyCloud location for ${subject}`);
|
|
4559
|
+
this.name = "CloudLocationResolutionError";
|
|
4560
|
+
this.attempts = attempts;
|
|
4561
|
+
}
|
|
4562
|
+
};
|
|
4563
|
+
function locationPayloadForRecord(record) {
|
|
4564
|
+
return {
|
|
4565
|
+
version: record.version,
|
|
4566
|
+
subject: record.subject,
|
|
4567
|
+
multiaddrs: [...record.multiaddrs],
|
|
4568
|
+
updated_at: record.updated_at,
|
|
4569
|
+
sequence: record.sequence
|
|
4570
|
+
};
|
|
4571
|
+
}
|
|
4572
|
+
function canonicalLocationPayload(payload) {
|
|
4573
|
+
return JSON.stringify({
|
|
4574
|
+
version: payload.version,
|
|
4575
|
+
subject: payload.subject,
|
|
4576
|
+
multiaddrs: payload.multiaddrs,
|
|
4577
|
+
updated_at: payload.updated_at,
|
|
4578
|
+
sequence: payload.sequence
|
|
4579
|
+
});
|
|
4580
|
+
}
|
|
4581
|
+
async function signLocationRecord(payload, signer) {
|
|
4582
|
+
validateLocationRecordPayload(payload);
|
|
4583
|
+
const message = canonicalLocationPayload(payload);
|
|
4584
|
+
const signature = signer.type === "did:pkh" ? await signer.signMessage(message) : base64UrlEncode2(
|
|
4585
|
+
await signer.signBytes(new TextEncoder().encode(message))
|
|
4586
|
+
);
|
|
4587
|
+
return { ...payload, signature };
|
|
4588
|
+
}
|
|
4589
|
+
function validateLocationRecordPayload(input) {
|
|
4590
|
+
if (input === null || typeof input !== "object") {
|
|
4591
|
+
throw new LocationRecordValidationError("payload must be an object");
|
|
4592
|
+
}
|
|
4593
|
+
const payload = input;
|
|
4594
|
+
if (payload.version !== 1) {
|
|
4595
|
+
throw new LocationRecordValidationError("version must be 1");
|
|
4596
|
+
}
|
|
4597
|
+
validateSubject(payload.subject);
|
|
4598
|
+
validateMultiaddrs(payload.multiaddrs);
|
|
4599
|
+
if (typeof payload.updated_at !== "string" || Number.isNaN(Date.parse(payload.updated_at))) {
|
|
4600
|
+
throw new LocationRecordValidationError(
|
|
4601
|
+
"updated_at must be an ISO timestamp"
|
|
4602
|
+
);
|
|
4603
|
+
}
|
|
4604
|
+
if (typeof payload.sequence !== "number" || !Number.isSafeInteger(payload.sequence) || payload.sequence < 0) {
|
|
4605
|
+
throw new LocationRecordValidationError(
|
|
4606
|
+
"sequence must be a non-negative safe integer"
|
|
4607
|
+
);
|
|
4608
|
+
}
|
|
4609
|
+
return {
|
|
4610
|
+
version: 1,
|
|
4611
|
+
subject: payload.subject,
|
|
4612
|
+
multiaddrs: [...payload.multiaddrs],
|
|
4613
|
+
updated_at: payload.updated_at,
|
|
4614
|
+
sequence: payload.sequence
|
|
4615
|
+
};
|
|
4616
|
+
}
|
|
4617
|
+
function validateLocationRecord(input) {
|
|
4618
|
+
const payload = validateLocationRecordPayload(input);
|
|
4619
|
+
const signature = input.signature;
|
|
4620
|
+
if (typeof signature !== "string" || signature.length === 0) {
|
|
4621
|
+
throw new LocationRecordValidationError(
|
|
4622
|
+
"signature must be a non-empty string"
|
|
4623
|
+
);
|
|
4624
|
+
}
|
|
4625
|
+
return { ...payload, signature };
|
|
4626
|
+
}
|
|
4627
|
+
async function verifyLocationRecord(input) {
|
|
4628
|
+
const record = validateLocationRecord(input);
|
|
4629
|
+
const payload = canonicalLocationPayload(locationPayloadForRecord(record));
|
|
4630
|
+
if (record.subject.startsWith("did:pkh:")) {
|
|
4631
|
+
return verifyPkhSignature(record.subject, payload, record.signature);
|
|
4632
|
+
}
|
|
4633
|
+
if (record.subject.startsWith("did:key:")) {
|
|
4634
|
+
return verifyDidKeySignature(record.subject, payload, record.signature);
|
|
4635
|
+
}
|
|
4636
|
+
return false;
|
|
4637
|
+
}
|
|
4638
|
+
async function fetchLocationRecord(registryUrl, subject, fetchFn = globalThis.fetch) {
|
|
4639
|
+
const url = `${registryUrl.replace(/\/$/, "")}/v1/locations/${encodeURIComponent(subject)}`;
|
|
4640
|
+
const response = await fetchFn(url);
|
|
4641
|
+
if (response.status === 404) {
|
|
4642
|
+
return null;
|
|
4643
|
+
}
|
|
4644
|
+
if (!response.ok) {
|
|
4645
|
+
throw new Error(`location registry returned HTTP ${response.status}`);
|
|
4646
|
+
}
|
|
4647
|
+
const body = await response.json();
|
|
4648
|
+
if (body.record === void 0) {
|
|
4649
|
+
throw new LocationRecordValidationError("registry response missing record");
|
|
4650
|
+
}
|
|
4651
|
+
return validateLocationRecord(body.record);
|
|
4652
|
+
}
|
|
4653
|
+
async function resolveCloudLocation(subject, options = {}) {
|
|
4654
|
+
validateSubject(subject);
|
|
4655
|
+
const verifyRecords = options.verifyRecords ?? true;
|
|
4656
|
+
const attempts = await Promise.all([
|
|
4657
|
+
resolveExplicit(subject, options.explicitMultiaddrs),
|
|
4658
|
+
resolveBlockchain(subject, options.blockchain, verifyRecords),
|
|
4659
|
+
resolveCentralized(subject, options, verifyRecords),
|
|
4660
|
+
resolveFallback(subject, options.fallbackMultiaddrs)
|
|
4661
|
+
]);
|
|
4662
|
+
const winner = attempts.find((attempt) => attempt.candidate)?.candidate;
|
|
4663
|
+
if (!winner) {
|
|
4664
|
+
throw new CloudLocationResolutionError(subject, attempts);
|
|
4665
|
+
}
|
|
4666
|
+
return {
|
|
4667
|
+
subject,
|
|
4668
|
+
source: winner.source,
|
|
4669
|
+
multiaddrs: [...winner.multiaddrs],
|
|
4670
|
+
...winner.record ? { record: winner.record } : {},
|
|
4671
|
+
attempts,
|
|
4672
|
+
resolvedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4673
|
+
};
|
|
4674
|
+
}
|
|
4675
|
+
async function resolveTinyCloudHosts(subject, options = {}) {
|
|
4676
|
+
const location = await resolveCloudLocation(subject, {
|
|
4677
|
+
explicitMultiaddrs: hostsToMultiaddrs(options.explicitHosts),
|
|
4678
|
+
blockchain: options.blockchain,
|
|
4679
|
+
centralizedRegistryUrl: options.registryUrl === null ? void 0 : options.registryUrl ?? DEFAULT_TINYCLOUD_LOCATION_REGISTRY_URL,
|
|
4680
|
+
fallbackMultiaddrs: hostsToMultiaddrs(
|
|
4681
|
+
options.fallbackHosts === null ? void 0 : options.fallbackHosts ?? [DEFAULT_TINYCLOUD_FALLBACK_HOST]
|
|
4682
|
+
),
|
|
4683
|
+
fetch: options.fetch,
|
|
4684
|
+
verifyRecords: options.verifyRecords
|
|
4685
|
+
});
|
|
4686
|
+
return {
|
|
4687
|
+
hosts: location.multiaddrs.map((addr) => multiaddrToHttpUrl(addr)),
|
|
4688
|
+
location
|
|
4689
|
+
};
|
|
4690
|
+
}
|
|
4691
|
+
function multiaddrToHttpUrl(input) {
|
|
4692
|
+
const uri = multiaddrToUri(multiaddr(input));
|
|
4693
|
+
if (!uri.startsWith("http://") && !uri.startsWith("https://")) {
|
|
4694
|
+
throw new LocationRecordValidationError(
|
|
4695
|
+
`multiaddr does not resolve to http/https: ${input}`
|
|
4696
|
+
);
|
|
4697
|
+
}
|
|
4698
|
+
return uri;
|
|
4699
|
+
}
|
|
4700
|
+
function httpUrlToMultiaddr(input) {
|
|
4701
|
+
const url = new URL(input);
|
|
4702
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
4703
|
+
throw new LocationRecordValidationError("URL must use http or https");
|
|
4704
|
+
}
|
|
4705
|
+
return uriToMultiaddr(url.toString()).toString();
|
|
4706
|
+
}
|
|
4707
|
+
function hostsToMultiaddrs(hosts) {
|
|
4708
|
+
if (hosts === void 0 || hosts.length === 0) {
|
|
4709
|
+
return void 0;
|
|
4710
|
+
}
|
|
4711
|
+
return hosts.map(
|
|
4712
|
+
(host) => host.startsWith("/") ? host : httpUrlToMultiaddr(host)
|
|
4713
|
+
);
|
|
4714
|
+
}
|
|
4715
|
+
async function resolveExplicit(subject, multiaddrs) {
|
|
4716
|
+
return resolveAttempt("explicit", async () => {
|
|
4717
|
+
if (multiaddrs === void 0 || multiaddrs.length === 0) {
|
|
4718
|
+
return null;
|
|
4719
|
+
}
|
|
4720
|
+
return toCandidate(subject, "explicit", multiaddrs, false);
|
|
4721
|
+
});
|
|
4722
|
+
}
|
|
4723
|
+
async function resolveBlockchain(subject, resolver, verifyRecords) {
|
|
4724
|
+
return resolveAttempt("blockchain", async () => {
|
|
4725
|
+
if (!resolver) {
|
|
4726
|
+
return null;
|
|
4727
|
+
}
|
|
4728
|
+
return toCandidate(
|
|
4729
|
+
subject,
|
|
4730
|
+
"blockchain",
|
|
4731
|
+
await resolver(subject),
|
|
4732
|
+
verifyRecords
|
|
4733
|
+
);
|
|
4734
|
+
});
|
|
4735
|
+
}
|
|
4736
|
+
async function resolveCentralized(subject, options, verifyRecords) {
|
|
4737
|
+
return resolveAttempt("centralized", async () => {
|
|
4738
|
+
if (!options.centralizedRegistryUrl) {
|
|
4739
|
+
return null;
|
|
4740
|
+
}
|
|
4741
|
+
const record = await fetchLocationRecord(
|
|
4742
|
+
options.centralizedRegistryUrl,
|
|
4743
|
+
subject,
|
|
4744
|
+
options.fetch
|
|
4745
|
+
);
|
|
4746
|
+
return toCandidate(subject, "centralized", record, verifyRecords);
|
|
4747
|
+
});
|
|
4748
|
+
}
|
|
4749
|
+
async function resolveFallback(subject, multiaddrs) {
|
|
4750
|
+
return resolveAttempt("fallback", async () => {
|
|
4751
|
+
if (multiaddrs === void 0 || multiaddrs.length === 0) {
|
|
4752
|
+
return null;
|
|
4753
|
+
}
|
|
4754
|
+
return toCandidate(subject, "fallback", multiaddrs, false);
|
|
4755
|
+
});
|
|
4756
|
+
}
|
|
4757
|
+
async function resolveAttempt(source, resolve) {
|
|
4758
|
+
try {
|
|
4759
|
+
const candidate = await resolve();
|
|
4760
|
+
return candidate ? { source, candidate } : { source };
|
|
4761
|
+
} catch (error) {
|
|
4762
|
+
return {
|
|
4763
|
+
source,
|
|
4764
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
4765
|
+
};
|
|
4766
|
+
}
|
|
4767
|
+
}
|
|
4768
|
+
async function toCandidate(subject, source, input, verifyRecord) {
|
|
4769
|
+
if (input === null || input === void 0) {
|
|
4770
|
+
return null;
|
|
4771
|
+
}
|
|
4772
|
+
if (Array.isArray(input)) {
|
|
4773
|
+
validateMultiaddrs(input);
|
|
4774
|
+
return { source, multiaddrs: [...input] };
|
|
4775
|
+
}
|
|
4776
|
+
const maybeRecord = input;
|
|
4777
|
+
if (maybeRecord.version === 1 && maybeRecord.signature !== void 0) {
|
|
4778
|
+
const record = validateLocationRecord(input);
|
|
4779
|
+
if (record.subject !== subject) {
|
|
4780
|
+
throw new LocationRecordValidationError(
|
|
4781
|
+
"location record subject does not match requested subject"
|
|
4782
|
+
);
|
|
4783
|
+
}
|
|
4784
|
+
if (verifyRecord && !await verifyLocationRecord(record)) {
|
|
4785
|
+
throw new LocationRecordValidationError(
|
|
4786
|
+
"location record signature is invalid"
|
|
4787
|
+
);
|
|
4788
|
+
}
|
|
4789
|
+
return { source, multiaddrs: [...record.multiaddrs], record };
|
|
4790
|
+
}
|
|
4791
|
+
const candidateInput = input;
|
|
4792
|
+
if (!Array.isArray(candidateInput.multiaddrs)) {
|
|
4793
|
+
throw new LocationRecordValidationError(
|
|
4794
|
+
"candidate multiaddrs must be an array"
|
|
4795
|
+
);
|
|
4796
|
+
}
|
|
4797
|
+
validateMultiaddrs(candidateInput.multiaddrs);
|
|
4798
|
+
if (candidateInput.record !== void 0) {
|
|
4799
|
+
const record = validateLocationRecord(candidateInput.record);
|
|
4800
|
+
if (record.subject !== subject) {
|
|
4801
|
+
throw new LocationRecordValidationError(
|
|
4802
|
+
"location record subject does not match requested subject"
|
|
4803
|
+
);
|
|
4804
|
+
}
|
|
4805
|
+
if (verifyRecord && !await verifyLocationRecord(record)) {
|
|
4806
|
+
throw new LocationRecordValidationError(
|
|
4807
|
+
"location record signature is invalid"
|
|
4808
|
+
);
|
|
4809
|
+
}
|
|
4810
|
+
return { source, multiaddrs: [...candidateInput.multiaddrs], record };
|
|
4811
|
+
}
|
|
4812
|
+
return { source, multiaddrs: [...candidateInput.multiaddrs] };
|
|
4813
|
+
}
|
|
4814
|
+
function validateSubject(subject) {
|
|
4815
|
+
if (typeof subject !== "string" || subject.length === 0) {
|
|
4816
|
+
throw new LocationRecordValidationError(
|
|
4817
|
+
"subject must be a non-empty string"
|
|
4818
|
+
);
|
|
4819
|
+
}
|
|
4820
|
+
if (!subject.startsWith("did:pkh:") && !subject.startsWith("did:key:")) {
|
|
4821
|
+
throw new LocationRecordValidationError(
|
|
4822
|
+
"subject must be did:pkh or did:key"
|
|
4823
|
+
);
|
|
4824
|
+
}
|
|
4825
|
+
}
|
|
4826
|
+
function validateMultiaddrs(input) {
|
|
4827
|
+
if (!Array.isArray(input)) {
|
|
4828
|
+
throw new LocationRecordValidationError("multiaddrs must be an array");
|
|
4829
|
+
}
|
|
4830
|
+
for (const addr of input) {
|
|
4831
|
+
if (typeof addr !== "string" || addr.length === 0) {
|
|
4832
|
+
throw new LocationRecordValidationError(
|
|
4833
|
+
"multiaddr entries must be non-empty strings"
|
|
4834
|
+
);
|
|
4835
|
+
}
|
|
4836
|
+
try {
|
|
4837
|
+
multiaddr(addr);
|
|
4838
|
+
} catch {
|
|
4839
|
+
throw new LocationRecordValidationError(`invalid multiaddr: ${addr}`);
|
|
4840
|
+
}
|
|
4841
|
+
}
|
|
4842
|
+
}
|
|
4843
|
+
async function verifyPkhSignature(did, payload, signature) {
|
|
4844
|
+
const address = did.split(":").at(-1);
|
|
4845
|
+
if (!address || !/^0x[a-fA-F0-9]{40}$/.test(address)) {
|
|
4846
|
+
throw new LocationRecordValidationError(
|
|
4847
|
+
"did:pkh subject must end with an EVM address"
|
|
4848
|
+
);
|
|
4849
|
+
}
|
|
4850
|
+
if (!/^0x[0-9a-fA-F]+$/.test(signature)) {
|
|
4851
|
+
throw new LocationRecordValidationError("did:pkh signature must be hex");
|
|
4852
|
+
}
|
|
4853
|
+
return verifyMessage({
|
|
4854
|
+
address,
|
|
4855
|
+
message: payload,
|
|
4856
|
+
signature
|
|
4857
|
+
});
|
|
4858
|
+
}
|
|
4859
|
+
function verifyDidKeySignature(did, payload, signature) {
|
|
4860
|
+
const publicKey = ed25519PublicKeyFromDidKey(did);
|
|
4861
|
+
const signatureBytes = decodeBase64Url(signature);
|
|
4862
|
+
if (signatureBytes.length !== 64) {
|
|
4863
|
+
throw new LocationRecordValidationError(
|
|
4864
|
+
"did:key signature must be a base64url Ed25519 signature"
|
|
4865
|
+
);
|
|
4866
|
+
}
|
|
4867
|
+
return ed25519.verify(
|
|
4868
|
+
signatureBytes,
|
|
4869
|
+
new TextEncoder().encode(payload),
|
|
4870
|
+
publicKey
|
|
4871
|
+
);
|
|
4872
|
+
}
|
|
4873
|
+
function ed25519PublicKeyFromDidKey(did) {
|
|
4874
|
+
const identifier = did.slice("did:key:".length);
|
|
4875
|
+
if (!identifier.startsWith("z")) {
|
|
4876
|
+
throw new LocationRecordValidationError(
|
|
4877
|
+
"did:key must use base58btc multibase"
|
|
4878
|
+
);
|
|
4879
|
+
}
|
|
4880
|
+
const bytes = bases.base58btc.decode(identifier);
|
|
4881
|
+
if (bytes.length !== 34 || bytes[0] !== 237 || bytes[1] !== 1) {
|
|
4882
|
+
throw new LocationRecordValidationError(
|
|
4883
|
+
"did:key must be an Ed25519 public key"
|
|
4884
|
+
);
|
|
4885
|
+
}
|
|
4886
|
+
return bytes.slice(2);
|
|
4887
|
+
}
|
|
4888
|
+
function base64UrlEncode2(bytes) {
|
|
4889
|
+
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
4890
|
+
let output = "";
|
|
4891
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
4892
|
+
const a = bytes[i];
|
|
4893
|
+
const b = bytes[i + 1];
|
|
4894
|
+
const c = bytes[i + 2];
|
|
4895
|
+
const triplet = a << 16 | (b ?? 0) << 8 | (c ?? 0);
|
|
4896
|
+
output += alphabet[triplet >> 18 & 63];
|
|
4897
|
+
output += alphabet[triplet >> 12 & 63];
|
|
4898
|
+
if (i + 1 < bytes.length) {
|
|
4899
|
+
output += alphabet[triplet >> 6 & 63];
|
|
4900
|
+
}
|
|
4901
|
+
if (i + 2 < bytes.length) {
|
|
4902
|
+
output += alphabet[triplet & 63];
|
|
4903
|
+
}
|
|
4904
|
+
}
|
|
4905
|
+
return output;
|
|
4906
|
+
}
|
|
4907
|
+
function decodeBase64Url(value) {
|
|
4908
|
+
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
4909
|
+
const bytes = [];
|
|
4910
|
+
let buffer = 0;
|
|
4911
|
+
let bits = 0;
|
|
4912
|
+
for (const char of value) {
|
|
4913
|
+
const index = alphabet.indexOf(char);
|
|
4914
|
+
if (index < 0) {
|
|
4915
|
+
throw new LocationRecordValidationError(
|
|
4916
|
+
"did:key signature must be base64url"
|
|
4917
|
+
);
|
|
4918
|
+
}
|
|
4919
|
+
buffer = buffer << 6 | index;
|
|
4920
|
+
bits += 6;
|
|
4921
|
+
if (bits >= 8) {
|
|
4922
|
+
bits -= 8;
|
|
4923
|
+
bytes.push(buffer >> bits & 255);
|
|
4924
|
+
}
|
|
4925
|
+
}
|
|
4926
|
+
return Uint8Array.from(bytes);
|
|
4927
|
+
}
|
|
4928
|
+
|
|
4253
4929
|
// src/capabilities.ts
|
|
4254
4930
|
var PermissionNotInManifestError = class extends Error {
|
|
4255
4931
|
constructor(missing, granted) {
|
|
@@ -4370,10 +5046,13 @@ export {
|
|
|
4370
5046
|
CapabilityKeyRegistry,
|
|
4371
5047
|
CapabilityKeyRegistryErrorCodes,
|
|
4372
5048
|
ClientSessionSchema,
|
|
5049
|
+
CloudLocationResolutionError,
|
|
4373
5050
|
DEFAULT_DEFAULTS,
|
|
4374
5051
|
DEFAULT_EXPIRY,
|
|
4375
5052
|
DEFAULT_MANIFEST_SPACE,
|
|
4376
5053
|
DEFAULT_MANIFEST_VERSION,
|
|
5054
|
+
DEFAULT_TINYCLOUD_FALLBACK_HOST,
|
|
5055
|
+
DEFAULT_TINYCLOUD_LOCATION_REGISTRY_URL,
|
|
4377
5056
|
DataVaultService,
|
|
4378
5057
|
DatabaseHandle,
|
|
4379
5058
|
DelegationErrorCodes,
|
|
@@ -4381,18 +5060,22 @@ export {
|
|
|
4381
5060
|
DuckDbAction,
|
|
4382
5061
|
DuckDbDatabaseHandle,
|
|
4383
5062
|
DuckDbService2 as DuckDbService,
|
|
5063
|
+
EXPIRY,
|
|
4384
5064
|
EnsDataSchema,
|
|
4385
5065
|
ErrorCodes2 as ErrorCodes,
|
|
4386
5066
|
HooksService2 as HooksService,
|
|
4387
5067
|
KVService2 as KVService,
|
|
5068
|
+
LocationRecordValidationError,
|
|
4388
5069
|
ManifestValidationError,
|
|
4389
5070
|
PermissionNotInManifestError,
|
|
4390
5071
|
PrefixedKVService,
|
|
4391
5072
|
ProtocolMismatchError,
|
|
5073
|
+
SECRET_NAME_RE2 as SECRET_NAME_RE,
|
|
4392
5074
|
SERVICE_LONG_TO_SHORT,
|
|
4393
5075
|
SERVICE_SHORT_TO_LONG,
|
|
4394
5076
|
SQLAction,
|
|
4395
5077
|
SQLService2 as SQLService,
|
|
5078
|
+
SecretsService,
|
|
4396
5079
|
ServiceContext2 as ServiceContext,
|
|
4397
5080
|
SessionExpiredError,
|
|
4398
5081
|
SharingService,
|
|
@@ -4404,12 +5087,15 @@ export {
|
|
|
4404
5087
|
SpaceService,
|
|
4405
5088
|
TinyCloud,
|
|
4406
5089
|
UnsupportedFeatureError,
|
|
5090
|
+
VAULT_PERMISSION_SERVICE,
|
|
4407
5091
|
VaultHeaders,
|
|
4408
5092
|
VaultPublicSpaceKVActions,
|
|
4409
5093
|
VersionCheckError,
|
|
4410
5094
|
activateSessionWithHost,
|
|
4411
5095
|
applyPrefix,
|
|
4412
5096
|
buildSpaceUri,
|
|
5097
|
+
canonicalLocationPayload,
|
|
5098
|
+
canonicalizeSecretScope,
|
|
4413
5099
|
checkNodeInfo,
|
|
4414
5100
|
composeManifestRequest,
|
|
4415
5101
|
createCapabilityKeyRegistry,
|
|
@@ -4421,23 +5107,36 @@ export {
|
|
|
4421
5107
|
defaultSpaceCreationHandler,
|
|
4422
5108
|
err4 as err,
|
|
4423
5109
|
expandActionShortNames,
|
|
5110
|
+
expandPermissionEntries,
|
|
5111
|
+
expandPermissionEntry,
|
|
5112
|
+
fetchLocationRecord,
|
|
4424
5113
|
fetchPeerId,
|
|
5114
|
+
httpUrlToMultiaddr,
|
|
4425
5115
|
isCapabilitySubset,
|
|
4426
5116
|
loadManifest,
|
|
5117
|
+
locationPayloadForRecord,
|
|
4427
5118
|
makePublicSpaceId,
|
|
4428
5119
|
manifestAbilitiesUnion,
|
|
5120
|
+
multiaddrToHttpUrl,
|
|
4429
5121
|
normalizeDefaults,
|
|
4430
5122
|
ok4 as ok,
|
|
4431
5123
|
parseExpiry,
|
|
4432
5124
|
parseRecapCapabilities,
|
|
4433
5125
|
parseSpaceUri,
|
|
5126
|
+
resolveCloudLocation,
|
|
4434
5127
|
resolveManifest,
|
|
5128
|
+
resolveSecretPath2 as resolveSecretPath,
|
|
5129
|
+
resolveTinyCloudHosts,
|
|
4435
5130
|
resourceCapabilitiesToAbilitiesMap,
|
|
4436
5131
|
resourceCapabilitiesToSpaceAbilitiesMap,
|
|
4437
5132
|
serviceError4 as serviceError,
|
|
5133
|
+
signLocationRecord,
|
|
4438
5134
|
submitHostDelegation,
|
|
4439
5135
|
validateClientSession,
|
|
5136
|
+
validateLocationRecord,
|
|
5137
|
+
validateLocationRecordPayload,
|
|
4440
5138
|
validateManifest,
|
|
4441
|
-
validatePersistedSessionData
|
|
5139
|
+
validatePersistedSessionData,
|
|
5140
|
+
verifyLocationRecord
|
|
4442
5141
|
};
|
|
4443
5142
|
//# sourceMappingURL=index.js.map
|