@ixo/ucan 1.1.1 → 1.2.0

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.
@@ -1,4 +1,4 @@
1
1
 
2
- > @ixo/ucan@1.1.1 build /home/runner/work/qiforge/qiforge/packages/ucan
2
+ > @ixo/ucan@1.2.0 build /home/runner/work/qiforge/qiforge/packages/ucan
3
3
  > tsc
4
4
 
package/README.md CHANGED
@@ -74,7 +74,7 @@ const validator = await createUCANValidator({
74
74
  });
75
75
  ```
76
76
 
77
- ### 3. Protect a Route
77
+ ### 3. Protect a Route (Invocation)
78
78
 
79
79
  ```typescript
80
80
  app.post('/employees', async (req, res) => {
@@ -99,6 +99,37 @@ app.post('/employees', async (req, res) => {
99
99
  });
100
100
  ```
101
101
 
102
+ ### 3b. Validate a Delegation
103
+
104
+ Use `validateDelegation()` to verify a standalone delegation token — e.g. when a client sends a delegation in a header alongside a traditional auth mechanism. This verifies the cryptographic signature chain, audience, and expiration without requiring a capability definition.
105
+
106
+ ```typescript
107
+ app.use(async (req, res, next) => {
108
+ const delegationHeader = req.headers['x-ucan-delegation'];
109
+ if (!delegationHeader) return next();
110
+
111
+ const result = await validator.validateDelegation(delegationHeader);
112
+
113
+ if (!result.ok) {
114
+ console.warn(`Delegation invalid: [${result.error?.code}] ${result.error?.message}`);
115
+ return next();
116
+ }
117
+
118
+ // result.invoker — issuer DID (who granted the delegation)
119
+ // result.capability — first capability in the delegation
120
+ // result.expiration — effective expiration across the proof chain
121
+ // result.proofChain — e.g. ["did:ixo:root", "did:ixo:user"]
122
+
123
+ req.ucanDelegation = {
124
+ issuer: result.invoker,
125
+ capability: result.capability,
126
+ expiration: result.expiration,
127
+ };
128
+
129
+ next();
130
+ });
131
+ ```
132
+
102
133
  ### 4. Create & Use a Delegation (Client)
103
134
 
104
135
  ```typescript
@@ -183,18 +214,29 @@ Create a framework-agnostic validator.
183
214
  | `didResolver` | `DIDKeyResolver` | Resolver for non-`did:key` DIDs |
184
215
  | `invocationStore` | `InvocationStore` | Custom store for replay protection |
185
216
 
217
+ #### Methods
218
+
219
+ | Method | Description |
220
+ | ------------------------------------------------------------------- | ------------------------------------------------------------- |
221
+ | `validate(invocationBase64, capabilityDef, resource)` | Validate an invocation against a capability definition |
222
+ | `validateDelegation(delegationBase64)` | Validate a standalone delegation (signature, audience, chain) |
223
+
224
+ **`validate()`** validates a full invocation — checks signature, capability matching, caveats, replay protection, and resource authorization.
225
+
226
+ **`validateDelegation()`** validates a standalone delegation token — verifies the cryptographic signature of every delegation in the proof chain, checks audience matches `serverDid`, validates expiration, and ensures chain consistency (each proof's audience matches the child delegation's issuer). Supports `did:key` issuers natively and non-`did:key` issuers (e.g. `did:ixo`) via the configured `didResolver`.
227
+
186
228
  #### `ValidateResult`
187
229
 
188
- The `validator.validate()` method returns a `ValidateResult`:
230
+ Both methods return a `ValidateResult`:
189
231
 
190
232
  | Field | Type | Description |
191
233
  | ------------ | ---------------------------------------- | ---------------------------------------------------------------------------------------------------- |
192
234
  | `ok` | `boolean` | Whether validation succeeded |
193
- | `invoker` | `string` | DID of the invoker (on success) |
235
+ | `invoker` | `string` | DID of the invoker/issuer (on success) |
194
236
  | `capability` | `object` | Validated capability with `can`, `with`, and optional `nb` caveats (on success) |
195
237
  | `expiration` | `number \| undefined` | Effective expiration (Unix seconds) — the earliest across the delegation chain. Undefined = never. |
196
238
  | `proofChain` | `string[] \| undefined` | Delegation path from root issuer to invoker, e.g. `["did:key:root", "did:key:alice", "did:key:bob"]` |
197
- | `facts` | `Record<string, unknown>[] \| undefined` | Facts attached to the invocation. Undefined if none. |
239
+ | `facts` | `Record<string, unknown>[] \| undefined` | Facts attached to the invocation/delegation. Undefined if none. |
198
240
  | `error` | `object` | Error with `code` and `message` (on failure) |
199
241
 
200
242
  Error codes: `INVALID_FORMAT`, `INVALID_SIGNATURE`, `UNAUTHORIZED`, `REPLAY`, `EXPIRED`, `CAVEAT_VIOLATION`.
@@ -293,7 +335,7 @@ pnpm test # Run unit tests (vitest)
293
335
  pnpm test:ucan # Run interactive test script with full UCAN flow
294
336
  ```
295
337
 
296
- The unit tests cover proof chain construction, expiration computation, facts pass-through, validation failures, caveat enforcement, and replay protection.
338
+ The unit tests cover proof chain construction, expiration computation, facts pass-through, validation failures, caveat enforcement, replay protection, and delegation validation (signature verification, audience checks, expiration, tampered signatures, proof chain consistency, and `did:ixo` support).
297
339
 
298
340
  ## License
299
341
 
@@ -0,0 +1,7 @@
1
+ import type { DIDKeyResolver } from '../types.js';
2
+ export interface WebDIDResolverConfig {
3
+ fetch?: typeof globalThis.fetch;
4
+ fallbackToHttp?: boolean;
5
+ }
6
+ export declare function createWebDIDResolver(config?: WebDIDResolverConfig): DIDKeyResolver;
7
+ //# sourceMappingURL=web-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-resolver.d.ts","sourceRoot":"","sources":["../../src/did/web-resolver.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAU,MAAM,aAAa,CAAC;AAE1D,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAEhC,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAwBD,wBAAgB,oBAAoB,CAClC,MAAM,CAAC,EAAE,oBAAoB,GAC5B,cAAc,CAiGhB"}
@@ -0,0 +1,78 @@
1
+ export function createWebDIDResolver(config) {
2
+ const fetchFn = config?.fetch ?? globalThis.fetch;
3
+ return async (did) => {
4
+ if (!did.startsWith('did:web:')) {
5
+ return {
6
+ error: {
7
+ name: 'DIDKeyResolutionError',
8
+ did,
9
+ message: `Cannot resolve ${did}: not a did:web identifier`,
10
+ },
11
+ };
12
+ }
13
+ try {
14
+ const parts = did.slice('did:web:'.length).split(':');
15
+ const domain = decodeURIComponent(parts[0]);
16
+ const pathSegments = parts.slice(1).map(decodeURIComponent);
17
+ const path = pathSegments.length > 0
18
+ ? `/${pathSegments.join('/')}/did.json`
19
+ : '/.well-known/did.json';
20
+ const httpsUrl = `https://${domain}${path}`;
21
+ let response = null;
22
+ let fetchUrl = httpsUrl;
23
+ try {
24
+ response = await fetchFn(httpsUrl);
25
+ }
26
+ catch (httpsError) {
27
+ if (config?.fallbackToHttp) {
28
+ fetchUrl = `http://${domain}${path}`;
29
+ response = await fetchFn(fetchUrl);
30
+ }
31
+ else {
32
+ throw httpsError;
33
+ }
34
+ }
35
+ if (!response.ok && config?.fallbackToHttp && fetchUrl === httpsUrl) {
36
+ fetchUrl = `http://${domain}${path}`;
37
+ response = await fetchFn(fetchUrl);
38
+ }
39
+ if (!response.ok) {
40
+ return {
41
+ error: {
42
+ name: 'DIDKeyResolutionError',
43
+ did,
44
+ message: `Failed to fetch DID document from ${fetchUrl}: HTTP ${response.status}`,
45
+ },
46
+ };
47
+ }
48
+ const doc = (await response.json());
49
+ const keys = [];
50
+ for (const vm of doc.verificationMethod ?? []) {
51
+ if (vm.type.includes('Ed25519') &&
52
+ vm.publicKeyMultibase?.startsWith('z')) {
53
+ keys.push(`did:key:${vm.publicKeyMultibase}`);
54
+ }
55
+ }
56
+ if (keys.length === 0) {
57
+ return {
58
+ error: {
59
+ name: 'DIDKeyResolutionError',
60
+ did,
61
+ message: `No valid Ed25519 verification methods found in DID document for ${did}`,
62
+ },
63
+ };
64
+ }
65
+ return { ok: keys };
66
+ }
67
+ catch (error) {
68
+ return {
69
+ error: {
70
+ name: 'DIDKeyResolutionError',
71
+ did,
72
+ message: `Failed to resolve ${did}: ${error instanceof Error ? error.message : 'Unknown error'}`,
73
+ },
74
+ };
75
+ }
76
+ };
77
+ }
78
+ //# sourceMappingURL=web-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-resolver.js","sourceRoot":"","sources":["../../src/did/web-resolver.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@ export { defineCapability, Schema, type DefineCapabilityOptions, } from './capab
8
8
  export { createUCANValidator, type CreateValidatorOptions, type ValidateResult, type UCANValidator, } from './validator/validator.js';
