@tinycloud/sdk-core 2.2.0-beta.0 → 2.2.0-beta.10
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 +785 -71
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +179 -23
- package/dist/index.d.ts +179 -23
- package/dist/index.js +743 -47
- 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) */
|
|
@@ -820,6 +831,7 @@ var SpaceService = class {
|
|
|
820
831
|
this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
821
832
|
this.capabilityRegistry = config.capabilityRegistry;
|
|
822
833
|
this.createKVServiceFn = config.createKVService;
|
|
834
|
+
this.createVaultServiceFn = config.createVaultService;
|
|
823
835
|
this._userDid = config.userDid;
|
|
824
836
|
this.sharingService = config.sharingService;
|
|
825
837
|
this.createDelegationFn = config.createDelegation;
|
|
@@ -834,6 +846,7 @@ var SpaceService = class {
|
|
|
834
846
|
if (config.fetch) this.fetchFn = config.fetch;
|
|
835
847
|
if (config.capabilityRegistry) this.capabilityRegistry = config.capabilityRegistry;
|
|
836
848
|
if (config.createKVService) this.createKVServiceFn = config.createKVService;
|
|
849
|
+
if (config.createVaultService) this.createVaultServiceFn = config.createVaultService;
|
|
837
850
|
if (config.userDid !== void 0) this._userDid = config.userDid;
|
|
838
851
|
if (config.sharingService) this.sharingService = config.sharingService;
|
|
839
852
|
if (config.createDelegation) this.createDelegationFn = config.createDelegation;
|
|
@@ -1116,6 +1129,7 @@ var SpaceService = class {
|
|
|
1116
1129
|
id: spaceId,
|
|
1117
1130
|
name,
|
|
1118
1131
|
createKV: this.createSpaceScopedKV.bind(this),
|
|
1132
|
+
createVault: this.createSpaceScopedVault.bind(this),
|
|
1119
1133
|
createDelegations: this.createSpaceScopedDelegations.bind(this),
|
|
1120
1134
|
createSharing: this.createSpaceScopedSharing.bind(this),
|
|
1121
1135
|
getInfo: this.getSpaceInfo.bind(this)
|
|
@@ -1245,6 +1259,21 @@ var SpaceService = class {
|
|
|
1245
1259
|
}
|
|
1246
1260
|
});
|
|
1247
1261
|
}
|
|
1262
|
+
/**
|
|
1263
|
+
* Create a space-scoped Data Vault service.
|
|
1264
|
+
*/
|
|
1265
|
+
createSpaceScopedVault(spaceId) {
|
|
1266
|
+
if (this.createVaultServiceFn) {
|
|
1267
|
+
return this.createVaultServiceFn(spaceId);
|
|
1268
|
+
}
|
|
1269
|
+
return new Proxy({}, {
|
|
1270
|
+
get: () => {
|
|
1271
|
+
throw new Error(
|
|
1272
|
+
"Vault service factory not configured. Provide createVaultService in SpaceServiceConfig."
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1248
1277
|
/**
|
|
1249
1278
|
* Create space-scoped delegation operations.
|
|
1250
1279
|
*/
|
|
@@ -1989,7 +2018,11 @@ import {
|
|
|
1989
2018
|
DataVaultService,
|
|
1990
2019
|
VaultHeaders,
|
|
1991
2020
|
VaultPublicSpaceKVActions,
|
|
1992
|
-
createVaultCrypto
|
|
2021
|
+
createVaultCrypto,
|
|
2022
|
+
SecretsService,
|
|
2023
|
+
SECRET_NAME_RE as SECRET_NAME_RE2,
|
|
2024
|
+
canonicalizeSecretScope,
|
|
2025
|
+
resolveSecretPath as resolveSecretPath2
|
|
1993
2026
|
} from "@tinycloud/sdk-services";
|
|
1994
2027
|
|
|
1995
2028
|
// src/space.ts
|
|
@@ -2658,6 +2691,7 @@ function validateEncodedShareData(data) {
|
|
|
2658
2691
|
|
|
2659
2692
|
// src/manifest.ts
|
|
2660
2693
|
import ms from "ms";
|
|
2694
|
+
import { resolveSecretPath, SECRET_NAME_RE } from "@tinycloud/sdk-services";
|
|
2661
2695
|
var ManifestValidationError = class extends Error {
|
|
2662
2696
|
constructor(message) {
|
|
2663
2697
|
super(`Manifest validation failed: ${message}`);
|
|
@@ -2670,6 +2704,8 @@ var DEFAULT_MANIFEST_VERSION = 1;
|
|
|
2670
2704
|
var DEFAULT_MANIFEST_SPACE = "applications";
|
|
2671
2705
|
var ACCOUNT_REGISTRY_SPACE = "account";
|
|
2672
2706
|
var ACCOUNT_REGISTRY_PATH = "applications/";
|
|
2707
|
+
var SECRETS_SPACE = "secrets";
|
|
2708
|
+
var VAULT_PERMISSION_SERVICE = "tinycloud.vault";
|
|
2673
2709
|
var SERVICE_SHORT_TO_LONG = Object.freeze({
|
|
2674
2710
|
kv: "tinycloud.kv",
|
|
2675
2711
|
sql: "tinycloud.sql",
|
|
@@ -2694,12 +2730,6 @@ var DEFAULT_STANDARD_ENTRIES = [
|
|
|
2694
2730
|
space: DEFAULT_MANIFEST_SPACE,
|
|
2695
2731
|
path: "/",
|
|
2696
2732
|
actions: ["read", "write"]
|
|
2697
|
-
},
|
|
2698
|
-
{
|
|
2699
|
-
service: "tinycloud.capabilities",
|
|
2700
|
-
space: DEFAULT_MANIFEST_SPACE,
|
|
2701
|
-
path: "/",
|
|
2702
|
-
actions: ["read"]
|
|
2703
2733
|
}
|
|
2704
2734
|
];
|
|
2705
2735
|
var DEFAULT_ADMIN_ENTRIES = [
|
|
@@ -2714,12 +2744,6 @@ var DEFAULT_ADMIN_ENTRIES = [
|
|
|
2714
2744
|
space: DEFAULT_MANIFEST_SPACE,
|
|
2715
2745
|
path: "/",
|
|
2716
2746
|
actions: ["read", "write", "ddl"]
|
|
2717
|
-
},
|
|
2718
|
-
{
|
|
2719
|
-
service: "tinycloud.capabilities",
|
|
2720
|
-
space: DEFAULT_MANIFEST_SPACE,
|
|
2721
|
-
path: "/",
|
|
2722
|
-
actions: ["read", "admin"]
|
|
2723
2747
|
}
|
|
2724
2748
|
];
|
|
2725
2749
|
var DEFAULT_ALL_ENTRIES = [
|
|
@@ -2740,12 +2764,6 @@ var DEFAULT_ALL_ENTRIES = [
|
|
|
2740
2764
|
space: DEFAULT_MANIFEST_SPACE,
|
|
2741
2765
|
path: "/",
|
|
2742
2766
|
actions: ["read", "write"]
|
|
2743
|
-
},
|
|
2744
|
-
{
|
|
2745
|
-
service: "tinycloud.capabilities",
|
|
2746
|
-
space: DEFAULT_MANIFEST_SPACE,
|
|
2747
|
-
path: "/",
|
|
2748
|
-
actions: ["read", "admin"]
|
|
2749
2767
|
}
|
|
2750
2768
|
];
|
|
2751
2769
|
function parseExpiry(duration) {
|
|
@@ -2754,9 +2772,7 @@ function parseExpiry(duration) {
|
|
|
2754
2772
|
`expiry must be a non-empty duration string (got ${JSON.stringify(duration)})`
|
|
2755
2773
|
);
|
|
2756
2774
|
}
|
|
2757
|
-
const parsed = ms(
|
|
2758
|
-
duration
|
|
2759
|
-
);
|
|
2775
|
+
const parsed = ms(duration);
|
|
2760
2776
|
if (typeof parsed !== "number" || !Number.isFinite(parsed) || parsed <= 0) {
|
|
2761
2777
|
throw new ManifestValidationError(
|
|
2762
2778
|
`invalid expiry duration: ${JSON.stringify(duration)}`
|
|
@@ -2772,6 +2788,20 @@ function expandActionShortNames(service, actions) {
|
|
|
2772
2788
|
return `${service}/${a}`;
|
|
2773
2789
|
});
|
|
2774
2790
|
}
|
|
2791
|
+
function expandPermissionEntry(entry) {
|
|
2792
|
+
if (entry.service !== VAULT_PERMISSION_SERVICE) {
|
|
2793
|
+
return [
|
|
2794
|
+
{
|
|
2795
|
+
...entry,
|
|
2796
|
+
actions: expandActionShortNames(entry.service, entry.actions)
|
|
2797
|
+
}
|
|
2798
|
+
];
|
|
2799
|
+
}
|
|
2800
|
+
return expandVaultPermissionEntry(entry);
|
|
2801
|
+
}
|
|
2802
|
+
function expandPermissionEntries(entries) {
|
|
2803
|
+
return entries.flatMap(expandPermissionEntry);
|
|
2804
|
+
}
|
|
2775
2805
|
function applyPrefix(prefix, path, skipPrefix) {
|
|
2776
2806
|
if (skipPrefix) {
|
|
2777
2807
|
return path;
|
|
@@ -2811,30 +2841,81 @@ function validateManifest(input) {
|
|
|
2811
2841
|
);
|
|
2812
2842
|
}
|
|
2813
2843
|
if (typeof m.app_id !== "string" || m.app_id.length === 0) {
|
|
2814
|
-
throw new ManifestValidationError(
|
|
2844
|
+
throw new ManifestValidationError(
|
|
2845
|
+
"manifest.app_id is required and must be a non-empty string"
|
|
2846
|
+
);
|
|
2815
2847
|
}
|
|
2816
2848
|
if (typeof m.name !== "string" || m.name.length === 0) {
|
|
2817
|
-
throw new ManifestValidationError(
|
|
2849
|
+
throw new ManifestValidationError(
|
|
2850
|
+
"manifest.name is required and must be a non-empty string"
|
|
2851
|
+
);
|
|
2818
2852
|
}
|
|
2819
2853
|
if (m.did !== void 0 && (typeof m.did !== "string" || m.did.length === 0)) {
|
|
2820
|
-
throw new ManifestValidationError(
|
|
2854
|
+
throw new ManifestValidationError(
|
|
2855
|
+
"manifest.did must be a non-empty DID string"
|
|
2856
|
+
);
|
|
2821
2857
|
}
|
|
2822
2858
|
if (m.space !== void 0 && (typeof m.space !== "string" || m.space.length === 0)) {
|
|
2823
|
-
throw new ManifestValidationError(
|
|
2859
|
+
throw new ManifestValidationError(
|
|
2860
|
+
"manifest.space must be a non-empty string"
|
|
2861
|
+
);
|
|
2824
2862
|
}
|
|
2825
2863
|
if (m.expiry !== void 0) {
|
|
2826
2864
|
parseExpiry(m.expiry);
|
|
2827
2865
|
}
|
|
2828
2866
|
if (m.permissions !== void 0) {
|
|
2829
2867
|
if (!Array.isArray(m.permissions)) {
|
|
2830
|
-
throw new ManifestValidationError(
|
|
2868
|
+
throw new ManifestValidationError(
|
|
2869
|
+
"manifest.permissions must be an array"
|
|
2870
|
+
);
|
|
2831
2871
|
}
|
|
2832
2872
|
m.permissions.forEach(
|
|
2833
2873
|
(p, i) => validatePermissionEntry(p, `permissions[${i}]`)
|
|
2834
2874
|
);
|
|
2835
2875
|
}
|
|
2876
|
+
if (m.secrets !== void 0) {
|
|
2877
|
+
validateManifestSecrets(m.secrets);
|
|
2878
|
+
}
|
|
2836
2879
|
return m;
|
|
2837
2880
|
}
|
|
2881
|
+
function validateManifestSecrets(secrets) {
|
|
2882
|
+
if (secrets === null || typeof secrets !== "object" || Array.isArray(secrets)) {
|
|
2883
|
+
throw new ManifestValidationError("manifest.secrets must be an object");
|
|
2884
|
+
}
|
|
2885
|
+
for (const [name, spec] of Object.entries(secrets)) {
|
|
2886
|
+
if (!SECRET_NAME_RE.test(name)) {
|
|
2887
|
+
throw new ManifestValidationError(
|
|
2888
|
+
`manifest.secrets.${name} must match ${SECRET_NAME_RE.source}`
|
|
2889
|
+
);
|
|
2890
|
+
}
|
|
2891
|
+
try {
|
|
2892
|
+
resolveSecretPath(
|
|
2893
|
+
secretNameFromSpec(name, spec),
|
|
2894
|
+
{ scope: secretScopeFromSpec(spec) }
|
|
2895
|
+
);
|
|
2896
|
+
} catch (error) {
|
|
2897
|
+
throw new ManifestValidationError(
|
|
2898
|
+
`manifest.secrets.${name}: ${error instanceof Error ? error.message : String(error)}`
|
|
2899
|
+
);
|
|
2900
|
+
}
|
|
2901
|
+
const actions = secretActionsFromSpec(name, spec);
|
|
2902
|
+
if (actions.length === 0) {
|
|
2903
|
+
throw new ManifestValidationError(
|
|
2904
|
+
`manifest.secrets.${name} actions must be non-empty`
|
|
2905
|
+
);
|
|
2906
|
+
}
|
|
2907
|
+
for (const action of actions) {
|
|
2908
|
+
if (typeof action !== "string" || action.length === 0) {
|
|
2909
|
+
throw new ManifestValidationError(
|
|
2910
|
+
`manifest.secrets.${name} actions must be non-empty strings`
|
|
2911
|
+
);
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
if (spec !== null && typeof spec === "object" && !Array.isArray(spec) && spec.expiry !== void 0) {
|
|
2915
|
+
parseExpiry(spec.expiry);
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2838
2919
|
function validatePermissionEntry(p, path) {
|
|
2839
2920
|
if (p === null || typeof p !== "object") {
|
|
2840
2921
|
throw new ManifestValidationError(`${path} must be an object`);
|
|
@@ -2844,7 +2925,9 @@ function validatePermissionEntry(p, path) {
|
|
|
2844
2925
|
throw new ManifestValidationError(`${path}.service is required`);
|
|
2845
2926
|
}
|
|
2846
2927
|
if (entry.space !== void 0 && (typeof entry.space !== "string" || entry.space.length === 0)) {
|
|
2847
|
-
throw new ManifestValidationError(
|
|
2928
|
+
throw new ManifestValidationError(
|
|
2929
|
+
`${path}.space must be a non-empty string`
|
|
2930
|
+
);
|
|
2848
2931
|
}
|
|
2849
2932
|
if (typeof entry.path !== "string") {
|
|
2850
2933
|
throw new ManifestValidationError(
|
|
@@ -2856,6 +2939,16 @@ function validatePermissionEntry(p, path) {
|
|
|
2856
2939
|
`${path}.actions must be a non-empty array`
|
|
2857
2940
|
);
|
|
2858
2941
|
}
|
|
2942
|
+
for (const action of entry.actions) {
|
|
2943
|
+
if (typeof action !== "string" || action.length === 0) {
|
|
2944
|
+
throw new ManifestValidationError(
|
|
2945
|
+
`${path}.actions must contain non-empty strings`
|
|
2946
|
+
);
|
|
2947
|
+
}
|
|
2948
|
+
if (entry.service === VAULT_PERMISSION_SERVICE) {
|
|
2949
|
+
vaultActionExpansion(action);
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2859
2952
|
if (entry.expiry !== void 0) {
|
|
2860
2953
|
parseExpiry(entry.expiry);
|
|
2861
2954
|
}
|
|
@@ -2885,7 +2978,8 @@ function defaultEntriesForTier(tier) {
|
|
|
2885
2978
|
service: e.service,
|
|
2886
2979
|
space: e.space,
|
|
2887
2980
|
path: e.path,
|
|
2888
|
-
actions: [...e.actions]
|
|
2981
|
+
actions: [...e.actions],
|
|
2982
|
+
...e.skipPrefix !== void 0 ? { skipPrefix: e.skipPrefix } : {}
|
|
2889
2983
|
}));
|
|
2890
2984
|
}
|
|
2891
2985
|
function resolveManifest(input) {
|
|
@@ -2897,9 +2991,14 @@ function resolveManifest(input) {
|
|
|
2897
2991
|
const tier = normalizeDefaults(manifest.defaults);
|
|
2898
2992
|
const defaultEntries = defaultEntriesForTier(tier);
|
|
2899
2993
|
const explicitEntries = manifest.permissions ?? [];
|
|
2900
|
-
const
|
|
2901
|
-
const
|
|
2902
|
-
|
|
2994
|
+
const secretEntries = secretEntriesForManifest(manifest.secrets);
|
|
2995
|
+
const allEntries = [
|
|
2996
|
+
...defaultEntries,
|
|
2997
|
+
...explicitEntries,
|
|
2998
|
+
...secretEntries
|
|
2999
|
+
];
|
|
3000
|
+
const resources = withCapabilitiesReadForSpaces(
|
|
3001
|
+
allEntries.flatMap((entry) => resolveEntry(entry, prefix, expiryMs, space))
|
|
2903
3002
|
);
|
|
2904
3003
|
const additionalDelegates = manifest.did === void 0 ? [] : [
|
|
2905
3004
|
{
|
|
@@ -2919,24 +3018,191 @@ function resolveManifest(input) {
|
|
|
2919
3018
|
additionalDelegates
|
|
2920
3019
|
};
|
|
2921
3020
|
}
|
|
3021
|
+
function normalizeSecretActions(actions) {
|
|
3022
|
+
const out = [];
|
|
3023
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3024
|
+
const add = (action) => {
|
|
3025
|
+
if (!seen.has(action)) {
|
|
3026
|
+
out.push(action);
|
|
3027
|
+
seen.add(action);
|
|
3028
|
+
}
|
|
3029
|
+
};
|
|
3030
|
+
for (const action of actions) {
|
|
3031
|
+
if (action === "read") {
|
|
3032
|
+
add("get");
|
|
3033
|
+
continue;
|
|
3034
|
+
}
|
|
3035
|
+
if (action === "write") {
|
|
3036
|
+
add("put");
|
|
3037
|
+
continue;
|
|
3038
|
+
}
|
|
3039
|
+
if (action === "delete") {
|
|
3040
|
+
add("del");
|
|
3041
|
+
continue;
|
|
3042
|
+
}
|
|
3043
|
+
if (action === "get" || action === "put" || action === "del" || action === "list" || action === "metadata") {
|
|
3044
|
+
add(action);
|
|
3045
|
+
continue;
|
|
3046
|
+
}
|
|
3047
|
+
if (action === "tinycloud.kv/get" || action === "tinycloud.kv/put" || action === "tinycloud.kv/del" || action === "tinycloud.kv/list" || action === "tinycloud.kv/metadata") {
|
|
3048
|
+
add(action);
|
|
3049
|
+
continue;
|
|
3050
|
+
}
|
|
3051
|
+
throw new ManifestValidationError(
|
|
3052
|
+
`unknown secret action ${JSON.stringify(action)}; expected read, write, delete, list, or metadata`
|
|
3053
|
+
);
|
|
3054
|
+
}
|
|
3055
|
+
return out;
|
|
3056
|
+
}
|
|
3057
|
+
function secretNameFromSpec(fallbackName, spec) {
|
|
3058
|
+
if (spec !== null && typeof spec === "object" && !Array.isArray(spec)) {
|
|
3059
|
+
return spec.name ?? fallbackName;
|
|
3060
|
+
}
|
|
3061
|
+
return fallbackName;
|
|
3062
|
+
}
|
|
3063
|
+
function secretScopeFromSpec(spec) {
|
|
3064
|
+
if (spec !== null && typeof spec === "object" && !Array.isArray(spec)) {
|
|
3065
|
+
return spec.scope;
|
|
3066
|
+
}
|
|
3067
|
+
return void 0;
|
|
3068
|
+
}
|
|
3069
|
+
function secretActionsFromSpec(name, spec) {
|
|
3070
|
+
if (spec === true) {
|
|
3071
|
+
return ["read"];
|
|
3072
|
+
}
|
|
3073
|
+
if (typeof spec === "string") {
|
|
3074
|
+
return [spec];
|
|
3075
|
+
}
|
|
3076
|
+
if (Array.isArray(spec)) {
|
|
3077
|
+
return spec;
|
|
3078
|
+
}
|
|
3079
|
+
if (spec === null || typeof spec !== "object") {
|
|
3080
|
+
throw new ManifestValidationError(
|
|
3081
|
+
`manifest.secrets.${name} must be true, a string action, an actions array, or an object`
|
|
3082
|
+
);
|
|
3083
|
+
}
|
|
3084
|
+
if (spec.actions === void 0) {
|
|
3085
|
+
return ["read"];
|
|
3086
|
+
}
|
|
3087
|
+
if (typeof spec.actions === "string") {
|
|
3088
|
+
return [spec.actions];
|
|
3089
|
+
}
|
|
3090
|
+
if (Array.isArray(spec.actions)) {
|
|
3091
|
+
return spec.actions;
|
|
3092
|
+
}
|
|
3093
|
+
throw new ManifestValidationError(
|
|
3094
|
+
`manifest.secrets.${name}.actions must be a string or array`
|
|
3095
|
+
);
|
|
3096
|
+
}
|
|
3097
|
+
function secretEntriesForManifest(secrets) {
|
|
3098
|
+
if (secrets === void 0) {
|
|
3099
|
+
return [];
|
|
3100
|
+
}
|
|
3101
|
+
const entries = [];
|
|
3102
|
+
for (const [name, spec] of Object.entries(secrets)) {
|
|
3103
|
+
const actions = secretActionsFromSpec(name, spec);
|
|
3104
|
+
const secretPath = resolveSecretPath(
|
|
3105
|
+
secretNameFromSpec(name, spec),
|
|
3106
|
+
{ scope: secretScopeFromSpec(spec) }
|
|
3107
|
+
);
|
|
3108
|
+
const extra = spec !== true && typeof spec === "object" && !Array.isArray(spec) ? spec : {};
|
|
3109
|
+
entries.push({
|
|
3110
|
+
service: VAULT_PERMISSION_SERVICE,
|
|
3111
|
+
space: SECRETS_SPACE,
|
|
3112
|
+
path: secretPath.vaultKey,
|
|
3113
|
+
actions: normalizeSecretActions(actions),
|
|
3114
|
+
skipPrefix: true,
|
|
3115
|
+
...extra.expiry !== void 0 ? { expiry: extra.expiry } : {},
|
|
3116
|
+
...extra.description !== void 0 ? { description: extra.description } : {}
|
|
3117
|
+
});
|
|
3118
|
+
}
|
|
3119
|
+
return entries;
|
|
3120
|
+
}
|
|
2922
3121
|
function resolveEntry(entry, prefix, _inheritedExpiryMs, inheritedSpace) {
|
|
2923
3122
|
const resolvedPath = applyPrefix(
|
|
2924
3123
|
prefix,
|
|
2925
3124
|
entry.path,
|
|
2926
3125
|
entry.skipPrefix === true
|
|
2927
3126
|
);
|
|
2928
|
-
const resolvedActions = expandActionShortNames(entry.service, entry.actions);
|
|
2929
3127
|
const entryExpiryMs = entry.expiry !== void 0 ? parseExpiry(entry.expiry) : void 0;
|
|
2930
|
-
return {
|
|
2931
|
-
|
|
3128
|
+
return expandPermissionEntry({
|
|
3129
|
+
...entry,
|
|
2932
3130
|
space: entry.space ?? inheritedSpace,
|
|
2933
3131
|
path: resolvedPath,
|
|
2934
|
-
|
|
3132
|
+
skipPrefix: true
|
|
3133
|
+
}).map((expanded) => ({
|
|
3134
|
+
service: expanded.service,
|
|
3135
|
+
space: expanded.space ?? inheritedSpace,
|
|
3136
|
+
path: expanded.path,
|
|
3137
|
+
actions: expanded.actions,
|
|
2935
3138
|
// Only populate `expiryMs` when the entry had its own expiry override.
|
|
2936
3139
|
// When absent, callers use the parent (delegation or manifest) expiry
|
|
2937
3140
|
// which is carried on ResolvedDelegate.expiryMs / ResolvedCapabilities.expiryMs.
|
|
2938
|
-
...entryExpiryMs !== void 0 ? { expiryMs: entryExpiryMs } : {}
|
|
2939
|
-
|
|
3141
|
+
...entryExpiryMs !== void 0 ? { expiryMs: entryExpiryMs } : {},
|
|
3142
|
+
...entry.description !== void 0 ? { description: entry.description } : {}
|
|
3143
|
+
}));
|
|
3144
|
+
}
|
|
3145
|
+
function expandVaultPermissionEntry(entry) {
|
|
3146
|
+
const byBase = /* @__PURE__ */ new Map();
|
|
3147
|
+
for (const action of entry.actions) {
|
|
3148
|
+
const expansion = vaultActionExpansion(action);
|
|
3149
|
+
for (const base of expansion.bases) {
|
|
3150
|
+
const actions = byBase.get(base) ?? [];
|
|
3151
|
+
if (!actions.includes(expansion.action)) {
|
|
3152
|
+
actions.push(expansion.action);
|
|
3153
|
+
}
|
|
3154
|
+
byBase.set(base, actions);
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
return [...byBase.entries()].map(([base, actions]) => ({
|
|
3158
|
+
...entry,
|
|
3159
|
+
service: "tinycloud.kv",
|
|
3160
|
+
path: vaultKVPath(base, entry.path),
|
|
3161
|
+
actions,
|
|
3162
|
+
skipPrefix: true
|
|
3163
|
+
}));
|
|
3164
|
+
}
|
|
3165
|
+
function vaultActionExpansion(action) {
|
|
3166
|
+
const normalized = normalizeVaultAction(action);
|
|
3167
|
+
if (normalized === "read" || normalized === "get") {
|
|
3168
|
+
return { bases: ["keys", "vault"], action: "tinycloud.kv/get" };
|
|
3169
|
+
}
|
|
3170
|
+
if (normalized === "write" || normalized === "put") {
|
|
3171
|
+
return { bases: ["keys", "vault"], action: "tinycloud.kv/put" };
|
|
3172
|
+
}
|
|
3173
|
+
if (normalized === "delete" || normalized === "del") {
|
|
3174
|
+
return { bases: ["keys", "vault"], action: "tinycloud.kv/del" };
|
|
3175
|
+
}
|
|
3176
|
+
if (normalized === "list") {
|
|
3177
|
+
return { bases: ["vault"], action: "tinycloud.kv/list" };
|
|
3178
|
+
}
|
|
3179
|
+
if (normalized === "head") {
|
|
3180
|
+
return { bases: ["vault"], action: "tinycloud.kv/get" };
|
|
3181
|
+
}
|
|
3182
|
+
if (normalized === "metadata") {
|
|
3183
|
+
return { bases: ["vault"], action: "tinycloud.kv/metadata" };
|
|
3184
|
+
}
|
|
3185
|
+
throw new ManifestValidationError(
|
|
3186
|
+
`unknown vault action ${JSON.stringify(action)}; expected read, write, delete, get, put, del, list, head, or metadata`
|
|
3187
|
+
);
|
|
3188
|
+
}
|
|
3189
|
+
function normalizeVaultAction(action) {
|
|
3190
|
+
if (action.startsWith(`${VAULT_PERMISSION_SERVICE}/`)) {
|
|
3191
|
+
return action.slice(`${VAULT_PERMISSION_SERVICE}/`.length);
|
|
3192
|
+
}
|
|
3193
|
+
if (action.startsWith("tinycloud.kv/")) {
|
|
3194
|
+
return action.slice("tinycloud.kv/".length);
|
|
3195
|
+
}
|
|
3196
|
+
if (action.includes("/")) {
|
|
3197
|
+
throw new ManifestValidationError(
|
|
3198
|
+
`unknown vault action ${JSON.stringify(action)}; expected a tinycloud.vault or tinycloud.kv action`
|
|
3199
|
+
);
|
|
3200
|
+
}
|
|
3201
|
+
return action;
|
|
3202
|
+
}
|
|
3203
|
+
function vaultKVPath(base, path) {
|
|
3204
|
+
const normalized = path.startsWith("/") ? path.slice(1) : path;
|
|
3205
|
+
return `${base}/${normalized}`;
|
|
2940
3206
|
}
|
|
2941
3207
|
function cloneResourceCapability(entry) {
|
|
2942
3208
|
return {
|
|
@@ -2944,7 +3210,8 @@ function cloneResourceCapability(entry) {
|
|
|
2944
3210
|
space: entry.space,
|
|
2945
3211
|
path: entry.path,
|
|
2946
3212
|
actions: [...entry.actions],
|
|
2947
|
-
...entry.expiryMs !== void 0 ? { expiryMs: entry.expiryMs } : {}
|
|
3213
|
+
...entry.expiryMs !== void 0 ? { expiryMs: entry.expiryMs } : {},
|
|
3214
|
+
...entry.description !== void 0 ? { description: entry.description } : {}
|
|
2948
3215
|
};
|
|
2949
3216
|
}
|
|
2950
3217
|
function clonePermissionEntry(entry) {
|
|
@@ -2954,7 +3221,8 @@ function clonePermissionEntry(entry) {
|
|
|
2954
3221
|
path: entry.path,
|
|
2955
3222
|
actions: [...entry.actions],
|
|
2956
3223
|
...entry.skipPrefix !== void 0 ? { skipPrefix: entry.skipPrefix } : {},
|
|
2957
|
-
...entry.expiry !== void 0 ? { expiry: entry.expiry } : {}
|
|
3224
|
+
...entry.expiry !== void 0 ? { expiry: entry.expiry } : {},
|
|
3225
|
+
...entry.description !== void 0 ? { description: entry.description } : {}
|
|
2958
3226
|
};
|
|
2959
3227
|
}
|
|
2960
3228
|
function dedupeResources(resources) {
|
|
@@ -2973,19 +3241,36 @@ function dedupeResources(resources) {
|
|
|
2973
3241
|
seen.add(action);
|
|
2974
3242
|
}
|
|
2975
3243
|
}
|
|
3244
|
+
if (existing.description === void 0 && resource.description !== void 0) {
|
|
3245
|
+
existing.description = resource.description;
|
|
3246
|
+
}
|
|
2976
3247
|
}
|
|
2977
3248
|
return [...byKey.values()];
|
|
2978
3249
|
}
|
|
3250
|
+
function capabilitiesReadPermission(space) {
|
|
3251
|
+
return {
|
|
3252
|
+
service: "tinycloud.capabilities",
|
|
3253
|
+
space,
|
|
3254
|
+
path: "",
|
|
3255
|
+
actions: ["tinycloud.capabilities/read"]
|
|
3256
|
+
};
|
|
3257
|
+
}
|
|
3258
|
+
function withCapabilitiesReadForSpaces(resources) {
|
|
3259
|
+
if (resources.length === 0) {
|
|
3260
|
+
return [];
|
|
3261
|
+
}
|
|
3262
|
+
const spaces = new Set(resources.map((resource) => resource.space));
|
|
3263
|
+
return dedupeResources([
|
|
3264
|
+
...resources,
|
|
3265
|
+
...[...spaces].map(capabilitiesReadPermission)
|
|
3266
|
+
]);
|
|
3267
|
+
}
|
|
2979
3268
|
function accountRegistryPermission() {
|
|
2980
3269
|
return {
|
|
2981
3270
|
service: "tinycloud.kv",
|
|
2982
3271
|
space: ACCOUNT_REGISTRY_SPACE,
|
|
2983
3272
|
path: ACCOUNT_REGISTRY_PATH,
|
|
2984
|
-
actions: [
|
|
2985
|
-
"tinycloud.kv/get",
|
|
2986
|
-
"tinycloud.kv/put",
|
|
2987
|
-
"tinycloud.kv/list"
|
|
2988
|
-
]
|
|
3273
|
+
actions: ["tinycloud.kv/get", "tinycloud.kv/put", "tinycloud.kv/list"]
|
|
2989
3274
|
};
|
|
2990
3275
|
}
|
|
2991
3276
|
function composeManifestRequest(inputs, options = {}) {
|
|
@@ -3007,6 +3292,7 @@ function composeManifestRequest(inputs, options = {}) {
|
|
|
3007
3292
|
if (includeAccountRegistryPermissions) {
|
|
3008
3293
|
resources.push(accountRegistryPermission());
|
|
3009
3294
|
}
|
|
3295
|
+
const resourcesWithImplicitCapabilities = withCapabilitiesReadForSpaces(resources);
|
|
3010
3296
|
const manifestsByAppId = /* @__PURE__ */ new Map();
|
|
3011
3297
|
for (const manifest of manifests) {
|
|
3012
3298
|
const current = manifestsByAppId.get(manifest.app_id);
|
|
@@ -3026,7 +3312,7 @@ function composeManifestRequest(inputs, options = {}) {
|
|
|
3026
3312
|
})) : [];
|
|
3027
3313
|
return {
|
|
3028
3314
|
manifests,
|
|
3029
|
-
resources:
|
|
3315
|
+
resources: resourcesWithImplicitCapabilities,
|
|
3030
3316
|
delegationTargets,
|
|
3031
3317
|
registryRecords,
|
|
3032
3318
|
expiryMs: Math.max(...resolved.map((entry) => entry.expiryMs)),
|
|
@@ -4238,6 +4524,394 @@ async function checkNodeInfo(host, sdkProtocol, fetchFn = globalThis.fetch.bind(
|
|
|
4238
4524
|
};
|
|
4239
4525
|
}
|
|
4240
4526
|
|
|
4527
|
+
// src/location.ts
|
|
4528
|
+
import { multiaddr } from "@multiformats/multiaddr";
|
|
4529
|
+
import { multiaddrToUri } from "@multiformats/multiaddr-to-uri";
|
|
4530
|
+
import { uriToMultiaddr } from "@multiformats/uri-to-multiaddr";
|
|
4531
|
+
import { ed25519 } from "@noble/curves/ed25519";
|
|
4532
|
+
import { bases } from "multiformats/basics";
|
|
4533
|
+
import { verifyMessage } from "viem";
|
|
4534
|
+
var DEFAULT_TINYCLOUD_LOCATION_REGISTRY_URL = "https://registry.tinycloud.xyz";
|
|
4535
|
+
var DEFAULT_TINYCLOUD_FALLBACK_HOST = "https://node.tinycloud.xyz";
|
|
4536
|
+
var LocationRecordValidationError = class extends Error {
|
|
4537
|
+
constructor(message) {
|
|
4538
|
+
super(`Location record validation failed: ${message}`);
|
|
4539
|
+
this.name = "LocationRecordValidationError";
|
|
4540
|
+
}
|
|
4541
|
+
};
|
|
4542
|
+
var CloudLocationResolutionError = class extends Error {
|
|
4543
|
+
constructor(subject, attempts) {
|
|
4544
|
+
super(`Unable to resolve TinyCloud location for ${subject}`);
|
|
4545
|
+
this.name = "CloudLocationResolutionError";
|
|
4546
|
+
this.attempts = attempts;
|
|
4547
|
+
}
|
|
4548
|
+
};
|
|
4549
|
+
function locationPayloadForRecord(record) {
|
|
4550
|
+
return {
|
|
4551
|
+
version: record.version,
|
|
4552
|
+
subject: record.subject,
|
|
4553
|
+
multiaddrs: [...record.multiaddrs],
|
|
4554
|
+
updated_at: record.updated_at,
|
|
4555
|
+
sequence: record.sequence
|
|
4556
|
+
};
|
|
4557
|
+
}
|
|
4558
|
+
function canonicalLocationPayload(payload) {
|
|
4559
|
+
return JSON.stringify({
|
|
4560
|
+
version: payload.version,
|
|
4561
|
+
subject: payload.subject,
|
|
4562
|
+
multiaddrs: payload.multiaddrs,
|
|
4563
|
+
updated_at: payload.updated_at,
|
|
4564
|
+
sequence: payload.sequence
|
|
4565
|
+
});
|
|
4566
|
+
}
|
|
4567
|
+
async function signLocationRecord(payload, signer) {
|
|
4568
|
+
validateLocationRecordPayload(payload);
|
|
4569
|
+
const message = canonicalLocationPayload(payload);
|
|
4570
|
+
const signature = signer.type === "did:pkh" ? await signer.signMessage(message) : base64UrlEncode2(
|
|
4571
|
+
await signer.signBytes(new TextEncoder().encode(message))
|
|
4572
|
+
);
|
|
4573
|
+
return { ...payload, signature };
|
|
4574
|
+
}
|
|
4575
|
+
function validateLocationRecordPayload(input) {
|
|
4576
|
+
if (input === null || typeof input !== "object") {
|
|
4577
|
+
throw new LocationRecordValidationError("payload must be an object");
|
|
4578
|
+
}
|
|
4579
|
+
const payload = input;
|
|
4580
|
+
if (payload.version !== 1) {
|
|
4581
|
+
throw new LocationRecordValidationError("version must be 1");
|
|
4582
|
+
}
|
|
4583
|
+
validateSubject(payload.subject);
|
|
4584
|
+
validateMultiaddrs(payload.multiaddrs);
|
|
4585
|
+
if (typeof payload.updated_at !== "string" || Number.isNaN(Date.parse(payload.updated_at))) {
|
|
4586
|
+
throw new LocationRecordValidationError(
|
|
4587
|
+
"updated_at must be an ISO timestamp"
|
|
4588
|
+
);
|
|
4589
|
+
}
|
|
4590
|
+
if (typeof payload.sequence !== "number" || !Number.isSafeInteger(payload.sequence) || payload.sequence < 0) {
|
|
4591
|
+
throw new LocationRecordValidationError(
|
|
4592
|
+
"sequence must be a non-negative safe integer"
|
|
4593
|
+
);
|
|
4594
|
+
}
|
|
4595
|
+
return {
|
|
4596
|
+
version: 1,
|
|
4597
|
+
subject: payload.subject,
|
|
4598
|
+
multiaddrs: [...payload.multiaddrs],
|
|
4599
|
+
updated_at: payload.updated_at,
|
|
4600
|
+
sequence: payload.sequence
|
|
4601
|
+
};
|
|
4602
|
+
}
|
|
4603
|
+
function validateLocationRecord(input) {
|
|
4604
|
+
const payload = validateLocationRecordPayload(input);
|
|
4605
|
+
const signature = input.signature;
|
|
4606
|
+
if (typeof signature !== "string" || signature.length === 0) {
|
|
4607
|
+
throw new LocationRecordValidationError(
|
|
4608
|
+
"signature must be a non-empty string"
|
|
4609
|
+
);
|
|
4610
|
+
}
|
|
4611
|
+
return { ...payload, signature };
|
|
4612
|
+
}
|
|
4613
|
+
async function verifyLocationRecord(input) {
|
|
4614
|
+
const record = validateLocationRecord(input);
|
|
4615
|
+
const payload = canonicalLocationPayload(locationPayloadForRecord(record));
|
|
4616
|
+
if (record.subject.startsWith("did:pkh:")) {
|
|
4617
|
+
return verifyPkhSignature(record.subject, payload, record.signature);
|
|
4618
|
+
}
|
|
4619
|
+
if (record.subject.startsWith("did:key:")) {
|
|
4620
|
+
return verifyDidKeySignature(record.subject, payload, record.signature);
|
|
4621
|
+
}
|
|
4622
|
+
return false;
|
|
4623
|
+
}
|
|
4624
|
+
async function fetchLocationRecord(registryUrl, subject, fetchFn = globalThis.fetch) {
|
|
4625
|
+
const url = `${registryUrl.replace(/\/$/, "")}/v1/locations/${encodeURIComponent(subject)}`;
|
|
4626
|
+
const response = await fetchFn(url);
|
|
4627
|
+
if (response.status === 404) {
|
|
4628
|
+
return null;
|
|
4629
|
+
}
|
|
4630
|
+
if (!response.ok) {
|
|
4631
|
+
throw new Error(`location registry returned HTTP ${response.status}`);
|
|
4632
|
+
}
|
|
4633
|
+
const body = await response.json();
|
|
4634
|
+
if (body.record === void 0) {
|
|
4635
|
+
throw new LocationRecordValidationError("registry response missing record");
|
|
4636
|
+
}
|
|
4637
|
+
return validateLocationRecord(body.record);
|
|
4638
|
+
}
|
|
4639
|
+
async function resolveCloudLocation(subject, options = {}) {
|
|
4640
|
+
validateSubject(subject);
|
|
4641
|
+
const verifyRecords = options.verifyRecords ?? true;
|
|
4642
|
+
const attempts = await Promise.all([
|
|
4643
|
+
resolveExplicit(subject, options.explicitMultiaddrs),
|
|
4644
|
+
resolveBlockchain(subject, options.blockchain, verifyRecords),
|
|
4645
|
+
resolveCentralized(subject, options, verifyRecords),
|
|
4646
|
+
resolveFallback(subject, options.fallbackMultiaddrs)
|
|
4647
|
+
]);
|
|
4648
|
+
const winner = attempts.find((attempt) => attempt.candidate)?.candidate;
|
|
4649
|
+
if (!winner) {
|
|
4650
|
+
throw new CloudLocationResolutionError(subject, attempts);
|
|
4651
|
+
}
|
|
4652
|
+
return {
|
|
4653
|
+
subject,
|
|
4654
|
+
source: winner.source,
|
|
4655
|
+
multiaddrs: [...winner.multiaddrs],
|
|
4656
|
+
...winner.record ? { record: winner.record } : {},
|
|
4657
|
+
attempts,
|
|
4658
|
+
resolvedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4659
|
+
};
|
|
4660
|
+
}
|
|
4661
|
+
async function resolveTinyCloudHosts(subject, options = {}) {
|
|
4662
|
+
const location = await resolveCloudLocation(subject, {
|
|
4663
|
+
explicitMultiaddrs: hostsToMultiaddrs(options.explicitHosts),
|
|
4664
|
+
blockchain: options.blockchain,
|
|
4665
|
+
centralizedRegistryUrl: options.registryUrl === null ? void 0 : options.registryUrl ?? DEFAULT_TINYCLOUD_LOCATION_REGISTRY_URL,
|
|
4666
|
+
fallbackMultiaddrs: hostsToMultiaddrs(
|
|
4667
|
+
options.fallbackHosts === null ? void 0 : options.fallbackHosts ?? [DEFAULT_TINYCLOUD_FALLBACK_HOST]
|
|
4668
|
+
),
|
|
4669
|
+
fetch: options.fetch,
|
|
4670
|
+
verifyRecords: options.verifyRecords
|
|
4671
|
+
});
|
|
4672
|
+
return {
|
|
4673
|
+
hosts: location.multiaddrs.map((addr) => multiaddrToHttpUrl(addr)),
|
|
4674
|
+
location
|
|
4675
|
+
};
|
|
4676
|
+
}
|
|
4677
|
+
function multiaddrToHttpUrl(input) {
|
|
4678
|
+
const uri = multiaddrToUri(multiaddr(input));
|
|
4679
|
+
if (!uri.startsWith("http://") && !uri.startsWith("https://")) {
|
|
4680
|
+
throw new LocationRecordValidationError(
|
|
4681
|
+
`multiaddr does not resolve to http/https: ${input}`
|
|
4682
|
+
);
|
|
4683
|
+
}
|
|
4684
|
+
return uri;
|
|
4685
|
+
}
|
|
4686
|
+
function httpUrlToMultiaddr(input) {
|
|
4687
|
+
const url = new URL(input);
|
|
4688
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
4689
|
+
throw new LocationRecordValidationError("URL must use http or https");
|
|
4690
|
+
}
|
|
4691
|
+
return uriToMultiaddr(url.toString()).toString();
|
|
4692
|
+
}
|
|
4693
|
+
function hostsToMultiaddrs(hosts) {
|
|
4694
|
+
if (hosts === void 0 || hosts.length === 0) {
|
|
4695
|
+
return void 0;
|
|
4696
|
+
}
|
|
4697
|
+
return hosts.map(
|
|
4698
|
+
(host) => host.startsWith("/") ? host : httpUrlToMultiaddr(host)
|
|
4699
|
+
);
|
|
4700
|
+
}
|
|
4701
|
+
async function resolveExplicit(subject, multiaddrs) {
|
|
4702
|
+
return resolveAttempt("explicit", async () => {
|
|
4703
|
+
if (multiaddrs === void 0 || multiaddrs.length === 0) {
|
|
4704
|
+
return null;
|
|
4705
|
+
}
|
|
4706
|
+
return toCandidate(subject, "explicit", multiaddrs, false);
|
|
4707
|
+
});
|
|
4708
|
+
}
|
|
4709
|
+
async function resolveBlockchain(subject, resolver, verifyRecords) {
|
|
4710
|
+
return resolveAttempt("blockchain", async () => {
|
|
4711
|
+
if (!resolver) {
|
|
4712
|
+
return null;
|
|
4713
|
+
}
|
|
4714
|
+
return toCandidate(
|
|
4715
|
+
subject,
|
|
4716
|
+
"blockchain",
|
|
4717
|
+
await resolver(subject),
|
|
4718
|
+
verifyRecords
|
|
4719
|
+
);
|
|
4720
|
+
});
|
|
4721
|
+
}
|
|
4722
|
+
async function resolveCentralized(subject, options, verifyRecords) {
|
|
4723
|
+
return resolveAttempt("centralized", async () => {
|
|
4724
|
+
if (!options.centralizedRegistryUrl) {
|
|
4725
|
+
return null;
|
|
4726
|
+
}
|
|
4727
|
+
const record = await fetchLocationRecord(
|
|
4728
|
+
options.centralizedRegistryUrl,
|
|
4729
|
+
subject,
|
|
4730
|
+
options.fetch
|
|
4731
|
+
);
|
|
4732
|
+
return toCandidate(subject, "centralized", record, verifyRecords);
|
|
4733
|
+
});
|
|
4734
|
+
}
|
|
4735
|
+
async function resolveFallback(subject, multiaddrs) {
|
|
4736
|
+
return resolveAttempt("fallback", async () => {
|
|
4737
|
+
if (multiaddrs === void 0 || multiaddrs.length === 0) {
|
|
4738
|
+
return null;
|
|
4739
|
+
}
|
|
4740
|
+
return toCandidate(subject, "fallback", multiaddrs, false);
|
|
4741
|
+
});
|
|
4742
|
+
}
|
|
4743
|
+
async function resolveAttempt(source, resolve) {
|
|
4744
|
+
try {
|
|
4745
|
+
const candidate = await resolve();
|
|
4746
|
+
return candidate ? { source, candidate } : { source };
|
|
4747
|
+
} catch (error) {
|
|
4748
|
+
return {
|
|
4749
|
+
source,
|
|
4750
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
4751
|
+
};
|
|
4752
|
+
}
|
|
4753
|
+
}
|
|
4754
|
+
async function toCandidate(subject, source, input, verifyRecord) {
|
|
4755
|
+
if (input === null || input === void 0) {
|
|
4756
|
+
return null;
|
|
4757
|
+
}
|
|
4758
|
+
if (Array.isArray(input)) {
|
|
4759
|
+
validateMultiaddrs(input);
|
|
4760
|
+
return { source, multiaddrs: [...input] };
|
|
4761
|
+
}
|
|
4762
|
+
const maybeRecord = input;
|
|
4763
|
+
if (maybeRecord.version === 1 && maybeRecord.signature !== void 0) {
|
|
4764
|
+
const record = validateLocationRecord(input);
|
|
4765
|
+
if (record.subject !== subject) {
|
|
4766
|
+
throw new LocationRecordValidationError(
|
|
4767
|
+
"location record subject does not match requested subject"
|
|
4768
|
+
);
|
|
4769
|
+
}
|
|
4770
|
+
if (verifyRecord && !await verifyLocationRecord(record)) {
|
|
4771
|
+
throw new LocationRecordValidationError(
|
|
4772
|
+
"location record signature is invalid"
|
|
4773
|
+
);
|
|
4774
|
+
}
|
|
4775
|
+
return { source, multiaddrs: [...record.multiaddrs], record };
|
|
4776
|
+
}
|
|
4777
|
+
const candidateInput = input;
|
|
4778
|
+
if (!Array.isArray(candidateInput.multiaddrs)) {
|
|
4779
|
+
throw new LocationRecordValidationError(
|
|
4780
|
+
"candidate multiaddrs must be an array"
|
|
4781
|
+
);
|
|
4782
|
+
}
|
|
4783
|
+
validateMultiaddrs(candidateInput.multiaddrs);
|
|
4784
|
+
if (candidateInput.record !== void 0) {
|
|
4785
|
+
const record = validateLocationRecord(candidateInput.record);
|
|
4786
|
+
if (record.subject !== subject) {
|
|
4787
|
+
throw new LocationRecordValidationError(
|
|
4788
|
+
"location record subject does not match requested subject"
|
|
4789
|
+
);
|
|
4790
|
+
}
|
|
4791
|
+
if (verifyRecord && !await verifyLocationRecord(record)) {
|
|
4792
|
+
throw new LocationRecordValidationError(
|
|
4793
|
+
"location record signature is invalid"
|
|
4794
|
+
);
|
|
4795
|
+
}
|
|
4796
|
+
return { source, multiaddrs: [...candidateInput.multiaddrs], record };
|
|
4797
|
+
}
|
|
4798
|
+
return { source, multiaddrs: [...candidateInput.multiaddrs] };
|
|
4799
|
+
}
|
|
4800
|
+
function validateSubject(subject) {
|
|
4801
|
+
if (typeof subject !== "string" || subject.length === 0) {
|
|
4802
|
+
throw new LocationRecordValidationError(
|
|
4803
|
+
"subject must be a non-empty string"
|
|
4804
|
+
);
|
|
4805
|
+
}
|
|
4806
|
+
if (!subject.startsWith("did:pkh:") && !subject.startsWith("did:key:")) {
|
|
4807
|
+
throw new LocationRecordValidationError(
|
|
4808
|
+
"subject must be did:pkh or did:key"
|
|
4809
|
+
);
|
|
4810
|
+
}
|
|
4811
|
+
}
|
|
4812
|
+
function validateMultiaddrs(input) {
|
|
4813
|
+
if (!Array.isArray(input)) {
|
|
4814
|
+
throw new LocationRecordValidationError("multiaddrs must be an array");
|
|
4815
|
+
}
|
|
4816
|
+
for (const addr of input) {
|
|
4817
|
+
if (typeof addr !== "string" || addr.length === 0) {
|
|
4818
|
+
throw new LocationRecordValidationError(
|
|
4819
|
+
"multiaddr entries must be non-empty strings"
|
|
4820
|
+
);
|
|
4821
|
+
}
|
|
4822
|
+
try {
|
|
4823
|
+
multiaddr(addr);
|
|
4824
|
+
} catch {
|
|
4825
|
+
throw new LocationRecordValidationError(`invalid multiaddr: ${addr}`);
|
|
4826
|
+
}
|
|
4827
|
+
}
|
|
4828
|
+
}
|
|
4829
|
+
async function verifyPkhSignature(did, payload, signature) {
|
|
4830
|
+
const address = did.split(":").at(-1);
|
|
4831
|
+
if (!address || !/^0x[a-fA-F0-9]{40}$/.test(address)) {
|
|
4832
|
+
throw new LocationRecordValidationError(
|
|
4833
|
+
"did:pkh subject must end with an EVM address"
|
|
4834
|
+
);
|
|
4835
|
+
}
|
|
4836
|
+
if (!/^0x[0-9a-fA-F]+$/.test(signature)) {
|
|
4837
|
+
throw new LocationRecordValidationError("did:pkh signature must be hex");
|
|
4838
|
+
}
|
|
4839
|
+
return verifyMessage({
|
|
4840
|
+
address,
|
|
4841
|
+
message: payload,
|
|
4842
|
+
signature
|
|
4843
|
+
});
|
|
4844
|
+
}
|
|
4845
|
+
function verifyDidKeySignature(did, payload, signature) {
|
|
4846
|
+
const publicKey = ed25519PublicKeyFromDidKey(did);
|
|
4847
|
+
const signatureBytes = decodeBase64Url(signature);
|
|
4848
|
+
if (signatureBytes.length !== 64) {
|
|
4849
|
+
throw new LocationRecordValidationError(
|
|
4850
|
+
"did:key signature must be a base64url Ed25519 signature"
|
|
4851
|
+
);
|
|
4852
|
+
}
|
|
4853
|
+
return ed25519.verify(
|
|
4854
|
+
signatureBytes,
|
|
4855
|
+
new TextEncoder().encode(payload),
|
|
4856
|
+
publicKey
|
|
4857
|
+
);
|
|
4858
|
+
}
|
|
4859
|
+
function ed25519PublicKeyFromDidKey(did) {
|
|
4860
|
+
const identifier = did.slice("did:key:".length);
|
|
4861
|
+
if (!identifier.startsWith("z")) {
|
|
4862
|
+
throw new LocationRecordValidationError(
|
|
4863
|
+
"did:key must use base58btc multibase"
|
|
4864
|
+
);
|
|
4865
|
+
}
|
|
4866
|
+
const bytes = bases.base58btc.decode(identifier);
|
|
4867
|
+
if (bytes.length !== 34 || bytes[0] !== 237 || bytes[1] !== 1) {
|
|
4868
|
+
throw new LocationRecordValidationError(
|
|
4869
|
+
"did:key must be an Ed25519 public key"
|
|
4870
|
+
);
|
|
4871
|
+
}
|
|
4872
|
+
return bytes.slice(2);
|
|
4873
|
+
}
|
|
4874
|
+
function base64UrlEncode2(bytes) {
|
|
4875
|
+
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
4876
|
+
let output = "";
|
|
4877
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
4878
|
+
const a = bytes[i];
|
|
4879
|
+
const b = bytes[i + 1];
|
|
4880
|
+
const c = bytes[i + 2];
|
|
4881
|
+
const triplet = a << 16 | (b ?? 0) << 8 | (c ?? 0);
|
|
4882
|
+
output += alphabet[triplet >> 18 & 63];
|
|
4883
|
+
output += alphabet[triplet >> 12 & 63];
|
|
4884
|
+
if (i + 1 < bytes.length) {
|
|
4885
|
+
output += alphabet[triplet >> 6 & 63];
|
|
4886
|
+
}
|
|
4887
|
+
if (i + 2 < bytes.length) {
|
|
4888
|
+
output += alphabet[triplet & 63];
|
|
4889
|
+
}
|
|
4890
|
+
}
|
|
4891
|
+
return output;
|
|
4892
|
+
}
|
|
4893
|
+
function decodeBase64Url(value) {
|
|
4894
|
+
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
4895
|
+
const bytes = [];
|
|
4896
|
+
let buffer = 0;
|
|
4897
|
+
let bits = 0;
|
|
4898
|
+
for (const char of value) {
|
|
4899
|
+
const index = alphabet.indexOf(char);
|
|
4900
|
+
if (index < 0) {
|
|
4901
|
+
throw new LocationRecordValidationError(
|
|
4902
|
+
"did:key signature must be base64url"
|
|
4903
|
+
);
|
|
4904
|
+
}
|
|
4905
|
+
buffer = buffer << 6 | index;
|
|
4906
|
+
bits += 6;
|
|
4907
|
+
if (bits >= 8) {
|
|
4908
|
+
bits -= 8;
|
|
4909
|
+
bytes.push(buffer >> bits & 255);
|
|
4910
|
+
}
|
|
4911
|
+
}
|
|
4912
|
+
return Uint8Array.from(bytes);
|
|
4913
|
+
}
|
|
4914
|
+
|
|
4241
4915
|
// src/capabilities.ts
|
|
4242
4916
|
var PermissionNotInManifestError = class extends Error {
|
|
4243
4917
|
constructor(missing, granted) {
|
|
@@ -4358,10 +5032,13 @@ export {
|
|
|
4358
5032
|
CapabilityKeyRegistry,
|
|
4359
5033
|
CapabilityKeyRegistryErrorCodes,
|
|
4360
5034
|
ClientSessionSchema,
|
|
5035
|
+
CloudLocationResolutionError,
|
|
4361
5036
|
DEFAULT_DEFAULTS,
|
|
4362
5037
|
DEFAULT_EXPIRY,
|
|
4363
5038
|
DEFAULT_MANIFEST_SPACE,
|
|
4364
5039
|
DEFAULT_MANIFEST_VERSION,
|
|
5040
|
+
DEFAULT_TINYCLOUD_FALLBACK_HOST,
|
|
5041
|
+
DEFAULT_TINYCLOUD_LOCATION_REGISTRY_URL,
|
|
4365
5042
|
DataVaultService,
|
|
4366
5043
|
DatabaseHandle,
|
|
4367
5044
|
DelegationErrorCodes,
|
|
@@ -4373,14 +5050,17 @@ export {
|
|
|
4373
5050
|
ErrorCodes2 as ErrorCodes,
|
|
4374
5051
|
HooksService2 as HooksService,
|
|
4375
5052
|
KVService2 as KVService,
|
|
5053
|
+
LocationRecordValidationError,
|
|
4376
5054
|
ManifestValidationError,
|
|
4377
5055
|
PermissionNotInManifestError,
|
|
4378
5056
|
PrefixedKVService,
|
|
4379
5057
|
ProtocolMismatchError,
|
|
5058
|
+
SECRET_NAME_RE2 as SECRET_NAME_RE,
|
|
4380
5059
|
SERVICE_LONG_TO_SHORT,
|
|
4381
5060
|
SERVICE_SHORT_TO_LONG,
|
|
4382
5061
|
SQLAction,
|
|
4383
5062
|
SQLService2 as SQLService,
|
|
5063
|
+
SecretsService,
|
|
4384
5064
|
ServiceContext2 as ServiceContext,
|
|
4385
5065
|
SessionExpiredError,
|
|
4386
5066
|
SharingService,
|
|
@@ -4392,12 +5072,15 @@ export {
|
|
|
4392
5072
|
SpaceService,
|
|
4393
5073
|
TinyCloud,
|
|
4394
5074
|
UnsupportedFeatureError,
|
|
5075
|
+
VAULT_PERMISSION_SERVICE,
|
|
4395
5076
|
VaultHeaders,
|
|
4396
5077
|
VaultPublicSpaceKVActions,
|
|
4397
5078
|
VersionCheckError,
|
|
4398
5079
|
activateSessionWithHost,
|
|
4399
5080
|
applyPrefix,
|
|
4400
5081
|
buildSpaceUri,
|
|
5082
|
+
canonicalLocationPayload,
|
|
5083
|
+
canonicalizeSecretScope,
|
|
4401
5084
|
checkNodeInfo,
|
|
4402
5085
|
composeManifestRequest,
|
|
4403
5086
|
createCapabilityKeyRegistry,
|
|
@@ -4409,23 +5092,36 @@ export {
|
|
|
4409
5092
|
defaultSpaceCreationHandler,
|
|
4410
5093
|
err4 as err,
|
|
4411
5094
|
expandActionShortNames,
|
|
5095
|
+
expandPermissionEntries,
|
|
5096
|
+
expandPermissionEntry,
|
|
5097
|
+
fetchLocationRecord,
|
|
4412
5098
|
fetchPeerId,
|
|
5099
|
+
httpUrlToMultiaddr,
|
|
4413
5100
|
isCapabilitySubset,
|
|
4414
5101
|
loadManifest,
|
|
5102
|
+
locationPayloadForRecord,
|
|
4415
5103
|
makePublicSpaceId,
|
|
4416
5104
|
manifestAbilitiesUnion,
|
|
5105
|
+
multiaddrToHttpUrl,
|
|
4417
5106
|
normalizeDefaults,
|
|
4418
5107
|
ok4 as ok,
|
|
4419
5108
|
parseExpiry,
|
|
4420
5109
|
parseRecapCapabilities,
|
|
4421
5110
|
parseSpaceUri,
|
|
5111
|
+
resolveCloudLocation,
|
|
4422
5112
|
resolveManifest,
|
|
5113
|
+
resolveSecretPath2 as resolveSecretPath,
|
|
5114
|
+
resolveTinyCloudHosts,
|
|
4423
5115
|
resourceCapabilitiesToAbilitiesMap,
|
|
4424
5116
|
resourceCapabilitiesToSpaceAbilitiesMap,
|
|
4425
5117
|
serviceError4 as serviceError,
|
|
5118
|
+
signLocationRecord,
|
|
4426
5119
|
submitHostDelegation,
|
|
4427
5120
|
validateClientSession,
|
|
5121
|
+
validateLocationRecord,
|
|
5122
|
+
validateLocationRecordPayload,
|
|
4428
5123
|
validateManifest,
|
|
4429
|
-
validatePersistedSessionData
|
|
5124
|
+
validatePersistedSessionData,
|
|
5125
|
+
verifyLocationRecord
|
|
4430
5126
|
};
|
|
4431
5127
|
//# sourceMappingURL=index.js.map
|