iso-ucan 0.4.2 → 0.5.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.
- package/dist/src/capability.d.ts +0 -3
- package/dist/src/capability.d.ts.map +1 -1
- package/dist/src/delegation.d.ts +4 -1
- package/dist/src/delegation.d.ts.map +1 -1
- package/dist/src/invocation.d.ts.map +1 -1
- package/dist/src/rpc/client.d.ts +24 -0
- package/dist/src/rpc/client.d.ts.map +1 -0
- package/dist/src/rpc/command.d.ts +19 -0
- package/dist/src/rpc/command.d.ts.map +1 -0
- package/dist/src/rpc/index.d.ts +5 -0
- package/dist/src/rpc/index.d.ts.map +1 -0
- package/dist/src/rpc/receipt.d.ts +114 -0
- package/dist/src/rpc/receipt.d.ts.map +1 -0
- package/dist/src/rpc/server.d.ts +18 -0
- package/dist/src/rpc/server.d.ts.map +1 -0
- package/dist/src/rpc/types.d.ts +112 -0
- package/dist/src/rpc/types.d.ts.map +1 -0
- package/dist/src/store.d.ts +4 -2
- package/dist/src/store.d.ts.map +1 -1
- package/dist/src/types.d.ts +15 -3
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils.d.ts +4 -2
- package/dist/src/utils.d.ts.map +1 -1
- package/package.json +13 -20
- package/readme.md +130 -0
- package/src/capability.js +1 -6
- package/src/delegation.js +15 -13
- package/src/invocation.js +7 -4
- package/src/rpc/client.js +85 -0
- package/src/rpc/command.js +28 -0
- package/src/rpc/index.js +10 -0
- package/src/rpc/receipt.js +136 -0
- package/src/rpc/server.js +155 -0
- package/src/rpc/types.js +9 -0
- package/src/rpc/types.ts +153 -0
- package/src/store.js +41 -16
- package/src/types.ts +15 -6
- package/src/utils.js +20 -15
- package/dist/src/client.d.ts +0 -41
- package/dist/src/client.d.ts.map +0 -1
- package/dist/src/server.d.ts +0 -38
- package/dist/src/server.d.ts.map +0 -1
- package/src/client.js +0 -52
- package/src/server.js +0 -47
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { u8 } from 'iso-base/utils'
|
|
2
|
+
import { Invocation } from '../invocation.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @import {
|
|
6
|
+
* CommandsRecord,
|
|
7
|
+
* DefineServerOptions,
|
|
8
|
+
* ServerRequestHandler,
|
|
9
|
+
* } from './types.js'
|
|
10
|
+
* @import {ReceiptServerError} from './receipt.js'
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Build a {@link ReceiptServerError} payload.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} message
|
|
17
|
+
* @param {{
|
|
18
|
+
* cid?: import('multiformats').CID,
|
|
19
|
+
* cmd?: string,
|
|
20
|
+
* issues?: readonly import('@standard-schema/spec').StandardSchemaV1.Issue[],
|
|
21
|
+
* }} [extra]
|
|
22
|
+
* @returns {ReceiptServerError}
|
|
23
|
+
*/
|
|
24
|
+
function buildServerError(message, extra) {
|
|
25
|
+
/** @type {Record<string, unknown> | undefined} */
|
|
26
|
+
let data
|
|
27
|
+
if (extra?.cmd !== undefined || extra?.issues !== undefined) {
|
|
28
|
+
data = {}
|
|
29
|
+
if (extra.cmd !== undefined) data.cmd = extra.cmd
|
|
30
|
+
if (extra.issues !== undefined) data.issues = extra.issues
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** @type {ReceiptServerError['error']} */
|
|
34
|
+
const error = { code: 'SERVER_ERROR', message }
|
|
35
|
+
if (data !== undefined) error.data = data
|
|
36
|
+
if (extra?.cid !== undefined) error.cid = extra.cid
|
|
37
|
+
return { error }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Build a JSON {@link Response} for a server error.
|
|
42
|
+
*
|
|
43
|
+
* @param {number} status
|
|
44
|
+
* @param {string} message
|
|
45
|
+
* @param {Parameters<typeof buildServerError>[1]} [extra]
|
|
46
|
+
*/
|
|
47
|
+
function serverErrorResponse(status, message, extra) {
|
|
48
|
+
return Response.json(buildServerError(message, extra), { status })
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Define a type-safe server from a record of commands.
|
|
53
|
+
*
|
|
54
|
+
* The returned function accepts a standard `Request`, decodes its body as a
|
|
55
|
+
* UCAN {@link Invocation}, validates the args against the matching command's
|
|
56
|
+
* schema, and dispatches to the corresponding handler in `options.handlers`.
|
|
57
|
+
* The handler's return value is serialized to JSON.
|
|
58
|
+
*
|
|
59
|
+
* @template {CommandsRecord} Commands
|
|
60
|
+
* @param {Commands} commands
|
|
61
|
+
* @param {DefineServerOptions<Commands>} options
|
|
62
|
+
* @returns {ServerRequestHandler}
|
|
63
|
+
*/
|
|
64
|
+
export function defineServer(commands, options) {
|
|
65
|
+
/** @type {Record<string, CommandsRecord[string]>} */
|
|
66
|
+
const commandMap = {}
|
|
67
|
+
for (const command of Object.values(commands)) {
|
|
68
|
+
commandMap[command.capability.cmd] = command
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** @type {Record<string, (opts: any) => unknown>} */
|
|
72
|
+
const handlers = options.handlers
|
|
73
|
+
|
|
74
|
+
return async (request) => {
|
|
75
|
+
/** @type {Invocation | undefined} */
|
|
76
|
+
let invocation
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const bytes = u8(await request.arrayBuffer())
|
|
80
|
+
|
|
81
|
+
invocation = await Invocation.from({
|
|
82
|
+
bytes,
|
|
83
|
+
audience: options.signer,
|
|
84
|
+
verifierResolver: options.verifierResolver,
|
|
85
|
+
resolveProof: options.store.resolveProof.bind(options.store),
|
|
86
|
+
})
|
|
87
|
+
} catch (error) {
|
|
88
|
+
return serverErrorResponse(
|
|
89
|
+
400,
|
|
90
|
+
`Invalid invocation: ${
|
|
91
|
+
error instanceof Error ? error.message : String(error)
|
|
92
|
+
}`
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const cid = invocation.cid
|
|
97
|
+
const cmd = invocation.payload.cmd
|
|
98
|
+
const command = commandMap[cmd]
|
|
99
|
+
if (!command) {
|
|
100
|
+
return serverErrorResponse(404, `Command not found: ${cmd}`, {
|
|
101
|
+
cid,
|
|
102
|
+
cmd,
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const handler = handlers[cmd]
|
|
107
|
+
if (!handler) {
|
|
108
|
+
return serverErrorResponse(404, `Handler not found: ${cmd}`, {
|
|
109
|
+
cid,
|
|
110
|
+
cmd,
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const validated = await command.capability.validate(
|
|
115
|
+
/** @type {any} */ (invocation.payload.args)
|
|
116
|
+
)
|
|
117
|
+
if (validated.issues) {
|
|
118
|
+
return serverErrorResponse(400, `Invalid args for ${cmd}`, {
|
|
119
|
+
cid,
|
|
120
|
+
cmd,
|
|
121
|
+
issues: validated.issues,
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** @type {unknown} */
|
|
126
|
+
let receipt
|
|
127
|
+
try {
|
|
128
|
+
receipt = await handler({
|
|
129
|
+
args: validated.value,
|
|
130
|
+
invocation,
|
|
131
|
+
signer: options.signer,
|
|
132
|
+
store: options.store,
|
|
133
|
+
})
|
|
134
|
+
} catch (error) {
|
|
135
|
+
return serverErrorResponse(
|
|
136
|
+
500,
|
|
137
|
+
error instanceof Error ? error.message : String(error),
|
|
138
|
+
{ cid, cmd }
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const validatedReceipt = await command.receipt['~standard'].validate(
|
|
143
|
+
/** @type {any} */ (receipt)
|
|
144
|
+
)
|
|
145
|
+
if (validatedReceipt.issues) {
|
|
146
|
+
return serverErrorResponse(500, `Invalid receipt for ${cmd}`, {
|
|
147
|
+
cid,
|
|
148
|
+
cmd,
|
|
149
|
+
issues: validatedReceipt.issues,
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return Response.json(receipt)
|
|
154
|
+
}
|
|
155
|
+
}
|
package/src/rpc/types.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime stub for the type-only `./types.ts` module so that
|
|
3
|
+
* `export * from './types.js'` in `index.js` resolves under plain Node
|
|
4
|
+
* (e.g. when consumers run `.ts` files via `--experimental-strip-types`).
|
|
5
|
+
*
|
|
6
|
+
* All real declarations live in `./types.ts`; TypeScript's bundler
|
|
7
|
+
* resolution prefers the `.ts` file at type-check time.
|
|
8
|
+
*/
|
|
9
|
+
export {}
|
package/src/rpc/types.ts
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from '@standard-schema/spec'
|
|
2
|
+
import type { ISigner } from 'iso-signatures/types'
|
|
3
|
+
import type { Capability } from '../capability.js'
|
|
4
|
+
import type { Invocation } from '../invocation.js'
|
|
5
|
+
import type { Store } from '../store.js'
|
|
6
|
+
import type { ClientOptions, Promisable, VerifierResolver } from '../types.js'
|
|
7
|
+
|
|
8
|
+
export interface CommandOptions<
|
|
9
|
+
Args extends StandardSchemaV1,
|
|
10
|
+
Cmd extends string = string,
|
|
11
|
+
ReceiptSchema extends StandardSchemaV1 = StandardSchemaV1,
|
|
12
|
+
> {
|
|
13
|
+
cmd: Cmd
|
|
14
|
+
args: Args
|
|
15
|
+
receipt: ReceiptSchema
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface CommandOutput<
|
|
19
|
+
Args extends StandardSchemaV1,
|
|
20
|
+
Cmd extends string = string,
|
|
21
|
+
ReceiptSchema extends StandardSchemaV1 = StandardSchemaV1,
|
|
22
|
+
> {
|
|
23
|
+
capability: Capability<Args, Cmd>
|
|
24
|
+
receipt: ReceiptSchema
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type AnyCommand = CommandOutput<
|
|
28
|
+
StandardSchemaV1,
|
|
29
|
+
string,
|
|
30
|
+
StandardSchemaV1
|
|
31
|
+
>
|
|
32
|
+
|
|
33
|
+
export type CommandsRecord = Record<string, AnyCommand>
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Union of `{cmd, args, receipt}` shapes derived from a commands record.
|
|
37
|
+
* Used to discriminate `request` calls by `cmd`.
|
|
38
|
+
*
|
|
39
|
+
* `args` is the schema's input type (what the caller provides) and `receipt`
|
|
40
|
+
* is the schema's output type (what the caller receives back).
|
|
41
|
+
*/
|
|
42
|
+
export type CommandUnion<Commands extends CommandsRecord> = {
|
|
43
|
+
[K in keyof Commands]: Commands[K] extends CommandOutput<
|
|
44
|
+
infer Args,
|
|
45
|
+
infer Cmd,
|
|
46
|
+
infer ReceiptSchema
|
|
47
|
+
>
|
|
48
|
+
? {
|
|
49
|
+
cmd: Cmd
|
|
50
|
+
args: StandardSchemaV1.InferInput<Args>
|
|
51
|
+
receipt: StandardSchemaV1.InferOutput<ReceiptSchema>
|
|
52
|
+
}
|
|
53
|
+
: never
|
|
54
|
+
}[keyof Commands]
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Server-side counterpart to {@link CommandUnion}: `args` is the schema's
|
|
58
|
+
* output (validated value passed to the handler) and `receipt` is the
|
|
59
|
+
* schema's input — the full receipt object, including `cid`, that the
|
|
60
|
+
* handler is responsible for constructing (typically using `invocation.cid`).
|
|
61
|
+
*/
|
|
62
|
+
export type ServerCommandUnion<Commands extends CommandsRecord> = {
|
|
63
|
+
[K in keyof Commands]: Commands[K] extends CommandOutput<
|
|
64
|
+
infer Args,
|
|
65
|
+
infer Cmd,
|
|
66
|
+
infer ReceiptSchema
|
|
67
|
+
>
|
|
68
|
+
? {
|
|
69
|
+
cmd: Cmd
|
|
70
|
+
args: StandardSchemaV1.InferOutput<Args>
|
|
71
|
+
receipt: StandardSchemaV1.InferInput<ReceiptSchema>
|
|
72
|
+
}
|
|
73
|
+
: never
|
|
74
|
+
}[keyof Commands]
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Options for {@link defineClient}.
|
|
78
|
+
*
|
|
79
|
+
* Same as {@link ClientOptions} but the `capabilities` are derived from the
|
|
80
|
+
* commands record passed as the first argument.
|
|
81
|
+
*/
|
|
82
|
+
export type DefineClientOptions = Omit<ClientOptions, 'capabilities'>
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Options for {@link CommandClient.request}.
|
|
86
|
+
*/
|
|
87
|
+
export type RequestOptions<
|
|
88
|
+
Commands extends CommandsRecord,
|
|
89
|
+
Cmd extends CommandUnion<Commands>['cmd'] = CommandUnion<Commands>['cmd'],
|
|
90
|
+
> = {
|
|
91
|
+
cmd: Cmd
|
|
92
|
+
args: Extract<CommandUnion<Commands>, { cmd: Cmd }>['args']
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Type-safe client built from a record of commands.
|
|
97
|
+
*/
|
|
98
|
+
export interface CommandClient<Commands extends CommandsRecord> {
|
|
99
|
+
request<Cmd extends CommandUnion<Commands>['cmd']>(
|
|
100
|
+
options: RequestOptions<Commands, Cmd>
|
|
101
|
+
): Promise<Extract<CommandUnion<Commands>, { cmd: Cmd }>['receipt']>
|
|
102
|
+
invoke<Cmd extends CommandUnion<Commands>['cmd']>(
|
|
103
|
+
options: RequestOptions<Commands, Cmd>
|
|
104
|
+
): Promise<Invocation>
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Options passed to a server handler.
|
|
109
|
+
*
|
|
110
|
+
* `args` is the validated/decoded args from the command's schema.
|
|
111
|
+
*/
|
|
112
|
+
export interface ServerHandlerOptions<Args> {
|
|
113
|
+
args: Args
|
|
114
|
+
invocation: Invocation
|
|
115
|
+
signer: ISigner
|
|
116
|
+
store: Store
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Map of server handlers keyed by `cmd`, derived from a commands record.
|
|
121
|
+
*
|
|
122
|
+
* Each handler receives args typed as the command's args schema output and
|
|
123
|
+
* must return a value matching the command's receipt schema input.
|
|
124
|
+
*/
|
|
125
|
+
export type ServerHandlers<Commands extends CommandsRecord> = {
|
|
126
|
+
[Cmd in ServerCommandUnion<Commands>['cmd']]: (
|
|
127
|
+
options: ServerHandlerOptions<
|
|
128
|
+
Extract<ServerCommandUnion<Commands>, { cmd: Cmd }>['args']
|
|
129
|
+
>
|
|
130
|
+
) => Promisable<
|
|
131
|
+
Extract<ServerCommandUnion<Commands>, { cmd: Cmd }>['receipt']
|
|
132
|
+
>
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Options for {@link defineServer}.
|
|
137
|
+
*/
|
|
138
|
+
export interface DefineServerOptions<Commands extends CommandsRecord> {
|
|
139
|
+
handlers: ServerHandlers<Commands>
|
|
140
|
+
/**
|
|
141
|
+
* The server's signer. Used as the audience when decoding the incoming
|
|
142
|
+
* invocation and passed through to handlers as `signer` so they can issue
|
|
143
|
+
* receipts or further invocations.
|
|
144
|
+
*/
|
|
145
|
+
signer: ISigner
|
|
146
|
+
store: Store
|
|
147
|
+
verifierResolver: VerifierResolver
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Handler for an incoming HTTP request, returned by {@link defineServer}.
|
|
152
|
+
*/
|
|
153
|
+
export type ServerRequestHandler = (request: Request) => Promise<Response>
|
package/src/store.js
CHANGED
|
@@ -128,14 +128,28 @@ export class Store {
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
/**
|
|
131
|
-
* Resolve
|
|
132
|
-
*
|
|
131
|
+
* Resolve the first such chain found (depth-first), ordered from root to leaf.
|
|
132
|
+
*
|
|
133
|
+
* Cycles are detected via a per-path visited set of delegation CIDs so that
|
|
134
|
+
* self-delegations or other loops can be skipped without recursing forever.
|
|
133
135
|
*
|
|
134
136
|
* @param {import('./types.js').StoreProofsOptions} options
|
|
135
137
|
* @returns {Promise<Delegation[]>}
|
|
136
138
|
*/
|
|
137
139
|
async chain({ aud, sub, cmd, args }) {
|
|
138
|
-
|
|
140
|
+
const path = await this.#chain({ aud, sub, cmd, args }, new Set())
|
|
141
|
+
return path ? path.reverse() : []
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Internal depth-first search for a delegation chain. Returns the path in
|
|
146
|
+
* leaf-to-root order, or `null` if no path exists.
|
|
147
|
+
*
|
|
148
|
+
* @param {import('./types.js').StoreProofsOptions} options
|
|
149
|
+
* @param {Set<string>} visited - delegation CIDs already on the current path
|
|
150
|
+
* @returns {Promise<Delegation[] | null>}
|
|
151
|
+
*/
|
|
152
|
+
async #chain({ aud, sub, cmd, args }, visited) {
|
|
139
153
|
const parents = parentCmds(cmd)
|
|
140
154
|
|
|
141
155
|
const sources = merge(
|
|
@@ -147,28 +161,39 @@ export class Store {
|
|
|
147
161
|
if (!parents.includes(proof.cmd)) continue
|
|
148
162
|
if (!validate(args, proof.pol)) continue
|
|
149
163
|
|
|
164
|
+
const cidStr = proof.cid.toString()
|
|
165
|
+
if (visited.has(cidStr)) continue
|
|
166
|
+
|
|
167
|
+
const issDid = didParse(proof.iss).did
|
|
168
|
+
|
|
150
169
|
// If root, return this proof as the end of the path
|
|
151
|
-
if (proof.sub ===
|
|
152
|
-
// console.log('🚀 ~ found root:', proof.iss, proof.sub, proof.cmd)
|
|
170
|
+
if (proof.sub === issDid) {
|
|
153
171
|
return [proof]
|
|
154
172
|
}
|
|
155
173
|
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
174
|
+
// Skip non-root self-delegations (iss === aud). They never add
|
|
175
|
+
// capabilities a chain wouldn't already have through the predecessor
|
|
176
|
+
// delegation, and following them only lengthens the resulting chain.
|
|
177
|
+
if (issDid === proof.aud) continue
|
|
178
|
+
|
|
179
|
+
visited.add(cidStr)
|
|
180
|
+
const nextPath = await this.#chain(
|
|
181
|
+
{
|
|
182
|
+
aud: issDid,
|
|
183
|
+
sub: proof.sub ? proof.sub : sub,
|
|
184
|
+
cmd: proof.cmd,
|
|
185
|
+
args,
|
|
186
|
+
},
|
|
187
|
+
visited
|
|
188
|
+
)
|
|
189
|
+
visited.delete(cidStr)
|
|
190
|
+
|
|
164
191
|
if (nextPath?.length) {
|
|
165
192
|
return [proof, ...nextPath]
|
|
166
193
|
}
|
|
167
|
-
// console.log('🚀 ~ no path found')
|
|
168
194
|
}
|
|
169
195
|
|
|
170
|
-
|
|
171
|
-
return []
|
|
196
|
+
return null
|
|
172
197
|
}
|
|
173
198
|
}
|
|
174
199
|
|
package/src/types.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type { Policy } from './types/policy'
|
|
|
13
13
|
export type { StandardSchemaV1 } from '@standard-schema/spec'
|
|
14
14
|
export type { DID, DIDURL, DIDURLObject } from 'iso-did/types'
|
|
15
15
|
export type { ISigner } from 'iso-signatures/types'
|
|
16
|
+
export * from './rpc/types'
|
|
16
17
|
export * from './types/envelope'
|
|
17
18
|
export * from './types/policy'
|
|
18
19
|
export * from './types/varsig'
|
|
@@ -63,12 +64,22 @@ export interface DelegationOptions<Schema extends StandardSchemaV1> {
|
|
|
63
64
|
nonce?: Uint8Array
|
|
64
65
|
cmd: string
|
|
65
66
|
meta?: CborObject
|
|
67
|
+
/**
|
|
68
|
+
* The current time in seconds. Mostly used for testing.
|
|
69
|
+
* @default Math.floor(Date.now() / 1000)
|
|
70
|
+
*/
|
|
71
|
+
now?: number
|
|
66
72
|
}
|
|
67
73
|
|
|
68
74
|
export interface DelegationValidateOptions {
|
|
69
75
|
isRevoked?: IsRevoked
|
|
70
76
|
didResolver?: DidResolver
|
|
71
77
|
verifierResolver: VerifierResolver
|
|
78
|
+
/**
|
|
79
|
+
* The current time in seconds. Mostly used for testing.
|
|
80
|
+
* @default Math.floor(Date.now() / 1000)
|
|
81
|
+
*/
|
|
82
|
+
now?: number
|
|
72
83
|
}
|
|
73
84
|
|
|
74
85
|
export interface DelegationFromOptions extends DelegationValidateOptions {
|
|
@@ -113,7 +124,7 @@ export interface InvocationOptions extends DelegationValidateOptions {
|
|
|
113
124
|
|
|
114
125
|
export interface InvocationFromOptions extends DelegationValidateOptions {
|
|
115
126
|
bytes: Uint8Array
|
|
116
|
-
audience
|
|
127
|
+
audience?: VerifiableDID
|
|
117
128
|
resolveProof: ResolveProof
|
|
118
129
|
}
|
|
119
130
|
|
|
@@ -127,7 +138,7 @@ export interface InvocationFromOptions extends DelegationValidateOptions {
|
|
|
127
138
|
export interface CapabilityOptions<
|
|
128
139
|
Schema extends StandardSchemaV1,
|
|
129
140
|
Cmd extends string = string,
|
|
130
|
-
>
|
|
141
|
+
> {
|
|
131
142
|
schema: Schema
|
|
132
143
|
cmd: Cmd
|
|
133
144
|
}
|
|
@@ -147,10 +158,7 @@ export interface CapabilityDelegateOptions<Schema extends StandardSchemaV1>
|
|
|
147
158
|
* Invoke a capability
|
|
148
159
|
*/
|
|
149
160
|
export interface CapabilityInvokeOptions<Schema extends StandardSchemaV1>
|
|
150
|
-
extends Omit<
|
|
151
|
-
InvocationOptions,
|
|
152
|
-
'cmd' | 'args' | 'prf' | 'verifierResolver' | 'didResolver' | 'isRevoked'
|
|
153
|
-
> {
|
|
161
|
+
extends Omit<InvocationOptions, 'cmd' | 'args' | 'prf'> {
|
|
154
162
|
args: StandardSchemaV1.InferOutput<Schema>
|
|
155
163
|
/**
|
|
156
164
|
* Store to resolve proofs from
|
|
@@ -267,4 +275,5 @@ export type ClientOptions = {
|
|
|
267
275
|
audience: DIDURLObject
|
|
268
276
|
store: Store
|
|
269
277
|
capabilities: Capability<StandardSchemaV1, string>[]
|
|
278
|
+
verifierResolver: VerifierResolver
|
|
270
279
|
}
|
package/src/utils.js
CHANGED
|
@@ -42,7 +42,7 @@ export function nowInSeconds() {
|
|
|
42
42
|
export async function cid(envelope) {
|
|
43
43
|
const bytes = Envelope.encode({
|
|
44
44
|
signature: envelope.signature,
|
|
45
|
-
// @ts-
|
|
45
|
+
// @ts-expect-error
|
|
46
46
|
signaturePayload: signaturePayload(envelope),
|
|
47
47
|
})
|
|
48
48
|
const hash = await sha256.digest(bytes)
|
|
@@ -91,9 +91,9 @@ export function isSigAndDidCompatible(did, sigType) {
|
|
|
91
91
|
* Validate the expiration of a UCAN
|
|
92
92
|
*
|
|
93
93
|
* @param {number | null} exp
|
|
94
|
+
* @param {number} [now]
|
|
94
95
|
*/
|
|
95
|
-
export function assertExpiration(exp) {
|
|
96
|
-
const now = nowInSeconds()
|
|
96
|
+
export function assertExpiration(exp, now = nowInSeconds()) {
|
|
97
97
|
if (exp !== null && !Number.isSafeInteger(exp)) {
|
|
98
98
|
throw new TypeError(
|
|
99
99
|
`UCAN expiration must be null or a safe integer. Received: ${exp}`
|
|
@@ -111,9 +111,10 @@ export function assertExpiration(exp) {
|
|
|
111
111
|
* Validate the not before time of a UCAN
|
|
112
112
|
*
|
|
113
113
|
* @param {number} [nbf]
|
|
114
|
+
* @param {number} [now]
|
|
114
115
|
*/
|
|
115
|
-
export function assertNotBefore(nbf) {
|
|
116
|
-
if (nbf && nbf >
|
|
116
|
+
export function assertNotBefore(nbf, now = nowInSeconds()) {
|
|
117
|
+
if (nbf && nbf > now) {
|
|
117
118
|
throw new Error('UCAN not valid yet')
|
|
118
119
|
}
|
|
119
120
|
}
|
|
@@ -149,15 +150,19 @@ export async function verifySignature(
|
|
|
149
150
|
didResolver
|
|
150
151
|
) {
|
|
151
152
|
const issuer = await validateIssuerAndSignature(envelope, didResolver)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
153
|
+
try {
|
|
154
|
+
const isVerified = await signatureVerifierResolver.verify({
|
|
155
|
+
signature: envelope.signature,
|
|
156
|
+
message: dagCbor.encode(signaturePayload(envelope)),
|
|
157
|
+
did: issuer,
|
|
158
|
+
type: envelope.alg,
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
if (!isVerified) {
|
|
162
|
+
throw new Error('UCAN signature verification failed')
|
|
163
|
+
}
|
|
164
|
+
} catch (error) {
|
|
165
|
+
throw new Error('UCAN signature verification failed', { cause: error })
|
|
161
166
|
}
|
|
162
167
|
|
|
163
168
|
return true
|
|
@@ -333,7 +338,7 @@ export const selector = z.union([
|
|
|
333
338
|
export const statement =
|
|
334
339
|
/** @type {typeof z.lazy<z.ZodType<import('../src/types.js').Statement<unknown>>>} */ (
|
|
335
340
|
z.lazy
|
|
336
|
-
// @ts-
|
|
341
|
+
// @ts-expect-error
|
|
337
342
|
)(() => {
|
|
338
343
|
return z.union([
|
|
339
344
|
z.tuple([
|
package/dist/src/client.d.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @import {InferProtocol, RouteOutput, StandardSchemaV1, ClientOptions, RouterClient} from './types'
|
|
3
|
-
* @import {Capability} from './capability'
|
|
4
|
-
*/
|
|
5
|
-
/**
|
|
6
|
-
* Workaround to make imports types work
|
|
7
|
-
* @template {{ cmd: string; in: unknown; out: unknown; }} T
|
|
8
|
-
* @typedef {Object} DELETE_ME
|
|
9
|
-
* @prop {string} url
|
|
10
|
-
* @prop {Capability<StandardSchemaV1, string>[]} capabilities
|
|
11
|
-
* @prop {InferProtocol<Record<string, RouteOutput<Capability<StandardSchemaV1>, unknown>>>} router
|
|
12
|
-
* @prop {ClientOptions} options
|
|
13
|
-
* @prop {RouterClient<T>} routerClient
|
|
14
|
-
*/
|
|
15
|
-
/**
|
|
16
|
-
* @template {InferProtocol<Record<string, RouteOutput<Capability<StandardSchemaV1>, unknown>>>} T
|
|
17
|
-
* @param {ClientOptions} options
|
|
18
|
-
* @returns {RouterClient<T>}
|
|
19
|
-
*/
|
|
20
|
-
export function createClient<T extends InferProtocol<Record<string, RouteOutput<Capability<StandardSchemaV1>, unknown>>>>(options: ClientOptions): RouterClient<T>;
|
|
21
|
-
/**
|
|
22
|
-
* Workaround to make imports types work
|
|
23
|
-
*/
|
|
24
|
-
export type DELETE_ME<T extends {
|
|
25
|
-
cmd: string;
|
|
26
|
-
in: unknown;
|
|
27
|
-
out: unknown;
|
|
28
|
-
}> = {
|
|
29
|
-
url: string;
|
|
30
|
-
capabilities: Capability<StandardSchemaV1, string>[];
|
|
31
|
-
router: InferProtocol<Record<string, RouteOutput<Capability<StandardSchemaV1>, unknown>>>;
|
|
32
|
-
options: ClientOptions;
|
|
33
|
-
routerClient: RouterClient<T>;
|
|
34
|
-
};
|
|
35
|
-
import type { StandardSchemaV1 } from './types';
|
|
36
|
-
import type { Capability } from './capability';
|
|
37
|
-
import type { RouteOutput } from './types';
|
|
38
|
-
import type { InferProtocol } from './types';
|
|
39
|
-
import type { ClientOptions } from './types';
|
|
40
|
-
import type { RouterClient } from './types';
|
|
41
|
-
//# sourceMappingURL=client.d.ts.map
|
package/dist/src/client.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.js"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;GASG;AAEH;;;;GAIG;AACH,6BAJiG,CAAC,SAArF,cAAe,MAAM,CAAC,MAAM,EAAE,YAAY,WAAW,gBAAgB,CAAC,EAAE,OAAO,CAAC,CAAC,CAAE,WACrF,aAAa,GACX,aAAa,CAAC,CAAC,CAgC3B;;;;sBA5C0D,CAAC,SAA/C;IAAG,GAAG,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,OAAO,CAAC;CAAG;SAEhD,MAAM;kBACN,WAAW,gBAAgB,EAAE,MAAM,CAAC,EAAE;YACtC,cAAc,MAAM,CAAC,MAAM,EAAE,YAAY,WAAW,gBAAgB,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;aACjF,aAAa;kBACb,aAAa,CAAC,CAAC;;sCAZmE,SAAS;gCACxE,cAAc;iCADiD,SAAS;mCAAT,SAAS;mCAAT,SAAS;kCAAT,SAAS"}
|
package/dist/src/server.d.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @import { RouteOutput, StandardSchemaV1, RouteOptions, RouteHandlerOptions} from './types'
|
|
3
|
-
* @import {Capability} from './capability'
|
|
4
|
-
*/
|
|
5
|
-
/**
|
|
6
|
-
* Workaround to make imports types work
|
|
7
|
-
* @template {Capability<StandardSchemaV1, string>} T
|
|
8
|
-
* @template Output
|
|
9
|
-
* @typedef {Object} DELETE_ME
|
|
10
|
-
* @prop {string} url
|
|
11
|
-
* @prop {Capability<StandardSchemaV1, string>[]} capabilities
|
|
12
|
-
* @prop {RouteOutput<T, Output>} router
|
|
13
|
-
* @prop {RouteOptions<T, Output>} options
|
|
14
|
-
* @prop {RouteHandlerOptions} routerClient
|
|
15
|
-
*/
|
|
16
|
-
/**
|
|
17
|
-
* @template Output
|
|
18
|
-
* @template {Capability<StandardSchemaV1>} Cap
|
|
19
|
-
* @param {RouteOptions<Cap, Output>} options
|
|
20
|
-
* @returns {RouteOutput<Cap, Output>}
|
|
21
|
-
*/
|
|
22
|
-
export function route<Output, Cap extends Capability<StandardSchemaV1>>(options: RouteOptions<Cap, Output>): RouteOutput<Cap, Output>;
|
|
23
|
-
/**
|
|
24
|
-
* Workaround to make imports types work
|
|
25
|
-
*/
|
|
26
|
-
export type DELETE_ME<T extends Capability<StandardSchemaV1, string>, Output> = {
|
|
27
|
-
url: string;
|
|
28
|
-
capabilities: Capability<StandardSchemaV1, string>[];
|
|
29
|
-
router: RouteOutput<T, Output>;
|
|
30
|
-
options: RouteOptions<T, Output>;
|
|
31
|
-
routerClient: RouteHandlerOptions;
|
|
32
|
-
};
|
|
33
|
-
import type { StandardSchemaV1 } from './types';
|
|
34
|
-
import type { Capability } from './capability';
|
|
35
|
-
import type { RouteOptions } from './types';
|
|
36
|
-
import type { RouteOutput } from './types';
|
|
37
|
-
import type { RouteHandlerOptions } from './types';
|
|
38
|
-
//# sourceMappingURL=server.d.ts.map
|
package/dist/src/server.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server.js"],"names":[],"mappings":"AAIA;;;GAGG;AAEH;;;;;;;;;;GAUG;AAEH;;;;;GAKG;AACH,sBALa,MAAM,EACyB,GAAG,SAAlC,WAAY,gBAAgB,CAAE,WAChC,aAAa,GAAG,EAAE,MAAM,CAAC,GACvB,YAAY,GAAG,EAAE,MAAM,CAAC,CAqBpC;;;;sBAnCmD,CAAC,SAAxC,WAAY,gBAAgB,EAAE,MAAM,CAAE,EACtC,MAAM;SAET,MAAM;kBACN,WAAW,gBAAgB,EAAE,MAAM,CAAC,EAAE;YACtC,YAAY,CAAC,EAAE,MAAM,CAAC;aACtB,aAAa,CAAC,EAAE,MAAM,CAAC;kBACvB,mBAAmB;;sCAbuD,SAAS;gCAChE,cAAc;kCADyC,SAAS;iCAAT,SAAS;yCAAT,SAAS"}
|
package/src/client.js
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @import {InferProtocol, RouteOutput, StandardSchemaV1, ClientOptions, RouterClient} from './types'
|
|
3
|
-
* @import {Capability} from './capability'
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Workaround to make imports types work
|
|
8
|
-
* @template {{ cmd: string; in: unknown; out: unknown; }} T
|
|
9
|
-
* @typedef {Object} DELETE_ME
|
|
10
|
-
* @prop {string} url
|
|
11
|
-
* @prop {Capability<StandardSchemaV1, string>[]} capabilities
|
|
12
|
-
* @prop {InferProtocol<Record<string, RouteOutput<Capability<StandardSchemaV1>, unknown>>>} router
|
|
13
|
-
* @prop {ClientOptions} options
|
|
14
|
-
* @prop {RouterClient<T>} routerClient
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* @template {InferProtocol<Record<string, RouteOutput<Capability<StandardSchemaV1>, unknown>>>} T
|
|
19
|
-
* @param {ClientOptions} options
|
|
20
|
-
* @returns {RouterClient<T>}
|
|
21
|
-
*/
|
|
22
|
-
export function createClient(options) {
|
|
23
|
-
const { url, capabilities } = options
|
|
24
|
-
|
|
25
|
-
// Create a map of capabilities by their `cmd` property
|
|
26
|
-
const capabilityMap = Object.fromEntries(
|
|
27
|
-
capabilities.map((cap) => [cap.cmd, cap])
|
|
28
|
-
)
|
|
29
|
-
return {
|
|
30
|
-
request: async (/** @type {string} */ cmd, /** @type {any} */ args) => {
|
|
31
|
-
const capability = capabilityMap[cmd]
|
|
32
|
-
|
|
33
|
-
if (!capability) {
|
|
34
|
-
throw new Error(`Capability not found: ${cmd}`)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const inv = await capability.invoke({
|
|
38
|
-
args,
|
|
39
|
-
iss: options.issuer,
|
|
40
|
-
sub: options.audience.did,
|
|
41
|
-
store: options.store,
|
|
42
|
-
exp: null,
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
const response = await fetch(url, {
|
|
46
|
-
method: 'POST',
|
|
47
|
-
body: /** @type {BufferSource} */ (inv.bytes),
|
|
48
|
-
})
|
|
49
|
-
return response.json()
|
|
50
|
-
},
|
|
51
|
-
}
|
|
52
|
-
}
|