9
9
  export { generateKeypair, parseSigner, signerFromMnemonic, createDelegation, createInvocation, serializeInvocation, serializeDelegation, parseDelegation, type Signer, type Delegation, type Capability, type Fact, } from './client/create-client.js';
10
10
  export { createIxoDIDResolver, createCompositeDIDResolver, type IxoDIDResolverConfig, } from './did/ixo-resolver.js';
11
+ export { createWebDIDResolver, type WebDIDResolverConfig, } from './did/web-resolver.js';
11
12
  export { InMemoryInvocationStore, createInvocationStore, } from './store/memory.js';
12
13
  export declare const VERSION = "1.0.0";
13
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAmDA,OAAO,KAAK,YAAY,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,YAAY,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,eAAe,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,eAAe,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAMtD,YAAY,EACV,MAAM,EACN,MAAM,EACN,YAAY,EACZ,sBAAsB,EACtB,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,YAAY,CAAC;AAMpB,OAAO,EACL,gBAAgB,EAChB,MAAM,EACN,KAAK,uBAAuB,GAC7B,MAAM,8BAA8B,CAAC;AAMtC,OAAO,EACL,mBAAmB,EACnB,KAAK,sBAAsB,EAC3B,KAAK,cAAc,EACnB,KAAK,aAAa,GACnB,MAAM,0BAA0B,CAAC;AAMlC,OAAO,EACL,eAAe,EACf,WAAW,EACX,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACnB,eAAe,EACf,KAAK,MAAM,EACX,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,IAAI,GACV,MAAM,2BAA2B,CAAC;AAMnC,OAAO,EACL,oBAAoB,EACpB,0BAA0B,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,uBAAuB,CAAC;AAM/B,OAAO,EACL,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,mBAAmB,CAAC;AAM3B,eAAO,MAAM,OAAO,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAmDA,OAAO,KAAK,YAAY,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,YAAY,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,eAAe,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,eAAe,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAMtD,YAAY,EACV,MAAM,EACN,MAAM,EACN,YAAY,EACZ,sBAAsB,EACtB,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,YAAY,CAAC;AAMpB,OAAO,EACL,gBAAgB,EAChB,MAAM,EACN,KAAK,uBAAuB,GAC7B,MAAM,8BAA8B,CAAC;AAMtC,OAAO,EACL,mBAAmB,EACnB,KAAK,sBAAsB,EAC3B,KAAK,cAAc,EACnB,KAAK,aAAa,GACnB,MAAM,0BAA0B,CAAC;AAMlC,OAAO,EACL,eAAe,EACf,WAAW,EACX,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACnB,eAAe,EACf,KAAK,MAAM,EACX,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,IAAI,GACV,MAAM,2BAA2B,CAAC;AAMnC,OAAO,EACL,oBAAoB,EACpB,0BAA0B,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,oBAAoB,EACpB,KAAK,oBAAoB,GAC1B,MAAM,uBAAuB,CAAC;AAM/B,OAAO,EACL,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,mBAAmB,CAAC;AAM3B,eAAO,MAAM,OAAO,UAAU,CAAC"}
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ export { defineCapability, Schema, } from './capabilities/capability.js';
7
7
  export { createUCANValidator, } from './validator/validator.js';
