@tinycloud/sdk-core 2.1.0-beta.1 → 2.1.0-beta.3
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 +520 -394
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +161 -17
- package/dist/index.d.ts +161 -17
- package/dist/index.js +518 -394
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -493,6 +493,16 @@ var DelegationApiResponseSchema = z3.object({
|
|
|
493
493
|
/** CID of the created delegation */
|
|
494
494
|
cid: z3.string().optional()
|
|
495
495
|
});
|
|
496
|
+
var DelegatedResourceSchema = z3.object({
|
|
497
|
+
/** Short-form service name, e.g. "kv", "sql", "duckdb", "capabilities", "hooks". */
|
|
498
|
+
service: z3.string(),
|
|
499
|
+
/** Full space id string, e.g. "tinycloud:pkh:eip155:1:0x....:default". */
|
|
500
|
+
space: z3.string(),
|
|
501
|
+
/** Resource path; empty string when the resource URI had no path segment. */
|
|
502
|
+
path: z3.string(),
|
|
503
|
+
/** Full-URN ability strings, e.g. ["tinycloud.kv/get", "tinycloud.kv/put"]. */
|
|
504
|
+
actions: z3.array(z3.string())
|
|
505
|
+
});
|
|
496
506
|
var CreateDelegationWasmParamsSchema = z3.object({
|
|
497
507
|
/** The session containing delegation credentials */
|
|
498
508
|
session: z3.unknown().refine(
|
|
@@ -503,10 +513,23 @@ var CreateDelegationWasmParamsSchema = z3.object({
|
|
|
503
513
|
delegateDID: z3.string(),
|
|
504
514
|
/** Space ID this delegation applies to */
|
|
505
515
|
spaceId: z3.string(),
|
|
506
|
-
/**
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
516
|
+
/**
|
|
517
|
+
* Multi-resource abilities map: short-service → path → full-URN actions.
|
|
518
|
+
* Matches the shape accepted by `prepareSession`.
|
|
519
|
+
*
|
|
520
|
+
* Example:
|
|
521
|
+
* ```
|
|
522
|
+
* {
|
|
523
|
+
* kv: {
|
|
524
|
+
* "com.listen.app/": ["tinycloud.kv/get", "tinycloud.kv/put"]
|
|
525
|
+
* },
|
|
526
|
+
* sql: {
|
|
527
|
+
* "com.listen.app/data.sqlite": ["tinycloud.sql/read"]
|
|
528
|
+
* }
|
|
529
|
+
* }
|
|
530
|
+
* ```
|
|
531
|
+
*/
|
|
532
|
+
abilities: z3.record(z3.string(), z3.record(z3.string(), z3.array(z3.string()))),
|
|
510
533
|
/** Expiration time in seconds since Unix epoch */
|
|
511
534
|
expirationSecs: z3.number(),
|
|
512
535
|
/** Optional not-before time in seconds since Unix epoch */
|
|
@@ -519,12 +542,13 @@ var CreateDelegationWasmResultSchema = z3.object({
|
|
|
519
542
|
cid: z3.string(),
|
|
520
543
|
/** DID of the delegate */
|
|
521
544
|
delegateDID: z3.string(),
|
|
522
|
-
/** Resource path the delegation grants access to */
|
|
523
|
-
path: z3.string(),
|
|
524
|
-
/** Actions the delegation authorizes */
|
|
525
|
-
actions: z3.array(z3.string()),
|
|
526
545
|
/** Expiration time */
|
|
527
|
-
expiry: z3.coerce.date()
|
|
546
|
+
expiry: z3.coerce.date(),
|
|
547
|
+
/**
|
|
548
|
+
* All (service, space, path, actions) entries granted by this delegation.
|
|
549
|
+
* Always non-empty on success.
|
|
550
|
+
*/
|
|
551
|
+
resources: z3.array(DelegatedResourceSchema)
|
|
528
552
|
});
|
|
529
553
|
|
|
530
554
|
// src/spaces/spaces.schema.ts
|
|
@@ -2631,104 +2655,449 @@ function validateEncodedShareData(data) {
|
|
|
2631
2655
|
return { ok: true, data: result.data };
|
|
2632
2656
|
}
|
|
2633
2657
|
|
|
2634
|
-
// src/
|
|
2635
|
-
|
|
2636
|
-
var
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2658
|
+
// src/manifest.ts
|
|
2659
|
+
import ms from "ms";
|
|
2660
|
+
var ManifestValidationError = class extends Error {
|
|
2661
|
+
constructor(message) {
|
|
2662
|
+
super(`Manifest validation failed: ${message}`);
|
|
2663
|
+
this.name = "ManifestValidationError";
|
|
2664
|
+
}
|
|
2665
|
+
};
|
|
2666
|
+
var DEFAULT_EXPIRY = "30d";
|
|
2667
|
+
var DEFAULT_DEFAULTS = true;
|
|
2668
|
+
var SERVICE_SHORT_TO_LONG = Object.freeze({
|
|
2669
|
+
kv: "tinycloud.kv",
|
|
2670
|
+
sql: "tinycloud.sql",
|
|
2671
|
+
duckdb: "tinycloud.duckdb",
|
|
2672
|
+
capabilities: "tinycloud.capabilities",
|
|
2673
|
+
hooks: "tinycloud.hooks"
|
|
2674
|
+
});
|
|
2675
|
+
var SERVICE_LONG_TO_SHORT = Object.freeze(
|
|
2676
|
+
Object.fromEntries(
|
|
2677
|
+
Object.entries(SERVICE_SHORT_TO_LONG).map(([s, l]) => [l, s])
|
|
2678
|
+
)
|
|
2679
|
+
);
|
|
2680
|
+
var DEFAULT_STANDARD_ENTRIES = [
|
|
2681
|
+
{
|
|
2682
|
+
service: "tinycloud.kv",
|
|
2683
|
+
space: "default",
|
|
2684
|
+
path: "/",
|
|
2685
|
+
actions: ["get", "put", "del", "list", "metadata"]
|
|
2686
|
+
},
|
|
2687
|
+
{
|
|
2688
|
+
service: "tinycloud.sql",
|
|
2689
|
+
space: "default",
|
|
2690
|
+
path: "/",
|
|
2691
|
+
actions: ["read", "write"]
|
|
2692
|
+
},
|
|
2693
|
+
{
|
|
2694
|
+
service: "tinycloud.capabilities",
|
|
2695
|
+
space: "default",
|
|
2696
|
+
path: "/",
|
|
2697
|
+
actions: ["read"]
|
|
2698
|
+
}
|
|
2699
|
+
];
|
|
2700
|
+
var DEFAULT_ADMIN_ENTRIES = [
|
|
2701
|
+
{
|
|
2702
|
+
service: "tinycloud.kv",
|
|
2703
|
+
space: "default",
|
|
2704
|
+
path: "/",
|
|
2705
|
+
actions: ["get", "put", "del", "list", "metadata"]
|
|
2706
|
+
},
|
|
2707
|
+
{
|
|
2708
|
+
service: "tinycloud.sql",
|
|
2709
|
+
space: "default",
|
|
2710
|
+
path: "/",
|
|
2711
|
+
actions: ["read", "write", "ddl"]
|
|
2712
|
+
},
|
|
2713
|
+
{
|
|
2714
|
+
service: "tinycloud.capabilities",
|
|
2715
|
+
space: "default",
|
|
2716
|
+
path: "/",
|
|
2717
|
+
actions: ["read", "admin"]
|
|
2718
|
+
}
|
|
2719
|
+
];
|
|
2720
|
+
var DEFAULT_ALL_ENTRIES = [
|
|
2721
|
+
{
|
|
2722
|
+
service: "tinycloud.kv",
|
|
2723
|
+
space: "default",
|
|
2724
|
+
path: "/",
|
|
2725
|
+
actions: ["get", "put", "del", "list", "metadata"]
|
|
2726
|
+
},
|
|
2727
|
+
{
|
|
2728
|
+
service: "tinycloud.sql",
|
|
2729
|
+
space: "default",
|
|
2730
|
+
path: "/",
|
|
2731
|
+
actions: ["read", "write", "ddl"]
|
|
2732
|
+
},
|
|
2733
|
+
{
|
|
2734
|
+
service: "tinycloud.duckdb",
|
|
2735
|
+
space: "default",
|
|
2736
|
+
path: "/",
|
|
2737
|
+
actions: ["read", "write"]
|
|
2738
|
+
},
|
|
2739
|
+
{
|
|
2740
|
+
service: "tinycloud.capabilities",
|
|
2741
|
+
space: "default",
|
|
2742
|
+
path: "/",
|
|
2743
|
+
actions: ["read", "admin"]
|
|
2744
|
+
}
|
|
2745
|
+
];
|
|
2746
|
+
function parseExpiry(duration) {
|
|
2747
|
+
if (typeof duration !== "string" || duration.length === 0) {
|
|
2748
|
+
throw new ManifestValidationError(
|
|
2749
|
+
`expiry must be a non-empty duration string (got ${JSON.stringify(duration)})`
|
|
2750
|
+
);
|
|
2751
|
+
}
|
|
2752
|
+
const parsed = ms(
|
|
2753
|
+
duration
|
|
2754
|
+
);
|
|
2755
|
+
if (typeof parsed !== "number" || !Number.isFinite(parsed) || parsed <= 0) {
|
|
2756
|
+
throw new ManifestValidationError(
|
|
2757
|
+
`invalid expiry duration: ${JSON.stringify(duration)}`
|
|
2758
|
+
);
|
|
2759
|
+
}
|
|
2760
|
+
return parsed;
|
|
2646
2761
|
}
|
|
2647
|
-
function
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
}
|
|
2654
|
-
|
|
2762
|
+
function expandActionShortNames(service, actions) {
|
|
2763
|
+
return actions.map((a) => {
|
|
2764
|
+
if (a.includes("/")) {
|
|
2765
|
+
return a;
|
|
2766
|
+
}
|
|
2767
|
+
return `${service}/${a}`;
|
|
2768
|
+
});
|
|
2769
|
+
}
|
|
2770
|
+
function applyPrefix(prefix, path, skipPrefix) {
|
|
2771
|
+
if (skipPrefix) {
|
|
2772
|
+
return path;
|
|
2655
2773
|
}
|
|
2656
|
-
|
|
2774
|
+
if (prefix === "") {
|
|
2775
|
+
return path;
|
|
2776
|
+
}
|
|
2777
|
+
if (path.startsWith("/")) {
|
|
2778
|
+
return `${prefix}${path}`;
|
|
2779
|
+
}
|
|
2780
|
+
return `${prefix}/${path}`;
|
|
2657
2781
|
}
|
|
2658
|
-
function
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2782
|
+
async function loadManifest(url) {
|
|
2783
|
+
const fetchFn = globalThis.fetch;
|
|
2784
|
+
if (typeof fetchFn !== "function") {
|
|
2785
|
+
throw new ManifestValidationError(
|
|
2786
|
+
"loadManifest requires a global fetch; pass the manifest object directly on runtimes without fetch"
|
|
2787
|
+
);
|
|
2662
2788
|
}
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
throw new Error("No base64 decoding available");
|
|
2789
|
+
const res = await fetchFn(url);
|
|
2790
|
+
if (!res.ok) {
|
|
2791
|
+
throw new ManifestValidationError(
|
|
2792
|
+
`failed to fetch manifest from ${url}: HTTP ${res.status}`
|
|
2793
|
+
);
|
|
2669
2794
|
}
|
|
2795
|
+
const json = await res.json();
|
|
2796
|
+
return validateManifest(json);
|
|
2670
2797
|
}
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
*/
|
|
2675
|
-
constructor(config) {
|
|
2676
|
-
this.hosts = config.hosts;
|
|
2677
|
-
this.session = config.session;
|
|
2678
|
-
this.invoke = config.invoke;
|
|
2679
|
-
this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
2680
|
-
this.keyProvider = config.keyProvider;
|
|
2681
|
-
this.registry = config.registry;
|
|
2682
|
-
this.delegationManager = config.delegationManager;
|
|
2683
|
-
this.createKVService = config.createKVService;
|
|
2684
|
-
this.baseUrl = (config.baseUrl ?? "").replace(/\/$/, "");
|
|
2685
|
-
this.createDelegationFn = config.createDelegation;
|
|
2686
|
-
this.createDelegationWasmFn = config.createDelegationWasm;
|
|
2687
|
-
this.pathPrefix = config.pathPrefix ?? "";
|
|
2688
|
-
this.sessionExpiry = config.sessionExpiry;
|
|
2689
|
-
this.onRootDelegationNeeded = config.onRootDelegationNeeded;
|
|
2798
|
+
function validateManifest(input) {
|
|
2799
|
+
if (input === null || typeof input !== "object") {
|
|
2800
|
+
throw new ManifestValidationError("manifest must be an object");
|
|
2690
2801
|
}
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
get host() {
|
|
2695
|
-
return this.hosts[0];
|
|
2802
|
+
const m = input;
|
|
2803
|
+
if (typeof m.id !== "string" || m.id.length === 0) {
|
|
2804
|
+
throw new ManifestValidationError("manifest.id is required and must be a non-empty string");
|
|
2696
2805
|
}
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
*/
|
|
2700
|
-
updateSession(session) {
|
|
2701
|
-
this.session = session;
|
|
2806
|
+
if (typeof m.name !== "string" || m.name.length === 0) {
|
|
2807
|
+
throw new ManifestValidationError("manifest.name is required and must be a non-empty string");
|
|
2702
2808
|
}
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
this.session = config.session;
|
|
2710
|
-
}
|
|
2711
|
-
if (config.delegationManager !== void 0) {
|
|
2712
|
-
this.delegationManager = config.delegationManager;
|
|
2713
|
-
}
|
|
2714
|
-
if (config.createDelegation !== void 0) {
|
|
2715
|
-
this.createDelegationFn = config.createDelegation;
|
|
2716
|
-
}
|
|
2717
|
-
if (config.createDelegationWasm !== void 0) {
|
|
2718
|
-
this.createDelegationWasmFn = config.createDelegationWasm;
|
|
2719
|
-
}
|
|
2720
|
-
if (config.sessionExpiry !== void 0) {
|
|
2721
|
-
this.sessionExpiry = config.sessionExpiry;
|
|
2722
|
-
}
|
|
2723
|
-
if (config.onRootDelegationNeeded !== void 0) {
|
|
2724
|
-
this.onRootDelegationNeeded = config.onRootDelegationNeeded;
|
|
2809
|
+
if (m.expiry !== void 0) {
|
|
2810
|
+
parseExpiry(m.expiry);
|
|
2811
|
+
}
|
|
2812
|
+
if (m.permissions !== void 0) {
|
|
2813
|
+
if (!Array.isArray(m.permissions)) {
|
|
2814
|
+
throw new ManifestValidationError("manifest.permissions must be an array");
|
|
2725
2815
|
}
|
|
2816
|
+
m.permissions.forEach(
|
|
2817
|
+
(p, i) => validatePermissionEntry(p, `permissions[${i}]`)
|
|
2818
|
+
);
|
|
2726
2819
|
}
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2820
|
+
if (m.delegations !== void 0) {
|
|
2821
|
+
if (!Array.isArray(m.delegations)) {
|
|
2822
|
+
throw new ManifestValidationError("manifest.delegations must be an array");
|
|
2823
|
+
}
|
|
2824
|
+
m.delegations.forEach((d, i) => {
|
|
2825
|
+
if (typeof d?.to !== "string" || d.to.length === 0) {
|
|
2826
|
+
throw new ManifestValidationError(
|
|
2827
|
+
`delegations[${i}].to is required and must be a non-empty DID string`
|
|
2828
|
+
);
|
|
2829
|
+
}
|
|
2830
|
+
if (d.expiry !== void 0) {
|
|
2831
|
+
parseExpiry(d.expiry);
|
|
2832
|
+
}
|
|
2833
|
+
if (!Array.isArray(d.permissions)) {
|
|
2834
|
+
throw new ManifestValidationError(
|
|
2835
|
+
`delegations[${i}].permissions must be an array`
|
|
2836
|
+
);
|
|
2837
|
+
}
|
|
2838
|
+
d.permissions.forEach(
|
|
2839
|
+
(p, j) => validatePermissionEntry(p, `delegations[${i}].permissions[${j}]`)
|
|
2840
|
+
);
|
|
2841
|
+
});
|
|
2842
|
+
}
|
|
2843
|
+
return m;
|
|
2844
|
+
}
|
|
2845
|
+
function validatePermissionEntry(p, path) {
|
|
2846
|
+
if (p === null || typeof p !== "object") {
|
|
2847
|
+
throw new ManifestValidationError(`${path} must be an object`);
|
|
2848
|
+
}
|
|
2849
|
+
const entry = p;
|
|
2850
|
+
if (typeof entry.service !== "string" || entry.service.length === 0) {
|
|
2851
|
+
throw new ManifestValidationError(`${path}.service is required`);
|
|
2852
|
+
}
|
|
2853
|
+
if (typeof entry.space !== "string" || entry.space.length === 0) {
|
|
2854
|
+
throw new ManifestValidationError(`${path}.space is required`);
|
|
2855
|
+
}
|
|
2856
|
+
if (typeof entry.path !== "string") {
|
|
2857
|
+
throw new ManifestValidationError(
|
|
2858
|
+
`${path}.path is required (use "" or "/" for root)`
|
|
2859
|
+
);
|
|
2860
|
+
}
|
|
2861
|
+
if (!Array.isArray(entry.actions) || entry.actions.length === 0) {
|
|
2862
|
+
throw new ManifestValidationError(
|
|
2863
|
+
`${path}.actions must be a non-empty array`
|
|
2864
|
+
);
|
|
2865
|
+
}
|
|
2866
|
+
if (entry.expiry !== void 0) {
|
|
2867
|
+
parseExpiry(entry.expiry);
|
|
2868
|
+
}
|
|
2869
|
+
}
|
|
2870
|
+
function normalizeDefaults(value) {
|
|
2871
|
+
if (value === void 0) {
|
|
2872
|
+
return DEFAULT_DEFAULTS;
|
|
2873
|
+
}
|
|
2874
|
+
if (typeof value === "boolean") {
|
|
2875
|
+
return value;
|
|
2876
|
+
}
|
|
2877
|
+
if (typeof value !== "string") {
|
|
2878
|
+
return true;
|
|
2879
|
+
}
|
|
2880
|
+
const normalized = value.trim().toLowerCase();
|
|
2881
|
+
if (normalized === "admin" || normalized === "all") {
|
|
2882
|
+
return normalized;
|
|
2883
|
+
}
|
|
2884
|
+
return true;
|
|
2885
|
+
}
|
|
2886
|
+
function defaultEntriesForTier(tier) {
|
|
2887
|
+
if (tier === false) {
|
|
2888
|
+
return [];
|
|
2889
|
+
}
|
|
2890
|
+
const source = tier === "admin" ? DEFAULT_ADMIN_ENTRIES : tier === "all" ? DEFAULT_ALL_ENTRIES : DEFAULT_STANDARD_ENTRIES;
|
|
2891
|
+
return source.map((e) => ({
|
|
2892
|
+
service: e.service,
|
|
2893
|
+
space: e.space,
|
|
2894
|
+
path: e.path,
|
|
2895
|
+
actions: [...e.actions]
|
|
2896
|
+
}));
|
|
2897
|
+
}
|
|
2898
|
+
function resolveManifest(input) {
|
|
2899
|
+
const manifest = validateManifest(input);
|
|
2900
|
+
const prefix = manifest.prefix !== void 0 ? manifest.prefix : manifest.id;
|
|
2901
|
+
const expiryMs = parseExpiry(manifest.expiry ?? DEFAULT_EXPIRY);
|
|
2902
|
+
const includePublicSpace = manifest.includePublicSpace ?? true;
|
|
2903
|
+
const tier = normalizeDefaults(manifest.defaults);
|
|
2904
|
+
const defaultEntries = defaultEntriesForTier(tier);
|
|
2905
|
+
const explicitEntries = manifest.permissions ?? [];
|
|
2906
|
+
const allEntries = [...defaultEntries, ...explicitEntries];
|
|
2907
|
+
const resources = allEntries.map(
|
|
2908
|
+
(entry) => resolveEntry(entry, prefix, expiryMs)
|
|
2909
|
+
);
|
|
2910
|
+
const additionalDelegates = (manifest.delegations ?? []).map((d) => ({
|
|
2911
|
+
did: d.to,
|
|
2912
|
+
name: d.name,
|
|
2913
|
+
expiryMs: parseExpiry(d.expiry ?? manifest.expiry ?? DEFAULT_EXPIRY),
|
|
2914
|
+
permissions: d.permissions.map(
|
|
2915
|
+
(entry) => resolveEntry(
|
|
2916
|
+
entry,
|
|
2917
|
+
prefix,
|
|
2918
|
+
parseExpiry(d.expiry ?? manifest.expiry ?? DEFAULT_EXPIRY)
|
|
2919
|
+
)
|
|
2920
|
+
)
|
|
2921
|
+
}));
|
|
2922
|
+
return {
|
|
2923
|
+
id: manifest.id,
|
|
2924
|
+
resources,
|
|
2925
|
+
expiryMs,
|
|
2926
|
+
includePublicSpace,
|
|
2927
|
+
additionalDelegates
|
|
2928
|
+
};
|
|
2929
|
+
}
|
|
2930
|
+
function resolveEntry(entry, prefix, _inheritedExpiryMs) {
|
|
2931
|
+
const resolvedPath = applyPrefix(
|
|
2932
|
+
prefix,
|
|
2933
|
+
entry.path,
|
|
2934
|
+
entry.skipPrefix === true
|
|
2935
|
+
);
|
|
2936
|
+
const resolvedActions = expandActionShortNames(entry.service, entry.actions);
|
|
2937
|
+
const entryExpiryMs = entry.expiry !== void 0 ? parseExpiry(entry.expiry) : void 0;
|
|
2938
|
+
return {
|
|
2939
|
+
service: entry.service,
|
|
2940
|
+
space: entry.space,
|
|
2941
|
+
path: resolvedPath,
|
|
2942
|
+
actions: resolvedActions,
|
|
2943
|
+
// Only populate `expiryMs` when the entry had its own expiry override.
|
|
2944
|
+
// When absent, callers use the parent (delegation or manifest) expiry
|
|
2945
|
+
// which is carried on ResolvedDelegate.expiryMs / ResolvedCapabilities.expiryMs.
|
|
2946
|
+
...entryExpiryMs !== void 0 ? { expiryMs: entryExpiryMs } : {}
|
|
2947
|
+
};
|
|
2948
|
+
}
|
|
2949
|
+
function resourceCapabilitiesToAbilitiesMap(resources) {
|
|
2950
|
+
const out = {};
|
|
2951
|
+
for (const r of resources) {
|
|
2952
|
+
const shortService = SERVICE_LONG_TO_SHORT[r.service];
|
|
2953
|
+
if (shortService === void 0) {
|
|
2954
|
+
throw new ManifestValidationError(
|
|
2955
|
+
`unknown service '${r.service}' \u2014 no short-form mapping. Known services: ${Object.keys(SERVICE_LONG_TO_SHORT).join(", ")}`
|
|
2956
|
+
);
|
|
2957
|
+
}
|
|
2958
|
+
if (out[shortService] === void 0) {
|
|
2959
|
+
out[shortService] = {};
|
|
2960
|
+
}
|
|
2961
|
+
const pathsMap = out[shortService];
|
|
2962
|
+
const existing = pathsMap[r.path];
|
|
2963
|
+
if (existing === void 0) {
|
|
2964
|
+
pathsMap[r.path] = [...r.actions];
|
|
2965
|
+
} else {
|
|
2966
|
+
const seen = new Set(existing);
|
|
2967
|
+
for (const action of r.actions) {
|
|
2968
|
+
if (!seen.has(action)) {
|
|
2969
|
+
existing.push(action);
|
|
2970
|
+
seen.add(action);
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
return out;
|
|
2976
|
+
}
|
|
2977
|
+
function manifestAbilitiesUnion(resolved) {
|
|
2978
|
+
const all = [...resolved.resources];
|
|
2979
|
+
for (const delegate of resolved.additionalDelegates) {
|
|
2980
|
+
for (const perm of delegate.permissions) {
|
|
2981
|
+
all.push(perm);
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
return resourceCapabilitiesToAbilitiesMap(all);
|
|
2985
|
+
}
|
|
2986
|
+
|
|
2987
|
+
// src/delegations/SharingService.ts
|
|
2988
|
+
function inferShortServiceFromActionUrns(actions) {
|
|
2989
|
+
let short;
|
|
2990
|
+
for (const action of actions) {
|
|
2991
|
+
const slash = action.indexOf("/");
|
|
2992
|
+
if (slash === -1) return void 0;
|
|
2993
|
+
const longService = action.slice(0, slash);
|
|
2994
|
+
const candidate = SERVICE_LONG_TO_SHORT[longService];
|
|
2995
|
+
if (candidate === void 0) return void 0;
|
|
2996
|
+
if (short === void 0) {
|
|
2997
|
+
short = candidate;
|
|
2998
|
+
} else if (short !== candidate) {
|
|
2999
|
+
return void 0;
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
return short;
|
|
3003
|
+
}
|
|
3004
|
+
var DEFAULT_READ_ACTIONS = ["tinycloud.kv/get", "tinycloud.kv/metadata"];
|
|
3005
|
+
var DEFAULT_EXPIRY_MS = 24 * 60 * 60 * 1e3;
|
|
3006
|
+
var BASE64_PREFIX = "tc1:";
|
|
3007
|
+
function createError2(code, message, cause, meta) {
|
|
3008
|
+
return {
|
|
3009
|
+
code,
|
|
3010
|
+
message,
|
|
3011
|
+
service: "delegation",
|
|
3012
|
+
cause,
|
|
3013
|
+
meta
|
|
3014
|
+
};
|
|
3015
|
+
}
|
|
3016
|
+
function base64UrlEncode(data) {
|
|
3017
|
+
let base64;
|
|
3018
|
+
if (typeof btoa !== "undefined") {
|
|
3019
|
+
base64 = btoa(unescape(encodeURIComponent(data)));
|
|
3020
|
+
} else if (typeof Buffer !== "undefined") {
|
|
3021
|
+
base64 = Buffer.from(data, "utf-8").toString("base64");
|
|
3022
|
+
} else {
|
|
3023
|
+
throw new Error("No base64 encoding available");
|
|
3024
|
+
}
|
|
3025
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
3026
|
+
}
|
|
3027
|
+
function base64UrlDecode(encoded) {
|
|
3028
|
+
let base64 = encoded.replace(/-/g, "+").replace(/_/g, "/");
|
|
3029
|
+
while (base64.length % 4) {
|
|
3030
|
+
base64 += "=";
|
|
3031
|
+
}
|
|
3032
|
+
if (typeof atob !== "undefined") {
|
|
3033
|
+
return decodeURIComponent(escape(atob(base64)));
|
|
3034
|
+
} else if (typeof Buffer !== "undefined") {
|
|
3035
|
+
return Buffer.from(base64, "base64").toString("utf-8");
|
|
3036
|
+
} else {
|
|
3037
|
+
throw new Error("No base64 decoding available");
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
var SharingService = class {
|
|
3041
|
+
/**
|
|
3042
|
+
* Creates a new SharingService instance.
|
|
3043
|
+
*/
|
|
3044
|
+
constructor(config) {
|
|
3045
|
+
this.hosts = config.hosts;
|
|
3046
|
+
this.session = config.session;
|
|
3047
|
+
this.invoke = config.invoke;
|
|
3048
|
+
this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
3049
|
+
this.keyProvider = config.keyProvider;
|
|
3050
|
+
this.registry = config.registry;
|
|
3051
|
+
this.delegationManager = config.delegationManager;
|
|
3052
|
+
this.createKVService = config.createKVService;
|
|
3053
|
+
this.baseUrl = (config.baseUrl ?? "").replace(/\/$/, "");
|
|
3054
|
+
this.createDelegationFn = config.createDelegation;
|
|
3055
|
+
this.createDelegationWasmFn = config.createDelegationWasm;
|
|
3056
|
+
this.pathPrefix = config.pathPrefix ?? "";
|
|
3057
|
+
this.sessionExpiry = config.sessionExpiry;
|
|
3058
|
+
this.onRootDelegationNeeded = config.onRootDelegationNeeded;
|
|
3059
|
+
}
|
|
3060
|
+
/**
|
|
3061
|
+
* Gets the primary host URL.
|
|
3062
|
+
*/
|
|
3063
|
+
get host() {
|
|
3064
|
+
return this.hosts[0];
|
|
3065
|
+
}
|
|
3066
|
+
/**
|
|
3067
|
+
* Updates the session (e.g., after re-authentication).
|
|
3068
|
+
*/
|
|
3069
|
+
updateSession(session) {
|
|
3070
|
+
this.session = session;
|
|
3071
|
+
}
|
|
3072
|
+
/**
|
|
3073
|
+
* Updates the service configuration.
|
|
3074
|
+
* Used to add full capabilities (session, delegationManager, createDelegation, createDelegationWasm) after signIn.
|
|
3075
|
+
*/
|
|
3076
|
+
updateConfig(config) {
|
|
3077
|
+
if (config.session !== void 0) {
|
|
3078
|
+
this.session = config.session;
|
|
3079
|
+
}
|
|
3080
|
+
if (config.delegationManager !== void 0) {
|
|
3081
|
+
this.delegationManager = config.delegationManager;
|
|
3082
|
+
}
|
|
3083
|
+
if (config.createDelegation !== void 0) {
|
|
3084
|
+
this.createDelegationFn = config.createDelegation;
|
|
3085
|
+
}
|
|
3086
|
+
if (config.createDelegationWasm !== void 0) {
|
|
3087
|
+
this.createDelegationWasmFn = config.createDelegationWasm;
|
|
3088
|
+
}
|
|
3089
|
+
if (config.sessionExpiry !== void 0) {
|
|
3090
|
+
this.sessionExpiry = config.sessionExpiry;
|
|
3091
|
+
}
|
|
3092
|
+
if (config.onRootDelegationNeeded !== void 0) {
|
|
3093
|
+
this.onRootDelegationNeeded = config.onRootDelegationNeeded;
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
/**
|
|
3097
|
+
* Generate a sharing link with an embedded private key.
|
|
3098
|
+
*
|
|
3099
|
+
* Flow:
|
|
3100
|
+
* 1. Spawn new session key (unique per share)
|
|
2732
3101
|
* 2. Create delegation from current session to spawned key
|
|
2733
3102
|
* 3. Package: { key (with private!), delegation, path, host }
|
|
2734
3103
|
* 4. Encode based on schema (base64 for now)
|
|
@@ -2975,12 +3344,34 @@ var SharingService = class {
|
|
|
2975
3344
|
}
|
|
2976
3345
|
if (this.createDelegationWasmFn) {
|
|
2977
3346
|
try {
|
|
3347
|
+
if (actions.length === 0) {
|
|
3348
|
+
return {
|
|
3349
|
+
ok: false,
|
|
3350
|
+
error: createError2(
|
|
3351
|
+
DelegationErrorCodes.VALIDATION_ERROR,
|
|
3352
|
+
"createDelegation requires at least one action"
|
|
3353
|
+
)
|
|
3354
|
+
};
|
|
3355
|
+
}
|
|
3356
|
+
const shortService = inferShortServiceFromActionUrns(actions);
|
|
3357
|
+
if (shortService === void 0) {
|
|
3358
|
+
return {
|
|
3359
|
+
ok: false,
|
|
3360
|
+
error: createError2(
|
|
3361
|
+
DelegationErrorCodes.VALIDATION_ERROR,
|
|
3362
|
+
`createDelegation: cannot infer service from actions ${JSON.stringify(actions)} \u2014 expected full URNs like "tinycloud.kv/get"`
|
|
3363
|
+
)
|
|
3364
|
+
};
|
|
3365
|
+
}
|
|
2978
3366
|
const wasmResult = this.createDelegationWasmFn({
|
|
2979
3367
|
session: this.session,
|
|
2980
3368
|
delegateDID,
|
|
2981
3369
|
spaceId: this.session.spaceId,
|
|
2982
|
-
|
|
2983
|
-
|
|
3370
|
+
abilities: {
|
|
3371
|
+
[shortService]: {
|
|
3372
|
+
[path]: [...actions]
|
|
3373
|
+
}
|
|
3374
|
+
},
|
|
2984
3375
|
expirationSecs: Math.floor(expiry.getTime() / 1e3)
|
|
2985
3376
|
});
|
|
2986
3377
|
const registerRes = await this.fetchFn(`${this.host}/delegate`, {
|
|
@@ -2999,12 +3390,22 @@ var SharingService = class {
|
|
|
2999
3390
|
)
|
|
3000
3391
|
};
|
|
3001
3392
|
}
|
|
3393
|
+
if (wasmResult.resources.length === 0) {
|
|
3394
|
+
return {
|
|
3395
|
+
ok: false,
|
|
3396
|
+
error: createError2(
|
|
3397
|
+
DelegationErrorCodes.CREATION_FAILED,
|
|
3398
|
+
"createDelegation WASM returned empty resources array for a single-entry request"
|
|
3399
|
+
)
|
|
3400
|
+
};
|
|
3401
|
+
}
|
|
3402
|
+
const primary = wasmResult.resources[0];
|
|
3002
3403
|
return {
|
|
3003
3404
|
cid: wasmResult.cid,
|
|
3004
3405
|
delegateDID: wasmResult.delegateDID,
|
|
3005
3406
|
spaceId: this.session.spaceId,
|
|
3006
|
-
path:
|
|
3007
|
-
actions:
|
|
3407
|
+
path: primary.path,
|
|
3408
|
+
actions: primary.actions,
|
|
3008
3409
|
expiry: wasmResult.expiry,
|
|
3009
3410
|
isRevoked: false,
|
|
3010
3411
|
authHeader: wasmResult.delegation,
|
|
@@ -3734,298 +4135,6 @@ async function checkNodeInfo(host, sdkProtocol, fetchFn = globalThis.fetch.bind(
|
|
|
3734
4135
|
};
|
|
3735
4136
|
}
|
|
3736
4137
|
|
|
3737
|
-
// src/manifest.ts
|
|
3738
|
-
import ms from "ms";
|
|
3739
|
-
var ManifestValidationError = class extends Error {
|
|
3740
|
-
constructor(message) {
|
|
3741
|
-
super(`Manifest validation failed: ${message}`);
|
|
3742
|
-
this.name = "ManifestValidationError";
|
|
3743
|
-
}
|
|
3744
|
-
};
|
|
3745
|
-
var DEFAULT_EXPIRY = "30d";
|
|
3746
|
-
var DEFAULT_DEFAULTS = true;
|
|
3747
|
-
var SERVICE_SHORT_TO_LONG = Object.freeze({
|
|
3748
|
-
kv: "tinycloud.kv",
|
|
3749
|
-
sql: "tinycloud.sql",
|
|
3750
|
-
duckdb: "tinycloud.duckdb",
|
|
3751
|
-
capabilities: "tinycloud.capabilities",
|
|
3752
|
-
hooks: "tinycloud.hooks"
|
|
3753
|
-
});
|
|
3754
|
-
var SERVICE_LONG_TO_SHORT = Object.freeze(
|
|
3755
|
-
Object.fromEntries(
|
|
3756
|
-
Object.entries(SERVICE_SHORT_TO_LONG).map(([s, l]) => [l, s])
|
|
3757
|
-
)
|
|
3758
|
-
);
|
|
3759
|
-
var DEFAULT_STANDARD_ENTRIES = [
|
|
3760
|
-
{
|
|
3761
|
-
service: "tinycloud.kv",
|
|
3762
|
-
space: "default",
|
|
3763
|
-
path: "/",
|
|
3764
|
-
actions: ["get", "put", "del", "list", "metadata"]
|
|
3765
|
-
},
|
|
3766
|
-
{
|
|
3767
|
-
service: "tinycloud.sql",
|
|
3768
|
-
space: "default",
|
|
3769
|
-
path: "/",
|
|
3770
|
-
actions: ["read", "write"]
|
|
3771
|
-
},
|
|
3772
|
-
{
|
|
3773
|
-
service: "tinycloud.capabilities",
|
|
3774
|
-
space: "default",
|
|
3775
|
-
path: "/",
|
|
3776
|
-
actions: ["read"]
|
|
3777
|
-
}
|
|
3778
|
-
];
|
|
3779
|
-
var DEFAULT_ADMIN_ENTRIES = [
|
|
3780
|
-
{
|
|
3781
|
-
service: "tinycloud.kv",
|
|
3782
|
-
space: "default",
|
|
3783
|
-
path: "/",
|
|
3784
|
-
actions: ["get", "put", "del", "list", "metadata"]
|
|
3785
|
-
},
|
|
3786
|
-
{
|
|
3787
|
-
service: "tinycloud.sql",
|
|
3788
|
-
space: "default",
|
|
3789
|
-
path: "/",
|
|
3790
|
-
actions: ["read", "write", "ddl"]
|
|
3791
|
-
},
|
|
3792
|
-
{
|
|
3793
|
-
service: "tinycloud.capabilities",
|
|
3794
|
-
space: "default",
|
|
3795
|
-
path: "/",
|
|
3796
|
-
actions: ["read", "admin"]
|
|
3797
|
-
}
|
|
3798
|
-
];
|
|
3799
|
-
var DEFAULT_ALL_ENTRIES = [
|
|
3800
|
-
{
|
|
3801
|
-
service: "tinycloud.kv",
|
|
3802
|
-
space: "default",
|
|
3803
|
-
path: "/",
|
|
3804
|
-
actions: ["get", "put", "del", "list", "metadata"]
|
|
3805
|
-
},
|
|
3806
|
-
{
|
|
3807
|
-
service: "tinycloud.sql",
|
|
3808
|
-
space: "default",
|
|
3809
|
-
path: "/",
|
|
3810
|
-
actions: ["read", "write", "ddl"]
|
|
3811
|
-
},
|
|
3812
|
-
{
|
|
3813
|
-
service: "tinycloud.duckdb",
|
|
3814
|
-
space: "default",
|
|
3815
|
-
path: "/",
|
|
3816
|
-
actions: ["read", "write"]
|
|
3817
|
-
},
|
|
3818
|
-
{
|
|
3819
|
-
service: "tinycloud.capabilities",
|
|
3820
|
-
space: "default",
|
|
3821
|
-
path: "/",
|
|
3822
|
-
actions: ["read", "admin"]
|
|
3823
|
-
}
|
|
3824
|
-
];
|
|
3825
|
-
function parseExpiry(duration) {
|
|
3826
|
-
if (typeof duration !== "string" || duration.length === 0) {
|
|
3827
|
-
throw new ManifestValidationError(
|
|
3828
|
-
`expiry must be a non-empty duration string (got ${JSON.stringify(duration)})`
|
|
3829
|
-
);
|
|
3830
|
-
}
|
|
3831
|
-
const parsed = ms(
|
|
3832
|
-
duration
|
|
3833
|
-
);
|
|
3834
|
-
if (typeof parsed !== "number" || !Number.isFinite(parsed) || parsed <= 0) {
|
|
3835
|
-
throw new ManifestValidationError(
|
|
3836
|
-
`invalid expiry duration: ${JSON.stringify(duration)}`
|
|
3837
|
-
);
|
|
3838
|
-
}
|
|
3839
|
-
return parsed;
|
|
3840
|
-
}
|
|
3841
|
-
function expandActionShortNames(service, actions) {
|
|
3842
|
-
return actions.map((a) => {
|
|
3843
|
-
if (a.includes("/")) {
|
|
3844
|
-
return a;
|
|
3845
|
-
}
|
|
3846
|
-
return `${service}/${a}`;
|
|
3847
|
-
});
|
|
3848
|
-
}
|
|
3849
|
-
function applyPrefix(prefix, path, skipPrefix) {
|
|
3850
|
-
if (skipPrefix) {
|
|
3851
|
-
return path;
|
|
3852
|
-
}
|
|
3853
|
-
if (prefix === "") {
|
|
3854
|
-
return path;
|
|
3855
|
-
}
|
|
3856
|
-
if (path.startsWith("/")) {
|
|
3857
|
-
return `${prefix}${path}`;
|
|
3858
|
-
}
|
|
3859
|
-
return `${prefix}/${path}`;
|
|
3860
|
-
}
|
|
3861
|
-
async function loadManifest(url) {
|
|
3862
|
-
const fetchFn = globalThis.fetch;
|
|
3863
|
-
if (typeof fetchFn !== "function") {
|
|
3864
|
-
throw new ManifestValidationError(
|
|
3865
|
-
"loadManifest requires a global fetch; pass the manifest object directly on runtimes without fetch"
|
|
3866
|
-
);
|
|
3867
|
-
}
|
|
3868
|
-
const res = await fetchFn(url);
|
|
3869
|
-
if (!res.ok) {
|
|
3870
|
-
throw new ManifestValidationError(
|
|
3871
|
-
`failed to fetch manifest from ${url}: HTTP ${res.status}`
|
|
3872
|
-
);
|
|
3873
|
-
}
|
|
3874
|
-
const json = await res.json();
|
|
3875
|
-
return validateManifest(json);
|
|
3876
|
-
}
|
|
3877
|
-
function validateManifest(input) {
|
|
3878
|
-
if (input === null || typeof input !== "object") {
|
|
3879
|
-
throw new ManifestValidationError("manifest must be an object");
|
|
3880
|
-
}
|
|
3881
|
-
const m = input;
|
|
3882
|
-
if (typeof m.id !== "string" || m.id.length === 0) {
|
|
3883
|
-
throw new ManifestValidationError("manifest.id is required and must be a non-empty string");
|
|
3884
|
-
}
|
|
3885
|
-
if (typeof m.name !== "string" || m.name.length === 0) {
|
|
3886
|
-
throw new ManifestValidationError("manifest.name is required and must be a non-empty string");
|
|
3887
|
-
}
|
|
3888
|
-
if (m.expiry !== void 0) {
|
|
3889
|
-
parseExpiry(m.expiry);
|
|
3890
|
-
}
|
|
3891
|
-
if (m.permissions !== void 0) {
|
|
3892
|
-
if (!Array.isArray(m.permissions)) {
|
|
3893
|
-
throw new ManifestValidationError("manifest.permissions must be an array");
|
|
3894
|
-
}
|
|
3895
|
-
m.permissions.forEach(
|
|
3896
|
-
(p, i) => validatePermissionEntry(p, `permissions[${i}]`)
|
|
3897
|
-
);
|
|
3898
|
-
}
|
|
3899
|
-
if (m.delegations !== void 0) {
|
|
3900
|
-
if (!Array.isArray(m.delegations)) {
|
|
3901
|
-
throw new ManifestValidationError("manifest.delegations must be an array");
|
|
3902
|
-
}
|
|
3903
|
-
m.delegations.forEach((d, i) => {
|
|
3904
|
-
if (typeof d?.to !== "string" || d.to.length === 0) {
|
|
3905
|
-
throw new ManifestValidationError(
|
|
3906
|
-
`delegations[${i}].to is required and must be a non-empty DID string`
|
|
3907
|
-
);
|
|
3908
|
-
}
|
|
3909
|
-
if (d.expiry !== void 0) {
|
|
3910
|
-
parseExpiry(d.expiry);
|
|
3911
|
-
}
|
|
3912
|
-
if (!Array.isArray(d.permissions)) {
|
|
3913
|
-
throw new ManifestValidationError(
|
|
3914
|
-
`delegations[${i}].permissions must be an array`
|
|
3915
|
-
);
|
|
3916
|
-
}
|
|
3917
|
-
d.permissions.forEach(
|
|
3918
|
-
(p, j) => validatePermissionEntry(p, `delegations[${i}].permissions[${j}]`)
|
|
3919
|
-
);
|
|
3920
|
-
});
|
|
3921
|
-
}
|
|
3922
|
-
return m;
|
|
3923
|
-
}
|
|
3924
|
-
function validatePermissionEntry(p, path) {
|
|
3925
|
-
if (p === null || typeof p !== "object") {
|
|
3926
|
-
throw new ManifestValidationError(`${path} must be an object`);
|
|
3927
|
-
}
|
|
3928
|
-
const entry = p;
|
|
3929
|
-
if (typeof entry.service !== "string" || entry.service.length === 0) {
|
|
3930
|
-
throw new ManifestValidationError(`${path}.service is required`);
|
|
3931
|
-
}
|
|
3932
|
-
if (typeof entry.space !== "string" || entry.space.length === 0) {
|
|
3933
|
-
throw new ManifestValidationError(`${path}.space is required`);
|
|
3934
|
-
}
|
|
3935
|
-
if (typeof entry.path !== "string") {
|
|
3936
|
-
throw new ManifestValidationError(
|
|
3937
|
-
`${path}.path is required (use "" or "/" for root)`
|
|
3938
|
-
);
|
|
3939
|
-
}
|
|
3940
|
-
if (!Array.isArray(entry.actions) || entry.actions.length === 0) {
|
|
3941
|
-
throw new ManifestValidationError(
|
|
3942
|
-
`${path}.actions must be a non-empty array`
|
|
3943
|
-
);
|
|
3944
|
-
}
|
|
3945
|
-
if (entry.expiry !== void 0) {
|
|
3946
|
-
parseExpiry(entry.expiry);
|
|
3947
|
-
}
|
|
3948
|
-
}
|
|
3949
|
-
function normalizeDefaults(value) {
|
|
3950
|
-
if (value === void 0) {
|
|
3951
|
-
return DEFAULT_DEFAULTS;
|
|
3952
|
-
}
|
|
3953
|
-
if (typeof value === "boolean") {
|
|
3954
|
-
return value;
|
|
3955
|
-
}
|
|
3956
|
-
if (typeof value !== "string") {
|
|
3957
|
-
return true;
|
|
3958
|
-
}
|
|
3959
|
-
const normalized = value.trim().toLowerCase();
|
|
3960
|
-
if (normalized === "admin" || normalized === "all") {
|
|
3961
|
-
return normalized;
|
|
3962
|
-
}
|
|
3963
|
-
return true;
|
|
3964
|
-
}
|
|
3965
|
-
function defaultEntriesForTier(tier) {
|
|
3966
|
-
if (tier === false) {
|
|
3967
|
-
return [];
|
|
3968
|
-
}
|
|
3969
|
-
const source = tier === "admin" ? DEFAULT_ADMIN_ENTRIES : tier === "all" ? DEFAULT_ALL_ENTRIES : DEFAULT_STANDARD_ENTRIES;
|
|
3970
|
-
return source.map((e) => ({
|
|
3971
|
-
service: e.service,
|
|
3972
|
-
space: e.space,
|
|
3973
|
-
path: e.path,
|
|
3974
|
-
actions: [...e.actions]
|
|
3975
|
-
}));
|
|
3976
|
-
}
|
|
3977
|
-
function resolveManifest(input) {
|
|
3978
|
-
const manifest = validateManifest(input);
|
|
3979
|
-
const prefix = manifest.prefix !== void 0 ? manifest.prefix : manifest.id;
|
|
3980
|
-
const expiryMs = parseExpiry(manifest.expiry ?? DEFAULT_EXPIRY);
|
|
3981
|
-
const includePublicSpace = manifest.includePublicSpace ?? true;
|
|
3982
|
-
const tier = normalizeDefaults(manifest.defaults);
|
|
3983
|
-
const defaultEntries = defaultEntriesForTier(tier);
|
|
3984
|
-
const explicitEntries = manifest.permissions ?? [];
|
|
3985
|
-
const allEntries = [...defaultEntries, ...explicitEntries];
|
|
3986
|
-
const resources = allEntries.map(
|
|
3987
|
-
(entry) => resolveEntry(entry, prefix, expiryMs)
|
|
3988
|
-
);
|
|
3989
|
-
const additionalDelegates = (manifest.delegations ?? []).map((d) => ({
|
|
3990
|
-
did: d.to,
|
|
3991
|
-
name: d.name,
|
|
3992
|
-
expiryMs: parseExpiry(d.expiry ?? manifest.expiry ?? DEFAULT_EXPIRY),
|
|
3993
|
-
permissions: d.permissions.map(
|
|
3994
|
-
(entry) => resolveEntry(
|
|
3995
|
-
entry,
|
|
3996
|
-
prefix,
|
|
3997
|
-
parseExpiry(d.expiry ?? manifest.expiry ?? DEFAULT_EXPIRY)
|
|
3998
|
-
)
|
|
3999
|
-
)
|
|
4000
|
-
}));
|
|
4001
|
-
return {
|
|
4002
|
-
id: manifest.id,
|
|
4003
|
-
resources,
|
|
4004
|
-
expiryMs,
|
|
4005
|
-
includePublicSpace,
|
|
4006
|
-
additionalDelegates
|
|
4007
|
-
};
|
|
4008
|
-
}
|
|
4009
|
-
function resolveEntry(entry, prefix, _inheritedExpiryMs) {
|
|
4010
|
-
const resolvedPath = applyPrefix(
|
|
4011
|
-
prefix,
|
|
4012
|
-
entry.path,
|
|
4013
|
-
entry.skipPrefix === true
|
|
4014
|
-
);
|
|
4015
|
-
const resolvedActions = expandActionShortNames(entry.service, entry.actions);
|
|
4016
|
-
const entryExpiryMs = entry.expiry !== void 0 ? parseExpiry(entry.expiry) : void 0;
|
|
4017
|
-
return {
|
|
4018
|
-
service: entry.service,
|
|
4019
|
-
space: entry.space,
|
|
4020
|
-
path: resolvedPath,
|
|
4021
|
-
actions: resolvedActions,
|
|
4022
|
-
// Only populate `expiryMs` when the entry had its own expiry override.
|
|
4023
|
-
// When absent, callers use the parent (delegation or manifest) expiry
|
|
4024
|
-
// which is carried on ResolvedDelegate.expiryMs / ResolvedCapabilities.expiryMs.
|
|
4025
|
-
...entryExpiryMs !== void 0 ? { expiryMs: entryExpiryMs } : {}
|
|
4026
|
-
};
|
|
4027
|
-
}
|
|
4028
|
-
|
|
4029
4138
|
// src/capabilities.ts
|
|
4030
4139
|
var PermissionNotInManifestError = class extends Error {
|
|
4031
4140
|
constructor(missing, granted) {
|
|
@@ -4044,6 +4153,16 @@ var SessionExpiredError = class extends Error {
|
|
|
4044
4153
|
this.expiredAt = expiredAt;
|
|
4045
4154
|
}
|
|
4046
4155
|
};
|
|
4156
|
+
function normalizeSpace(space) {
|
|
4157
|
+
if (!space.startsWith("tinycloud:")) {
|
|
4158
|
+
return space;
|
|
4159
|
+
}
|
|
4160
|
+
const lastColon = space.lastIndexOf(":");
|
|
4161
|
+
if (lastColon === -1 || lastColon === space.length - 1) {
|
|
4162
|
+
return space;
|
|
4163
|
+
}
|
|
4164
|
+
return space.slice(lastColon + 1);
|
|
4165
|
+
}
|
|
4047
4166
|
function isCapabilitySubset(requested, granted) {
|
|
4048
4167
|
const missing = [];
|
|
4049
4168
|
for (const req of requested) {
|
|
@@ -4059,7 +4178,7 @@ function canonicalizeEntryMatches(requested, granted) {
|
|
|
4059
4178
|
if (requested.service !== granted.service) {
|
|
4060
4179
|
return false;
|
|
4061
4180
|
}
|
|
4062
|
-
if (requested.space !== granted.space) {
|
|
4181
|
+
if (normalizeSpace(requested.space) !== normalizeSpace(granted.space)) {
|
|
4063
4182
|
return false;
|
|
4064
4183
|
}
|
|
4065
4184
|
if (!pathContains(granted.path, requested.path)) {
|
|
@@ -4111,7 +4230,10 @@ function parseRecapCapabilities(parseWasm, siwe) {
|
|
|
4111
4230
|
(entry.service.startsWith("tinycloud.") ? entry.service : `tinycloud.${entry.service}`);
|
|
4112
4231
|
return {
|
|
4113
4232
|
service: longService,
|
|
4114
|
-
space:
|
|
4233
|
+
// The Rust layer emits the space as a full `tinycloud:pkh:...:name`
|
|
4234
|
+
// URI (the recap target URI). Normalize to the short name so the
|
|
4235
|
+
// returned entries match the shape manifests use.
|
|
4236
|
+
space: normalizeSpace(entry.space),
|
|
4115
4237
|
path: entry.path,
|
|
4116
4238
|
actions: [...entry.actions]
|
|
4117
4239
|
};
|
|
@@ -4181,12 +4303,14 @@ export {
|
|
|
4181
4303
|
isCapabilitySubset,
|
|
4182
4304
|
loadManifest,
|
|
4183
4305
|
makePublicSpaceId,
|
|
4306
|
+
manifestAbilitiesUnion,
|
|
4184
4307
|
normalizeDefaults,
|
|
4185
4308
|
ok4 as ok,
|
|
4186
4309
|
parseExpiry,
|
|
4187
4310
|
parseRecapCapabilities,
|
|
4188
4311
|
parseSpaceUri,
|
|
4189
4312
|
resolveManifest,
|
|
4313
|
+
resourceCapabilitiesToAbilitiesMap,
|
|
4190
4314
|
serviceError4 as serviceError,
|
|
4191
4315
|
submitHostDelegation,
|
|
4192
4316
|
validateClientSession,
|