@tokono.ma/firkin 0.3.0 → 0.4.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/README.md +84 -14
- package/dist/bao.d.ts +25 -0
- package/dist/bao.js +225 -0
- package/dist/client.d.ts +210 -10
- package/dist/client.js +309 -16
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/plc.d.ts +87 -0
- package/dist/plc.js +97 -0
- package/dist/ucan.d.ts +7 -0
- package/dist/ucan.js +4 -1
- package/dist/webauthn.d.ts +56 -9
- package/dist/webauthn.js +83 -22
- package/package.json +5 -4
package/dist/client.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { CID } from "multiformats";
|
|
|
3
3
|
* live outside it). */
|
|
4
4
|
export declare const API_PREFIX = "/v1";
|
|
5
5
|
import type { Signer } from "./keys.ts";
|
|
6
|
+
import { type WebAuthnAssertion } from "./webauthn.ts";
|
|
7
|
+
import type { PlcAttestationMaterial } from "./plc.ts";
|
|
6
8
|
/** Per-call options a TokenSource may honor. */
|
|
7
9
|
export interface TokenOptions {
|
|
8
10
|
/** Override the token's expiry (seconds from now). */
|
|
@@ -15,6 +17,19 @@ export interface TokenOptions {
|
|
|
15
17
|
export type TokenSource = (command: string, options?: TokenOptions) => Promise<Uint8Array>;
|
|
16
18
|
/** Signer-backed source: mints a fresh direct invocation per request. */
|
|
17
19
|
export declare function signerToken(signer: Signer, expireIn?: number): TokenSource;
|
|
20
|
+
/**
|
|
21
|
+
* Source for an **attested session**: every invocation carries
|
|
22
|
+
* `meta["firkin/account"]`, the hint the server uses to resolve the
|
|
23
|
+
* session → account binding. The session key signs directly (no proofs); the
|
|
24
|
+
* binding, not the token, carries the account's authority.
|
|
25
|
+
*
|
|
26
|
+
* Obtain the binding with a passkey: `attestationChallenge(...)` →
|
|
27
|
+
* `credentials.get` → `Firkin.attest(...)`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function sessionTokenSource(signer: Signer, opts: {
|
|
30
|
+
account: string;
|
|
31
|
+
expireIn?: number;
|
|
32
|
+
}): TokenSource;
|
|
18
33
|
/** Fixed-token source: only appropriate for public/read-only usage. */
|
|
19
34
|
export declare function staticToken(token: Uint8Array): TokenSource;
|
|
20
35
|
export interface CarIngestResult {
|
|
@@ -31,6 +46,121 @@ export interface CarIngestResult {
|
|
|
31
46
|
error: string;
|
|
32
47
|
}[];
|
|
33
48
|
}
|
|
49
|
+
/** One page of a tag group. */
|
|
50
|
+
export interface TagPage {
|
|
51
|
+
name: string;
|
|
52
|
+
cids: string[];
|
|
53
|
+
count: number;
|
|
54
|
+
cursor: number;
|
|
55
|
+
/** Next page cursor, or null when this was the last page. */
|
|
56
|
+
next: number | null;
|
|
57
|
+
}
|
|
58
|
+
/** Result of `POST /v1/tokens/attestation`. */
|
|
59
|
+
export type AttestationResult = SessionAttestation | RecoveryAttestation;
|
|
60
|
+
/** A session binding (passkey or `did:plc`). */
|
|
61
|
+
export interface SessionAttestation {
|
|
62
|
+
kind: "session";
|
|
63
|
+
/** Content-addressed attestation id — revoke it with `revoke({cids})`. */
|
|
64
|
+
cid: string;
|
|
65
|
+
account: string;
|
|
66
|
+
/** The bound `did:key` that signs API calls. */
|
|
67
|
+
session: string;
|
|
68
|
+
commands: string[];
|
|
69
|
+
nbf: number;
|
|
70
|
+
exp: number;
|
|
71
|
+
}
|
|
72
|
+
/** A recovery grant: `recovery` may call `POST /v1/account/recover`. */
|
|
73
|
+
export interface RecoveryAttestation {
|
|
74
|
+
kind: "recovery";
|
|
75
|
+
cid: string;
|
|
76
|
+
account: string;
|
|
77
|
+
/** The DID authorised to recover the account. */
|
|
78
|
+
recovery: string;
|
|
79
|
+
/** Unix seconds; null for an indefinite grant. */
|
|
80
|
+
exp: number | null;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Canonicalize an instance origin: scheme + host + optional port, lowercased,
|
|
84
|
+
* with no trailing slash, path, query, fragment or userinfo.
|
|
85
|
+
*
|
|
86
|
+
* This matters twice over. Firkin compares the `firkin` audience field of its
|
|
87
|
+
* own records to `ORIGIN` exactly, and the ATProto indexers that can discover
|
|
88
|
+
* appeals — Constellation backlinks and Spacedust links — match URLs
|
|
89
|
+
* byte-for-byte, so `https://Firkin.example/` and `https://firkin.example`
|
|
90
|
+
* would be two unrelated values to them.
|
|
91
|
+
*
|
|
92
|
+
* Throws on a string that is not an absolute URL (e.g. a bare host).
|
|
93
|
+
*/
|
|
94
|
+
export declare function normalizeOrigin(origin: string): string;
|
|
95
|
+
/** NSID of the access appeal an operator's firehose consumer reads.
|
|
96
|
+
* See `lexicons/ma.tokono.firkin.appeal.json`. */
|
|
97
|
+
export declare const APPEAL_COLLECTION = "ma.tokono.firkin.appeal";
|
|
98
|
+
/** An appeal to be registered on one Firkin instance. */
|
|
99
|
+
export interface AppealRequest {
|
|
100
|
+
$type: "ma.tokono.firkin.appeal";
|
|
101
|
+
/** The target instance's canonical origin, e.g. `https://firkin.example`
|
|
102
|
+
* (see `normalizeOrigin`). */
|
|
103
|
+
firkin: string;
|
|
104
|
+
/** ISO 8601 timestamp. */
|
|
105
|
+
createdAt: string;
|
|
106
|
+
/** Optional ISO 8601 timestamp after which the appeal is void. */
|
|
107
|
+
expiresAt?: string;
|
|
108
|
+
/** Optional opaque correlation value. */
|
|
109
|
+
nonce?: string;
|
|
110
|
+
/** Optional message for the operator. PUBLIC — never put secrets here. */
|
|
111
|
+
note?: string;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Build an access appeal to write into the requesting account's repo.
|
|
115
|
+
*
|
|
116
|
+
* Firkin's HTTP API is admin-gated; this record is how an account asks an
|
|
117
|
+
* operator to register it. An operator-side consumer tails the ATProto
|
|
118
|
+
* firehose, filters this collection, and applies its own policy. Nothing here
|
|
119
|
+
* grants service — the commit signature only proves who appealed.
|
|
120
|
+
*
|
|
121
|
+
* `firkin` is normalized with `normalizeOrigin`, because an operator-side
|
|
122
|
+
* consumer (and any indexer in front of it) matches the value against its own
|
|
123
|
+
* origin exactly.
|
|
124
|
+
*
|
|
125
|
+
* Use `appealRkey(firkin)` as the record key.
|
|
126
|
+
*/
|
|
127
|
+
export declare function appealRequest(opts: {
|
|
128
|
+
firkin: string;
|
|
129
|
+
/** Seconds until `expiresAt` (omit for no expiry). */
|
|
130
|
+
expireIn?: number;
|
|
131
|
+
nonce?: string;
|
|
132
|
+
note?: string;
|
|
133
|
+
/** Override the creation time (unix seconds) — tests only. */
|
|
134
|
+
now?: number;
|
|
135
|
+
}): AppealRequest;
|
|
136
|
+
/** The conventional record key for an appeal: the instance host
|
|
137
|
+
* (e.g. `firkin.example`), which is idempotent and a valid rkey. */
|
|
138
|
+
export declare function appealRkey(firkin: string): string;
|
|
139
|
+
/** A did:plc session record (the value written into the account's repo). */
|
|
140
|
+
export interface PlcSessionRecord {
|
|
141
|
+
$type: "ma.tokono.firkin.session";
|
|
142
|
+
/** The API origin, matching server config (see `normalizeOrigin`). */
|
|
143
|
+
firkin: string;
|
|
144
|
+
/** The `did:key` session that will sign API calls. */
|
|
145
|
+
session: string;
|
|
146
|
+
/** Capability prefixes the session may exercise. */
|
|
147
|
+
commands: string[];
|
|
148
|
+
/** Unix seconds. */
|
|
149
|
+
nbf: number;
|
|
150
|
+
/** Unix seconds; the server caps `exp - nbf`. */
|
|
151
|
+
exp: number;
|
|
152
|
+
}
|
|
153
|
+
/** The record a did:plc account writes into its repo to attest a session
|
|
154
|
+
* (MST key `ma.tokono.firkin.session/<session did>`). Write it once with your
|
|
155
|
+
* ATProto agent, then submit the commit + MST path via `Firkin.attestPlc`. */
|
|
156
|
+
export declare function plcSessionRecord(opts: {
|
|
157
|
+
/** The API origin, matching server config (normalized). */
|
|
158
|
+
firkin: string;
|
|
159
|
+
session: string;
|
|
160
|
+
commands: string[];
|
|
161
|
+
expireIn?: number;
|
|
162
|
+
now?: number;
|
|
163
|
+
}): PlcSessionRecord;
|
|
34
164
|
export interface UploadResult {
|
|
35
165
|
cid: string;
|
|
36
166
|
/** True when the blob was already stored (dedup; claim joined). */
|
|
@@ -72,16 +202,27 @@ export declare class Firkin {
|
|
|
72
202
|
private readonly fetchImpl;
|
|
73
203
|
private readonly blockCache;
|
|
74
204
|
constructor(base: string, token: TokenSource, fetchImpl?: typeof fetch, blockCache?: BlockCache);
|
|
75
|
-
/** Upload bytes; returns the CID and whether it was already stored.
|
|
205
|
+
/** Upload bytes; returns the CID and whether it was already stored.
|
|
206
|
+
* `tags` is client-side sugar: after the bytes are stored (or deduped), the
|
|
207
|
+
* CID is added to each group with `tagAdd`. Tagging is a separate call, so
|
|
208
|
+
* if it fails the blob is already stored and `tagAdd` can be retried. */
|
|
76
209
|
upload(data: Uint8Array | Blob, opts?: {
|
|
77
210
|
codec?: number;
|
|
78
211
|
hash?: "sha256" | "blake3";
|
|
212
|
+
tags?: string | string[];
|
|
79
213
|
}): Promise<UploadResult>;
|
|
80
214
|
/** Fetch a whole blob. Reads are authenticated: the server requires the
|
|
81
215
|
* `/firkin/blob/read` capability (an operator can opt into anonymous
|
|
82
216
|
* reads with `PUBLIC_READS=true`, in which case the header is ignored).
|
|
83
217
|
* Read tokens are reusable — reads are not replay-tracked. */
|
|
84
218
|
get(cid: string): Promise<Uint8Array>;
|
|
219
|
+
/**
|
|
220
|
+
* Read a byte range of a **BDASL (BLAKE3)** blob with verified streaming:
|
|
221
|
+
* the server sends a bao proof and every chunk is checked against the CID
|
|
222
|
+
* before it is returned — the server is not trusted for integrity. Needs a
|
|
223
|
+
* BLAKE3 CID (`cidForBytes(data, { hash: "blake3" })`).
|
|
224
|
+
*/
|
|
225
|
+
verifiedRange(cid: string, range?: ByteRange): Promise<RangeResult>;
|
|
85
226
|
/** Fetch a byte range (206 responses; server-trusted verification).
|
|
86
227
|
* Authenticated like [`get`](#get). */
|
|
87
228
|
getRange(cid: string, range?: ByteRange): Promise<RangeResult>;
|
|
@@ -107,20 +248,40 @@ export declare class Firkin {
|
|
|
107
248
|
readUrl(cid: string, opts?: {
|
|
108
249
|
expireIn?: number;
|
|
109
250
|
}): Promise<string>;
|
|
110
|
-
/** Attach an already-stored CID to the signer's account.
|
|
111
|
-
|
|
251
|
+
/** Attach an already-stored CID to the signer's account. `tags` is the
|
|
252
|
+
* same sugar as `upload`: the CID is added to each group afterwards. */
|
|
253
|
+
claim(cid: string, opts?: {
|
|
254
|
+
tags?: string | string[];
|
|
255
|
+
}): Promise<void>;
|
|
112
256
|
/** Detach the signer's claim; bytes die with the last claimant. */
|
|
113
257
|
delete(cid: string): Promise<void>;
|
|
114
|
-
/** Revoke
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
258
|
+
/** Revoke delegations/sessions: specific delegation or attestation CIDs,
|
|
259
|
+
* or every grant minted before now (`all: true`). Seed/did:key accounts
|
|
260
|
+
* authorize this with a direct invocation by the account key. An attested
|
|
261
|
+
* session must additionally pass a fresh passkey assertion (`stepUp`,
|
|
262
|
+
* built with `stepUpChallenge`) — a stolen session key must not be able to
|
|
263
|
+
* revoke. */
|
|
120
264
|
revoke(opts: {
|
|
121
265
|
cids?: string[];
|
|
122
266
|
all?: boolean;
|
|
123
|
-
}): Promise<void>;
|
|
267
|
+
}, stepUp?: WebAuthnAssertion): Promise<void>;
|
|
268
|
+
/** Attest a session for a `did:plc` account. `material` is what the client
|
|
269
|
+
* fetched from the account's repo and the PLC directory (base64url DAG-CBOR):
|
|
270
|
+
*
|
|
271
|
+
* - `ops`: the PLC operation log, genesis first;
|
|
272
|
+
* - `commit`: the signed repo commit containing the record;
|
|
273
|
+
* - `record`: the `plcSessionRecord(...)` value;
|
|
274
|
+
* - `proof`: the MST nodes proving the record is in that commit.
|
|
275
|
+
*
|
|
276
|
+
* The server verifies everything offline (no PLC/PDS call). */
|
|
277
|
+
static attestPlc(base: string, material: PlcAttestationMaterial, fetchImpl?: typeof fetch): Promise<AttestationResult>;
|
|
278
|
+
/** Enrol a passkey session. The assertion's challenge must be
|
|
279
|
+
* `attestationChallenge(...)`; the server verifies the ceremony, stores the
|
|
280
|
+
* session → account binding, and returns its attestation CID. After this,
|
|
281
|
+
* requests are signed by the session key (see `sessionTokenSource`).
|
|
282
|
+
* Static: the endpoint is unauthenticated by design — the assertion IS the
|
|
283
|
+
* authentication. */
|
|
284
|
+
static attest(base: string, assertion: WebAuthnAssertion, fetchImpl?: typeof fetch): Promise<AttestationResult>;
|
|
124
285
|
/** (Admin) register a DID as an account, optionally binding a WebAuthn
|
|
125
286
|
* credential (`id` = base64url rawId, `spki` = base64url of the browser's
|
|
126
287
|
* getPublicKey() DER). The binding is what lets a fresh browser re-attach
|
|
@@ -156,6 +317,44 @@ export declare class Firkin {
|
|
|
156
317
|
registryDelete(name: string): Promise<void>;
|
|
157
318
|
/** The account's whole registry: name → CID (sorted). */
|
|
158
319
|
registryList(): Promise<Record<string, string>>;
|
|
320
|
+
/** Tag blobs: add CIDs to a per-account label group (idempotent). */
|
|
321
|
+
tagAdd(name: string, cids: string | string[]): Promise<{
|
|
322
|
+
name: string;
|
|
323
|
+
count: number;
|
|
324
|
+
}>;
|
|
325
|
+
/** One page of a tag group (groups are paged; `next` walks them). */
|
|
326
|
+
tagGet(name: string, opts?: {
|
|
327
|
+
cursor?: number;
|
|
328
|
+
}): Promise<TagPage>;
|
|
329
|
+
/** Every CID in a tag group (walks the pages). */
|
|
330
|
+
tagAll(name: string): Promise<string[]>;
|
|
331
|
+
/** The tag groups a CID belongs to (reverse lookup). Needs the full
|
|
332
|
+
* `/firkin/tag/read` command: a per-group proof cannot widen into it. */
|
|
333
|
+
blobTags(cid: string): Promise<string[]>;
|
|
334
|
+
/** Add a CID to each group in `tags` (sugar for `upload`/`claim`). */
|
|
335
|
+
private applyTags;
|
|
336
|
+
/** Every tag group with its CID count. */
|
|
337
|
+
tagList(): Promise<{
|
|
338
|
+
name: string;
|
|
339
|
+
count: number;
|
|
340
|
+
}[]>;
|
|
341
|
+
/** Untag one blob from a group. */
|
|
342
|
+
tagRemove(name: string, cid: string): Promise<{
|
|
343
|
+
name: string;
|
|
344
|
+
count: number;
|
|
345
|
+
}>;
|
|
346
|
+
/** Drop a tag group; the blobs stay stored. */
|
|
347
|
+
tagDelete(name: string): Promise<{
|
|
348
|
+
name: string;
|
|
349
|
+
cids: number;
|
|
350
|
+
}>;
|
|
351
|
+
/** Drop a tag group **and detach every blob in it** (bytes die where this
|
|
352
|
+
* account is the last claimant). Needs `/firkin/tag/delete`. */
|
|
353
|
+
tagDeleteBlobs(name: string): Promise<{
|
|
354
|
+
name: string;
|
|
355
|
+
blobs: number;
|
|
356
|
+
deleted: number;
|
|
357
|
+
}>;
|
|
159
358
|
/**
|
|
160
359
|
* Ingest a rootless CARv1 — "upload a `.car` file". Every block is verified
|
|
161
360
|
* against its own CID, policy-checked and claimed by the account; there is
|
|
@@ -214,6 +413,7 @@ export declare class Firkin {
|
|
|
214
413
|
pullFile(root: string): Promise<Uint8Array>;
|
|
215
414
|
}
|
|
216
415
|
export declare function carFile(root: CID | null, entries: [CID, Uint8Array][]): Uint8Array;
|
|
416
|
+
export declare function parseCarBlocks(car: Uint8Array): Map<string, Uint8Array>;
|
|
217
417
|
export declare function multihashSha256(data: Uint8Array): MultihashDigest;
|
|
218
418
|
type MultihashDigest = {
|
|
219
419
|
code: number;
|