8
8
  export { generateKeypair, parseSigner, signerFromMnemonic, createDelegation, createInvocation, serializeInvocation, serializeDelegation, parseDelegation, } from './client/create-client.js';
9
9
  export { createIxoDIDResolver, createCompositeDIDResolver, } from './did/ixo-resolver.js';
10
+ export { createWebDIDResolver, } from './did/web-resolver.js';
10
11
  export { InMemoryInvocationStore, createInvocationStore, } from './store/memory.js';
11
12
  export const VERSION = '1.0.0';
12
13
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAmDA,OAAO,KAAK,YAAY,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,YAAY,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,eAAe,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,eAAe,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAqBtD,OAAO,EACL,gBAAgB,EAChB,MAAM,GAEP,MAAM,8BAA8B,CAAC;AAMtC,OAAO,EACL,mBAAmB,GAIpB,MAAM,0BAA0B,CAAC;AAMlC,OAAO,EACL,eAAe,EACf,WAAW,EACX,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACnB,eAAe,GAKhB,MAAM,2BAA2B,CAAC;AAMnC,OAAO,EACL,oBAAoB,EACpB,0BAA0B,GAE3B,MAAM,uBAAuB,CAAC;AAM/B,OAAO,EACL,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,mBAAmB,CAAC;AAM3B,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAmDA,OAAO,KAAK,YAAY,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,YAAY,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,eAAe,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,eAAe,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAqBtD,OAAO,EACL,gBAAgB,EAChB,MAAM,GAEP,MAAM,8BAA8B,CAAC;AAMtC,OAAO,EACL,mBAAmB,GAIpB,MAAM,0BAA0B,CAAC;AAMlC,OAAO,EACL,eAAe,EACf,WAAW,EACX,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACnB,eAAe,GAKhB,MAAM,2BAA2B,CAAC;AAMnC,OAAO,EACL,oBAAoB,EACpB,0BAA0B,GAE3B,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,oBAAoB,GAErB,MAAM,uBAAuB,CAAC;AAM/B,OAAO,EACL,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,mBAAmB,CAAC;AAM3B,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC"}