@book.dev/sdk 3.8.0 → 3.10.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/ai.d.ts +12 -2
- package/dist/ai.js.map +1 -1
- package/dist/blockCatalogue.d.ts +560 -0
- package/dist/blockCatalogue.js +336 -0
- package/dist/blockCatalogue.js.map +1 -0
- package/dist/bookFolder.d.ts +9 -1
- package/dist/bookFolder.js +64 -2
- package/dist/bookFolder.js.map +1 -1
- package/dist/client.d.ts +22 -2
- package/dist/client.js +6 -2
- package/dist/client.js.map +1 -1
- package/dist/content.d.ts +40 -1
- package/dist/content.js +29 -7
- package/dist/content.js.map +1 -1
- package/dist/forwarding/forwardingClient.d.ts +68 -2
- package/dist/forwarding/forwardingClient.js +126 -12
- package/dist/forwarding/forwardingClient.js.map +1 -1
- package/dist/forwarding/index.d.ts +2 -1
- package/dist/forwarding/index.js +2 -1
- package/dist/forwarding/index.js.map +1 -1
- package/dist/forwarding/namespacedKeyStore.d.ts +52 -0
- package/dist/forwarding/namespacedKeyStore.js +99 -0
- package/dist/forwarding/namespacedKeyStore.js.map +1 -0
- package/dist/index.d.ts +10 -5
- package/dist/index.js +10 -5
- package/dist/index.js.map +1 -1
- package/dist/ledger.d.ts +29 -0
- package/dist/ledger.js +43 -0
- package/dist/ledger.js.map +1 -1
- package/dist/ledgerExportSection.d.ts +160 -0
- package/dist/ledgerExportSection.js +594 -0
- package/dist/ledgerExportSection.js.map +1 -0
- package/dist/orderKeys.d.ts +36 -0
- package/dist/orderKeys.js +88 -0
- package/dist/orderKeys.js.map +1 -0
- package/dist/plugins.d.ts +79 -8
- package/dist/plugins.js +98 -16
- package/dist/plugins.js.map +1 -1
- package/dist/provenance.d.ts +13 -0
- package/dist/registryClient.d.ts +227 -0
- package/dist/registryClient.js +397 -0
- package/dist/registryClient.js.map +1 -0
- package/dist/routes.d.ts +9 -0
- package/dist/routes.js +9 -0
- package/dist/routes.js.map +1 -1
- package/dist/suggestions.d.ts +12 -2
- package/dist/tableSnapshot.d.ts +233 -0
- package/dist/tableSnapshot.js +680 -0
- package/dist/tableSnapshot.js.map +1 -0
- package/dist/templates.d.ts +8 -0
- package/dist/templates.js +7 -2
- package/dist/templates.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client for the OpenBook registry protocol (`openbook-registry/1`) — the
|
|
3
|
+
* read side a store-browsing app needs: the registry identity document, the
|
|
4
|
+
* paginated install index, per-plugin metadata, verified downloads, and the
|
|
5
|
+
* signed revocation feed.
|
|
6
|
+
*
|
|
7
|
+
* Trust model (PROTOCOL.md §6): a registry is identified by its pinned
|
|
8
|
+
* `(baseUrl, keys)`, never by anything a fetch returned. The caller supplies
|
|
9
|
+
* the pin; this client verifies everything it downloads against it —
|
|
10
|
+
* digest recomputed from the bytes, publisher signature against the index's
|
|
11
|
+
* `pinnedKeys`, notary countersignature and revocation entries against the
|
|
12
|
+
* pinned notary key. Any failed step throws: there is no partial trust.
|
|
13
|
+
*/
|
|
14
|
+
import { type PluginManifest, type PluginPackage } from './plugins';
|
|
15
|
+
import type { FetchLike } from './client';
|
|
16
|
+
export type { FetchLike };
|
|
17
|
+
export declare function isSemver(v: string): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Semver precedence compare (spec §11): numeric MAJOR.MINOR.PATCH, then
|
|
20
|
+
* prerelease (absent > present; numeric identifiers numerically, and lower
|
|
21
|
+
* than alphanumeric ones). Build metadata is ignored. Returns <0, 0, >0.
|
|
22
|
+
* Inputs must already satisfy {@link isSemver}. Byte-for-byte port of the
|
|
23
|
+
* store's `compareSemver` (open-book-pub `packages/store/lib/canonical.ts`).
|
|
24
|
+
*/
|
|
25
|
+
export declare function compareSemver(a: string, b: string): number;
|
|
26
|
+
/** `GET /api/v1/registry` — the identity document a client pins. */
|
|
27
|
+
export interface RegistryDocument {
|
|
28
|
+
protocol: string;
|
|
29
|
+
name: string;
|
|
30
|
+
baseUrl: string;
|
|
31
|
+
apiVersion: number;
|
|
32
|
+
algorithms: string[];
|
|
33
|
+
notaryPublicKey: string | null;
|
|
34
|
+
registryPublicKey: string | null;
|
|
35
|
+
fingerprints: {
|
|
36
|
+
notary: string | null;
|
|
37
|
+
registry: string | null;
|
|
38
|
+
};
|
|
39
|
+
endpoints: Record<string, string>;
|
|
40
|
+
}
|
|
41
|
+
/** One row of `GET /api/v1/index` — a plugin at its latest approved version. */
|
|
42
|
+
export interface RegistryIndexEntry {
|
|
43
|
+
id: string;
|
|
44
|
+
name: string;
|
|
45
|
+
description: string;
|
|
46
|
+
icon: string | null;
|
|
47
|
+
category: string | null;
|
|
48
|
+
publisher: string | null;
|
|
49
|
+
/** Publisher keys this plugin id is pinned to (TOFU), first-seen first. */
|
|
50
|
+
pinnedKeys?: string[];
|
|
51
|
+
/** @deprecated Use `pinnedKeys`; retained as the pre-rotation fallback. */
|
|
52
|
+
pinnedKey: string;
|
|
53
|
+
latestVersion: string;
|
|
54
|
+
/** Canonical digest of the latest approved version — what both signatures cover. */
|
|
55
|
+
digest: string;
|
|
56
|
+
/** Content address of the publisher's uploaded document (pre-splice). */
|
|
57
|
+
artifactSha256: string;
|
|
58
|
+
}
|
|
59
|
+
export interface RegistryIndexPage {
|
|
60
|
+
plugins: RegistryIndexEntry[];
|
|
61
|
+
limit: number;
|
|
62
|
+
nextCursor: string | null;
|
|
63
|
+
hasMore: boolean;
|
|
64
|
+
}
|
|
65
|
+
/** The notary countersignature spliced into approved downloads (§5.4.1). */
|
|
66
|
+
export interface RegistryNotarization {
|
|
67
|
+
registry: string;
|
|
68
|
+
publicKey: string;
|
|
69
|
+
signature: string;
|
|
70
|
+
algorithm: string;
|
|
71
|
+
timestamp: string;
|
|
72
|
+
}
|
|
73
|
+
export interface RegistryPluginVersion {
|
|
74
|
+
version: string;
|
|
75
|
+
digest: string;
|
|
76
|
+
artifactSha256: string;
|
|
77
|
+
status: string;
|
|
78
|
+
createdAt: string;
|
|
79
|
+
manifest: PluginManifest;
|
|
80
|
+
notarization: RegistryNotarization | null;
|
|
81
|
+
}
|
|
82
|
+
/** `GET /api/v1/plugins/{id}` — a plugin's version history. */
|
|
83
|
+
export interface RegistryPluginMeta {
|
|
84
|
+
id: string;
|
|
85
|
+
pinnedKeys?: string[];
|
|
86
|
+
/** @deprecated Use `pinnedKeys`; retained as the pre-rotation fallback. */
|
|
87
|
+
pinnedKey: string;
|
|
88
|
+
createdAt: string;
|
|
89
|
+
versions: RegistryPluginVersion[];
|
|
90
|
+
}
|
|
91
|
+
/** One signed entry of the revocation feed (§5.5). */
|
|
92
|
+
export interface RegistryRevocation {
|
|
93
|
+
id: string;
|
|
94
|
+
seq: number;
|
|
95
|
+
pluginId: string;
|
|
96
|
+
/** The revoked version, or null = every version, including future ones. */
|
|
97
|
+
version: string | null;
|
|
98
|
+
reason: string;
|
|
99
|
+
signerPublicKey: string | null;
|
|
100
|
+
signature: string | null;
|
|
101
|
+
createdAt: string;
|
|
102
|
+
}
|
|
103
|
+
/** The signed freshness/rollback anchor accompanying a revocation feed. */
|
|
104
|
+
export interface RegistryRevocationHead {
|
|
105
|
+
maxSeq: number;
|
|
106
|
+
generatedAt: string;
|
|
107
|
+
publicKey: string;
|
|
108
|
+
signature: string;
|
|
109
|
+
}
|
|
110
|
+
/** The trust states a verified download can earn (§6.5). */
|
|
111
|
+
export interface RegistryTrust {
|
|
112
|
+
/** Package signature verifies with the pinned `registryPublicKey`. */
|
|
113
|
+
firstParty: boolean;
|
|
114
|
+
/** Notary countersignature verifies with the pinned `notaryPublicKey`. */
|
|
115
|
+
notarised: boolean;
|
|
116
|
+
}
|
|
117
|
+
/** A fully verified download: the package plus what the verification proved. */
|
|
118
|
+
export interface VerifiedDownload {
|
|
119
|
+
pkg: PluginPackage;
|
|
120
|
+
/** The recomputed canonical digest the signatures were checked over. */
|
|
121
|
+
digest: string;
|
|
122
|
+
trust: RegistryTrust;
|
|
123
|
+
notarization: RegistryNotarization | null;
|
|
124
|
+
}
|
|
125
|
+
/** The pin a client persists for a registry: base URL + keys, per §6.2. */
|
|
126
|
+
export interface RegistryPin {
|
|
127
|
+
baseUrl: string;
|
|
128
|
+
name?: string;
|
|
129
|
+
notaryPublicKey?: string | null;
|
|
130
|
+
registryPublicKey?: string | null;
|
|
131
|
+
}
|
|
132
|
+
/** A typed failure from the registry client; `code` is stable for branching. */
|
|
133
|
+
export declare class RegistryError extends Error {
|
|
134
|
+
readonly code: 'unsupported_protocol' | 'insecure_base_url' | 'http_error' | 'revoked' | 'not_found' | 'digest_mismatch' | 'etag_mismatch' | 'signature_invalid' | 'notarization_invalid' | 'revocation_unavailable' | 'bad_response';
|
|
135
|
+
constructor(code: 'unsupported_protocol' | 'insecure_base_url' | 'http_error' | 'revoked' | 'not_found' | 'digest_mismatch' | 'etag_mismatch' | 'signature_invalid' | 'notarization_invalid' | 'revocation_unavailable' | 'bad_response', message: string);
|
|
136
|
+
}
|
|
137
|
+
/** Must exceed the endpoint's 60s max-age + 300s SWR cache floor (§6.6). */
|
|
138
|
+
export declare const REVOCATION_STALENESS_MS: number;
|
|
139
|
+
/** HTTPS only, except an explicit developer-local http://localhost (§3). */
|
|
140
|
+
export declare function registryBaseUrlProblem(baseUrl: string): string | null;
|
|
141
|
+
/**
|
|
142
|
+
* Human-comparable fingerprint of a raw Ed25519 key (§6.2): SHA-256 of the
|
|
143
|
+
* raw 32 bytes, truncated to 128 bits, as eight dash-separated hex quads.
|
|
144
|
+
*/
|
|
145
|
+
export declare function registryKeyFingerprint(publicKeyBase64: string): Promise<string>;
|
|
146
|
+
/** Does a revocation entry cover this `(pluginId, version)`? `version: null` covers all. */
|
|
147
|
+
export declare function revocationMatches(entry: Pick<RegistryRevocation, 'pluginId' | 'version'>, pluginId: string, version: string): boolean;
|
|
148
|
+
/**
|
|
149
|
+
* Fetch + validate a registry's identity document WITHOUT a pin — the
|
|
150
|
+
* add-a-registry flow (§6.2): the caller must display the fingerprints for
|
|
151
|
+
* out-of-band confirmation before pinning anything from this response.
|
|
152
|
+
*/
|
|
153
|
+
export declare function fetchRegistryDocument(baseUrl: string, fetchImpl?: FetchLike): Promise<RegistryDocument>;
|
|
154
|
+
/**
|
|
155
|
+
* The read client for one pinned registry. All verification happens against
|
|
156
|
+
* the PIN handed to the constructor — never against keys learned from the
|
|
157
|
+
* network afterwards.
|
|
158
|
+
*/
|
|
159
|
+
export declare class RegistryClient {
|
|
160
|
+
readonly pin: RegistryPin;
|
|
161
|
+
private readonly fetchImpl;
|
|
162
|
+
private doc;
|
|
163
|
+
constructor(pin: RegistryPin, opts?: {
|
|
164
|
+
fetch?: FetchLike;
|
|
165
|
+
});
|
|
166
|
+
private get base();
|
|
167
|
+
private endpoint;
|
|
168
|
+
private json;
|
|
169
|
+
/**
|
|
170
|
+
* The registry's identity document (fetched once, cached). Checked against
|
|
171
|
+
* the pin: a protocol this client does not speak is refused, and a document
|
|
172
|
+
* whose keys disagree with pinned keys is refused as a possible attack
|
|
173
|
+
* (§6.2 — a pin change is a NEW trust decision, never a silent update).
|
|
174
|
+
*/
|
|
175
|
+
document(): Promise<RegistryDocument>;
|
|
176
|
+
/** One page of the install index (§5.2). */
|
|
177
|
+
indexPage(opts?: {
|
|
178
|
+
limit?: number;
|
|
179
|
+
cursor?: string;
|
|
180
|
+
q?: string;
|
|
181
|
+
category?: string;
|
|
182
|
+
}): Promise<RegistryIndexPage>;
|
|
183
|
+
/**
|
|
184
|
+
* Walk the whole (filtered) index, one page at a time — cursors are walked
|
|
185
|
+
* sequentially per §9, capped defensively against a cursor loop.
|
|
186
|
+
*/
|
|
187
|
+
indexAll(opts?: {
|
|
188
|
+
q?: string;
|
|
189
|
+
category?: string;
|
|
190
|
+
limit?: number;
|
|
191
|
+
}): Promise<RegistryIndexEntry[]>;
|
|
192
|
+
/** Find one plugin's index row by exact id (via the substring search filter). */
|
|
193
|
+
findIndexEntry(pluginId: string): Promise<RegistryIndexEntry | null>;
|
|
194
|
+
/** A plugin's version history (§5.3). Null when the registry doesn't know the id. */
|
|
195
|
+
pluginMeta(pluginId: string): Promise<RegistryPluginMeta | null>;
|
|
196
|
+
/**
|
|
197
|
+
* Download one version and verify it offline (§6.3). Every step must pass:
|
|
198
|
+
*
|
|
199
|
+
* 1. sha256(served bytes) equals the strong `ETag` (transport integrity).
|
|
200
|
+
* 2. The canonical digest RECOMPUTED from manifest+files equals `expect.digest`
|
|
201
|
+
* (the value the index/metadata promised) and the `X-Canonical-Digest` hint.
|
|
202
|
+
* 3. The publisher signature verifies over that digest with ANY member of
|
|
203
|
+
* `expect.pinnedKeys` — never with the key that travelled inside the package.
|
|
204
|
+
* 4. A present notary countersignature verifies with the PINNED notary key —
|
|
205
|
+
* present-but-invalid is a hard failure, not a downgrade to "unnotarised".
|
|
206
|
+
*
|
|
207
|
+
* Revocations are the caller's step (§6.6): check {@link revocations} before
|
|
208
|
+
* installing what this returns.
|
|
209
|
+
*/
|
|
210
|
+
download(pluginId: string, version: string, expect: {
|
|
211
|
+
digest: string;
|
|
212
|
+
pinnedKeys: string[];
|
|
213
|
+
}): Promise<VerifiedDownload>;
|
|
214
|
+
/**
|
|
215
|
+
* The revocation feed (§5.5, §6.6), from `since` (exclusive). Entries are
|
|
216
|
+
* verified against the pinned notary key; on a registry that advertises a
|
|
217
|
+
* notary key, unsigned or badly-signed entries are IGNORED (an injection is
|
|
218
|
+
* not a kill switch). Only a registry with no notary key at all has its
|
|
219
|
+
* unsigned entries honoured — failing open on the kill switch is the
|
|
220
|
+
* dangerous direction.
|
|
221
|
+
*/
|
|
222
|
+
revocations(since?: number): Promise<{
|
|
223
|
+
maxSeq: number;
|
|
224
|
+
entries: RegistryRevocation[];
|
|
225
|
+
generatedAt: string | null;
|
|
226
|
+
}>;
|
|
227
|
+
}
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client for the OpenBook registry protocol (`openbook-registry/1`) — the
|
|
3
|
+
* read side a store-browsing app needs: the registry identity document, the
|
|
4
|
+
* paginated install index, per-plugin metadata, verified downloads, and the
|
|
5
|
+
* signed revocation feed.
|
|
6
|
+
*
|
|
7
|
+
* Trust model (PROTOCOL.md §6): a registry is identified by its pinned
|
|
8
|
+
* `(baseUrl, keys)`, never by anything a fetch returned. The caller supplies
|
|
9
|
+
* the pin; this client verifies everything it downloads against it —
|
|
10
|
+
* digest recomputed from the bytes, publisher signature against the index's
|
|
11
|
+
* `pinnedKeys`, notary countersignature and revocation entries against the
|
|
12
|
+
* pinned notary key. Any failed step throws: there is no partial trust.
|
|
13
|
+
*/
|
|
14
|
+
import { canonicalDigest, canonicalJson, verifyEd25519Message, } from './plugins';
|
|
15
|
+
// ── Semver (the protocol's version ordering) ─────────────────────────────────
|
|
16
|
+
// Strict-enough semver: MAJOR.MINOR.PATCH with optional -prerelease/+build.
|
|
17
|
+
const SEMVER_RE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-[0-9A-Za-z-.]+)?(\+[0-9A-Za-z-.]+)?$/;
|
|
18
|
+
export function isSemver(v) {
|
|
19
|
+
return SEMVER_RE.test(v);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Semver precedence compare (spec §11): numeric MAJOR.MINOR.PATCH, then
|
|
23
|
+
* prerelease (absent > present; numeric identifiers numerically, and lower
|
|
24
|
+
* than alphanumeric ones). Build metadata is ignored. Returns <0, 0, >0.
|
|
25
|
+
* Inputs must already satisfy {@link isSemver}. Byte-for-byte port of the
|
|
26
|
+
* store's `compareSemver` (open-book-pub `packages/store/lib/canonical.ts`).
|
|
27
|
+
*/
|
|
28
|
+
export function compareSemver(a, b) {
|
|
29
|
+
const pa = SEMVER_RE.exec(a);
|
|
30
|
+
const pb = SEMVER_RE.exec(b);
|
|
31
|
+
if (!pa || !pb)
|
|
32
|
+
throw new TypeError(`compareSemver: not semver: ${!pa ? a : b}`);
|
|
33
|
+
for (let i = 1; i <= 3; i++) {
|
|
34
|
+
const d = Number(pa[i]) - Number(pb[i]);
|
|
35
|
+
if (d !== 0)
|
|
36
|
+
return d;
|
|
37
|
+
}
|
|
38
|
+
const prea = pa[4]?.slice(1);
|
|
39
|
+
const preb = pb[4]?.slice(1);
|
|
40
|
+
if (prea === undefined && preb === undefined)
|
|
41
|
+
return 0;
|
|
42
|
+
if (prea === undefined)
|
|
43
|
+
return 1; // a release outranks any prerelease
|
|
44
|
+
if (preb === undefined)
|
|
45
|
+
return -1;
|
|
46
|
+
const as = prea.split('.');
|
|
47
|
+
const bs = preb.split('.');
|
|
48
|
+
for (let i = 0; i < Math.max(as.length, bs.length); i++) {
|
|
49
|
+
const x = as[i];
|
|
50
|
+
const y = bs[i];
|
|
51
|
+
if (x === undefined)
|
|
52
|
+
return -1; // fewer identifiers = lower precedence
|
|
53
|
+
if (y === undefined)
|
|
54
|
+
return 1;
|
|
55
|
+
const xn = /^\d+$/.test(x);
|
|
56
|
+
const yn = /^\d+$/.test(y);
|
|
57
|
+
if (xn && yn) {
|
|
58
|
+
const d = Number(x) - Number(y);
|
|
59
|
+
if (d !== 0)
|
|
60
|
+
return d;
|
|
61
|
+
}
|
|
62
|
+
else if (xn !== yn) {
|
|
63
|
+
return xn ? -1 : 1; // numeric identifiers sort below alphanumeric
|
|
64
|
+
}
|
|
65
|
+
else if (x !== y) {
|
|
66
|
+
return x < y ? -1 : 1;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
/** A typed failure from the registry client; `code` is stable for branching. */
|
|
72
|
+
export class RegistryError extends Error {
|
|
73
|
+
constructor(code, message) {
|
|
74
|
+
super(message);
|
|
75
|
+
this.code = code;
|
|
76
|
+
this.name = 'RegistryError';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const DEFAULT_ENDPOINTS = {
|
|
80
|
+
registry: '/api/v1/registry',
|
|
81
|
+
index: '/api/v1/index',
|
|
82
|
+
plugin: '/api/v1/plugins/{id}',
|
|
83
|
+
download: '/api/v1/plugins/{id}/versions/{version}/download',
|
|
84
|
+
revocations: '/api/v1/revocations',
|
|
85
|
+
};
|
|
86
|
+
const PROTOCOL_MAJOR = 1;
|
|
87
|
+
/** Must exceed the endpoint's 60s max-age + 300s SWR cache floor (§6.6). */
|
|
88
|
+
export const REVOCATION_STALENESS_MS = 15 * 60 * 1000;
|
|
89
|
+
/** HTTPS only, except an explicit developer-local http://localhost (§3). */
|
|
90
|
+
export function registryBaseUrlProblem(baseUrl) {
|
|
91
|
+
let url;
|
|
92
|
+
try {
|
|
93
|
+
url = new URL(baseUrl);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return 'not a valid URL';
|
|
97
|
+
}
|
|
98
|
+
if (url.protocol === 'https:')
|
|
99
|
+
return null;
|
|
100
|
+
if (url.protocol === 'http:' && (url.hostname === 'localhost' || url.hostname === '127.0.0.1'))
|
|
101
|
+
return null;
|
|
102
|
+
return 'a registry must be served over https:// (http is allowed only for localhost)';
|
|
103
|
+
}
|
|
104
|
+
const sha256Hex = async (bytes) => {
|
|
105
|
+
const hash = await crypto.subtle.digest('SHA-256', bytes);
|
|
106
|
+
return [...new Uint8Array(hash)].map((b) => b.toString(16).padStart(2, '0')).join('');
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Human-comparable fingerprint of a raw Ed25519 key (§6.2): SHA-256 of the
|
|
110
|
+
* raw 32 bytes, truncated to 128 bits, as eight dash-separated hex quads.
|
|
111
|
+
*/
|
|
112
|
+
export async function registryKeyFingerprint(publicKeyBase64) {
|
|
113
|
+
const raw = Uint8Array.from(atob(publicKeyBase64), (c) => c.charCodeAt(0));
|
|
114
|
+
const hex = (await sha256Hex(raw)).slice(0, 32);
|
|
115
|
+
return hex.replace(/(.{4})(?=.)/g, '$1-');
|
|
116
|
+
}
|
|
117
|
+
/** Does a revocation entry cover this `(pluginId, version)`? `version: null` covers all. */
|
|
118
|
+
export function revocationMatches(entry, pluginId, version) {
|
|
119
|
+
return entry.pluginId === pluginId && (entry.version === null || entry.version === version);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Fetch + validate a registry's identity document WITHOUT a pin — the
|
|
123
|
+
* add-a-registry flow (§6.2): the caller must display the fingerprints for
|
|
124
|
+
* out-of-band confirmation before pinning anything from this response.
|
|
125
|
+
*/
|
|
126
|
+
export async function fetchRegistryDocument(baseUrl, fetchImpl = fetch) {
|
|
127
|
+
const problem = registryBaseUrlProblem(baseUrl);
|
|
128
|
+
if (problem)
|
|
129
|
+
throw new RegistryError('insecure_base_url', problem);
|
|
130
|
+
const res = await fetchImpl(`${baseUrl.replace(/\/$/, '')}${DEFAULT_ENDPOINTS.registry}`);
|
|
131
|
+
if (!res.ok)
|
|
132
|
+
throw new RegistryError('http_error', `registry document fetch failed (${res.status})`);
|
|
133
|
+
const doc = (await res.json());
|
|
134
|
+
const match = /^openbook-registry\/(\d+)$/.exec(doc?.protocol ?? '');
|
|
135
|
+
if (!match || Number(match[1]) !== PROTOCOL_MAJOR) {
|
|
136
|
+
throw new RegistryError('unsupported_protocol', `unsupported registry protocol ${JSON.stringify(doc?.protocol)} — this app speaks openbook-registry/${PROTOCOL_MAJOR}`);
|
|
137
|
+
}
|
|
138
|
+
if (!Array.isArray(doc.algorithms) || !doc.algorithms.includes('ed25519')) {
|
|
139
|
+
throw new RegistryError('unsupported_protocol', 'the registry offers no signature algorithm this app supports (ed25519)');
|
|
140
|
+
}
|
|
141
|
+
return doc;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* The read client for one pinned registry. All verification happens against
|
|
145
|
+
* the PIN handed to the constructor — never against keys learned from the
|
|
146
|
+
* network afterwards.
|
|
147
|
+
*/
|
|
148
|
+
export class RegistryClient {
|
|
149
|
+
constructor(pin, opts = {}) {
|
|
150
|
+
this.pin = pin;
|
|
151
|
+
this.doc = null;
|
|
152
|
+
this.fetchImpl = opts.fetch ?? ((url, init) => fetch(url, init));
|
|
153
|
+
const problem = registryBaseUrlProblem(pin.baseUrl);
|
|
154
|
+
if (problem)
|
|
155
|
+
throw new RegistryError('insecure_base_url', problem);
|
|
156
|
+
}
|
|
157
|
+
get base() {
|
|
158
|
+
return this.pin.baseUrl.replace(/\/$/, '');
|
|
159
|
+
}
|
|
160
|
+
async endpoint(name, subst = {}) {
|
|
161
|
+
// Refusal gates in document() are security decisions, not endpoint
|
|
162
|
+
// discovery hints. Propagate them instead of silently falling back.
|
|
163
|
+
const doc = await this.document();
|
|
164
|
+
let template = doc.endpoints?.[name] ?? DEFAULT_ENDPOINTS[name];
|
|
165
|
+
for (const [k, v] of Object.entries(subst))
|
|
166
|
+
template = template.replace(`{${k}}`, encodeURIComponent(v));
|
|
167
|
+
return `${this.base}${template}`;
|
|
168
|
+
}
|
|
169
|
+
async json(url) {
|
|
170
|
+
const res = await this.fetchImpl(url);
|
|
171
|
+
if (res.status === 404)
|
|
172
|
+
throw new RegistryError('not_found', `not found: ${url}`);
|
|
173
|
+
if (res.status === 410)
|
|
174
|
+
throw new RegistryError('revoked', 'this version has been revoked by the registry');
|
|
175
|
+
if (!res.ok)
|
|
176
|
+
throw new RegistryError('http_error', `registry request failed (${res.status}): ${url}`);
|
|
177
|
+
return { body: (await res.json()), res };
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* The registry's identity document (fetched once, cached). Checked against
|
|
181
|
+
* the pin: a protocol this client does not speak is refused, and a document
|
|
182
|
+
* whose keys disagree with pinned keys is refused as a possible attack
|
|
183
|
+
* (§6.2 — a pin change is a NEW trust decision, never a silent update).
|
|
184
|
+
*/
|
|
185
|
+
async document() {
|
|
186
|
+
if (this.doc)
|
|
187
|
+
return this.doc;
|
|
188
|
+
const doc = await fetchRegistryDocument(this.base, this.fetchImpl);
|
|
189
|
+
if (this.pin.notaryPublicKey && doc.notaryPublicKey !== this.pin.notaryPublicKey) {
|
|
190
|
+
throw new RegistryError('unsupported_protocol', 'the registry\'s notary key no longer matches the pinned key — refusing (re-confirm the registry out of band)');
|
|
191
|
+
}
|
|
192
|
+
if (this.pin.registryPublicKey && doc.registryPublicKey !== this.pin.registryPublicKey) {
|
|
193
|
+
throw new RegistryError('unsupported_protocol', 'the registry\'s first-party key no longer matches the pinned key — refusing (re-confirm the registry out of band)');
|
|
194
|
+
}
|
|
195
|
+
this.doc = doc;
|
|
196
|
+
return doc;
|
|
197
|
+
}
|
|
198
|
+
/** One page of the install index (§5.2). */
|
|
199
|
+
async indexPage(opts = {}) {
|
|
200
|
+
const url = new URL(await this.endpoint('index'));
|
|
201
|
+
if (opts.limit !== undefined)
|
|
202
|
+
url.searchParams.set('limit', String(opts.limit));
|
|
203
|
+
if (opts.cursor)
|
|
204
|
+
url.searchParams.set('cursor', opts.cursor);
|
|
205
|
+
if (opts.q)
|
|
206
|
+
url.searchParams.set('q', opts.q);
|
|
207
|
+
if (opts.category)
|
|
208
|
+
url.searchParams.set('category', opts.category);
|
|
209
|
+
const { body } = await this.json(url.toString());
|
|
210
|
+
if (!Array.isArray(body?.plugins))
|
|
211
|
+
throw new RegistryError('bad_response', 'index response has no plugins array');
|
|
212
|
+
return body;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Walk the whole (filtered) index, one page at a time — cursors are walked
|
|
216
|
+
* sequentially per §9, capped defensively against a cursor loop.
|
|
217
|
+
*/
|
|
218
|
+
async indexAll(opts = {}) {
|
|
219
|
+
const all = [];
|
|
220
|
+
let cursor;
|
|
221
|
+
for (let page = 0; page < 100; page++) {
|
|
222
|
+
const { plugins, nextCursor } = await this.indexPage({ ...opts, cursor });
|
|
223
|
+
all.push(...plugins);
|
|
224
|
+
if (!nextCursor)
|
|
225
|
+
return all;
|
|
226
|
+
cursor = nextCursor;
|
|
227
|
+
}
|
|
228
|
+
throw new RegistryError('bad_response', 'index walk exceeded 100 pages — refusing a possible cursor loop');
|
|
229
|
+
}
|
|
230
|
+
/** Find one plugin's index row by exact id (via the substring search filter). */
|
|
231
|
+
async findIndexEntry(pluginId) {
|
|
232
|
+
const matches = await this.indexAll({ q: pluginId });
|
|
233
|
+
return matches.find((p) => p.id === pluginId) ?? null;
|
|
234
|
+
}
|
|
235
|
+
/** A plugin's version history (§5.3). Null when the registry doesn't know the id. */
|
|
236
|
+
async pluginMeta(pluginId) {
|
|
237
|
+
try {
|
|
238
|
+
const { body } = await this.json(await this.endpoint('plugin', { id: pluginId }));
|
|
239
|
+
return body;
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
if (err instanceof RegistryError && err.code === 'not_found')
|
|
243
|
+
return null;
|
|
244
|
+
throw err;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Download one version and verify it offline (§6.3). Every step must pass:
|
|
249
|
+
*
|
|
250
|
+
* 1. sha256(served bytes) equals the strong `ETag` (transport integrity).
|
|
251
|
+
* 2. The canonical digest RECOMPUTED from manifest+files equals `expect.digest`
|
|
252
|
+
* (the value the index/metadata promised) and the `X-Canonical-Digest` hint.
|
|
253
|
+
* 3. The publisher signature verifies over that digest with ANY member of
|
|
254
|
+
* `expect.pinnedKeys` — never with the key that travelled inside the package.
|
|
255
|
+
* 4. A present notary countersignature verifies with the PINNED notary key —
|
|
256
|
+
* present-but-invalid is a hard failure, not a downgrade to "unnotarised".
|
|
257
|
+
*
|
|
258
|
+
* Revocations are the caller's step (§6.6): check {@link revocations} before
|
|
259
|
+
* installing what this returns.
|
|
260
|
+
*/
|
|
261
|
+
async download(pluginId, version, expect) {
|
|
262
|
+
const res = await this.fetchImpl(await this.endpoint('download', { id: pluginId, version }));
|
|
263
|
+
if (res.status === 410)
|
|
264
|
+
throw new RegistryError('revoked', `${pluginId}@${version} has been revoked by the registry`);
|
|
265
|
+
if (res.status === 404)
|
|
266
|
+
throw new RegistryError('not_found', `${pluginId}@${version} is not downloadable from this registry`);
|
|
267
|
+
if (!res.ok)
|
|
268
|
+
throw new RegistryError('http_error', `download failed (${res.status})`);
|
|
269
|
+
const served = new Uint8Array(await res.arrayBuffer());
|
|
270
|
+
const etag = res.headers.get('etag');
|
|
271
|
+
if (etag) {
|
|
272
|
+
const tag = etag.replace(/^W\//, '').replace(/^"|"$/g, '');
|
|
273
|
+
if ((await sha256Hex(served)) !== tag) {
|
|
274
|
+
throw new RegistryError('etag_mismatch', 'the downloaded bytes do not hash to the served ETag — corrupted or tampered in transit');
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
let doc;
|
|
278
|
+
try {
|
|
279
|
+
doc = JSON.parse(new TextDecoder().decode(served));
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
throw new RegistryError('bad_response', 'the download is not a JSON plugin package');
|
|
283
|
+
}
|
|
284
|
+
if (!doc || typeof doc !== 'object' || !doc.manifest || !doc.files) {
|
|
285
|
+
throw new RegistryError('bad_response', 'the download is missing manifest or files');
|
|
286
|
+
}
|
|
287
|
+
const digest = await canonicalDigest(doc.manifest, doc.files);
|
|
288
|
+
if (digest !== expect.digest) {
|
|
289
|
+
throw new RegistryError('digest_mismatch', 'the recomputed canonical digest does not match what the registry index promised');
|
|
290
|
+
}
|
|
291
|
+
const digestHeader = res.headers.get('x-canonical-digest');
|
|
292
|
+
if (digestHeader && digestHeader !== digest) {
|
|
293
|
+
throw new RegistryError('digest_mismatch', 'the X-Canonical-Digest header disagrees with the recomputed digest');
|
|
294
|
+
}
|
|
295
|
+
const sig = doc.signature;
|
|
296
|
+
let publisherVerified = false;
|
|
297
|
+
if (sig?.algorithm === 'ed25519') {
|
|
298
|
+
for (const pinnedKey of expect.pinnedKeys) {
|
|
299
|
+
if (await verifyEd25519Message(pinnedKey, digest, sig.signature)) {
|
|
300
|
+
publisherVerified = true;
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (!sig || !publisherVerified) {
|
|
306
|
+
throw new RegistryError('signature_invalid', 'the publisher signature does not verify against the key this plugin id is pinned to');
|
|
307
|
+
}
|
|
308
|
+
const notarization = doc.notarization ?? null;
|
|
309
|
+
let notarised = false;
|
|
310
|
+
if (this.pin.notaryPublicKey && !notarization) {
|
|
311
|
+
throw new RegistryError('notarization_invalid', 'the approved download is missing the notarization required by this pinned store');
|
|
312
|
+
}
|
|
313
|
+
if (notarization) {
|
|
314
|
+
// Verify with the PINNED notary key only. Without a pinned key the
|
|
315
|
+
// member is unverifiable and earns nothing (notarised stays false); a
|
|
316
|
+
// pinned key + failing signature is a hard failure.
|
|
317
|
+
if (this.pin.notaryPublicKey) {
|
|
318
|
+
if (!(await verifyEd25519Message(this.pin.notaryPublicKey, digest, notarization.signature))) {
|
|
319
|
+
throw new RegistryError('notarization_invalid', 'the notary countersignature does not verify against the pinned notary key');
|
|
320
|
+
}
|
|
321
|
+
notarised = true;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
const firstParty = !!this.pin.registryPublicKey && (await verifyEd25519Message(this.pin.registryPublicKey, digest, sig.signature));
|
|
325
|
+
return {
|
|
326
|
+
pkg: { manifest: doc.manifest, files: doc.files, signature: sig },
|
|
327
|
+
digest,
|
|
328
|
+
trust: { firstParty, notarised },
|
|
329
|
+
notarization,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* The revocation feed (§5.5, §6.6), from `since` (exclusive). Entries are
|
|
334
|
+
* verified against the pinned notary key; on a registry that advertises a
|
|
335
|
+
* notary key, unsigned or badly-signed entries are IGNORED (an injection is
|
|
336
|
+
* not a kill switch). Only a registry with no notary key at all has its
|
|
337
|
+
* unsigned entries honoured — failing open on the kill switch is the
|
|
338
|
+
* dangerous direction.
|
|
339
|
+
*/
|
|
340
|
+
async revocations(since) {
|
|
341
|
+
const url = new URL(await this.endpoint('revocations'));
|
|
342
|
+
if (since !== undefined && since > 0)
|
|
343
|
+
url.searchParams.set('since', String(since));
|
|
344
|
+
const { body } = await this.json(url.toString());
|
|
345
|
+
if (!Array.isArray(body?.revocations) || typeof body.maxSeq !== 'number') {
|
|
346
|
+
throw new RegistryError('bad_response', 'malformed revocations response');
|
|
347
|
+
}
|
|
348
|
+
const notaryKey = this.pin.notaryPublicKey ?? null;
|
|
349
|
+
let verifiedMaxSeq = body.maxSeq;
|
|
350
|
+
let generatedAt = null;
|
|
351
|
+
if (notaryKey) {
|
|
352
|
+
const head = body.head;
|
|
353
|
+
if (!head
|
|
354
|
+
|| !Number.isSafeInteger(head.maxSeq)
|
|
355
|
+
|| head.maxSeq < 0
|
|
356
|
+
|| typeof head.generatedAt !== 'string'
|
|
357
|
+
|| typeof head.signature !== 'string') {
|
|
358
|
+
throw new RegistryError('bad_response', 'a registry with a pinned notary key returned no valid signed revocation head');
|
|
359
|
+
}
|
|
360
|
+
const payload = canonicalJson({ generatedAt: head.generatedAt, maxSeq: head.maxSeq });
|
|
361
|
+
if (!(await verifyEd25519Message(notaryKey, payload, head.signature))) {
|
|
362
|
+
throw new RegistryError('bad_response', 'the revocation feed head does not verify against the pinned notary key');
|
|
363
|
+
}
|
|
364
|
+
const generatedAtMs = Date.parse(head.generatedAt);
|
|
365
|
+
if (!Number.isFinite(generatedAtMs) || Date.now() - generatedAtMs > REVOCATION_STALENESS_MS) {
|
|
366
|
+
throw new RegistryError('bad_response', 'the signed revocation feed head is stale');
|
|
367
|
+
}
|
|
368
|
+
if (since !== undefined && head.maxSeq < since) {
|
|
369
|
+
throw new RegistryError('bad_response', `the signed revocation feed rolled back from sequence ${since} to ${head.maxSeq}`);
|
|
370
|
+
}
|
|
371
|
+
verifiedMaxSeq = head.maxSeq;
|
|
372
|
+
generatedAt = head.generatedAt;
|
|
373
|
+
}
|
|
374
|
+
const entries = [];
|
|
375
|
+
for (const entry of body.revocations) {
|
|
376
|
+
if (!entry || typeof entry.pluginId !== 'string')
|
|
377
|
+
continue;
|
|
378
|
+
if (notaryKey) {
|
|
379
|
+
if (!entry.signature)
|
|
380
|
+
continue; // unsigned on a signing registry = injection, not revocation
|
|
381
|
+
const payload = canonicalJson({
|
|
382
|
+
id: entry.id,
|
|
383
|
+
pluginId: entry.pluginId,
|
|
384
|
+
reason: entry.reason,
|
|
385
|
+
revokedAt: entry.createdAt,
|
|
386
|
+
seq: entry.seq,
|
|
387
|
+
version: entry.version ?? null,
|
|
388
|
+
});
|
|
389
|
+
if (!(await verifyEd25519Message(notaryKey, payload, entry.signature)))
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
entries.push(entry);
|
|
393
|
+
}
|
|
394
|
+
return { maxSeq: verifiedMaxSeq, entries, generatedAt };
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
//# sourceMappingURL=registryClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registryClient.js","sourceRoot":"","sources":["../src/registryClient.ts"],"names":[],"mappings":"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"}
|
package/dist/routes.d.ts
CHANGED
|
@@ -257,6 +257,15 @@ export declare const API: {
|
|
|
257
257
|
* page/row mutation (server-managed) — only these ledger routes write them.
|
|
258
258
|
*/
|
|
259
259
|
readonly ledger: "/api/ledger";
|
|
260
|
+
/**
|
|
261
|
+
* LX-4: restore an export's embedded ledger-records section into an EMPTY
|
|
262
|
+
* ledger by replaying it through the server's ledger writer. `POST` with a
|
|
263
|
+
* {@link LedgerExportSection} body; instance-administration gated (the same
|
|
264
|
+
* gate as {@link API.importLibrary} — it writes a whole book). A target that
|
|
265
|
+
* already keeps any ledger data refuses with a typed 409 (`invalid-state`) —
|
|
266
|
+
* merging is explicitly out of scope.
|
|
267
|
+
*/
|
|
268
|
+
readonly ledgerRestoreSection: "/api/ledger/restore-section";
|
|
260
269
|
/** Accounts: `GET` (list) / `POST` (create — hierarchical colon-delimited name). */
|
|
261
270
|
readonly ledgerAccounts: "/api/ledger/accounts";
|
|
262
271
|
/** One account: `GET` / `PATCH` (rename; close — rejected at nonzero balance). */
|