@book.dev/sdk 1.60.0 → 1.65.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/account.d.ts +11 -0
- package/dist/account.js +27 -0
- package/dist/account.js.map +1 -1
- package/dist/ai.d.ts +10 -0
- package/dist/ai.js +12 -0
- package/dist/ai.js.map +1 -1
- package/dist/authorize.d.ts +104 -0
- package/dist/authorize.js +151 -0
- package/dist/authorize.js.map +1 -0
- package/dist/authors.d.ts +26 -0
- package/dist/authors.js +81 -0
- package/dist/authors.js.map +1 -0
- package/dist/backup.d.ts +46 -0
- package/dist/backup.js +15 -0
- package/dist/backup.js.map +1 -1
- package/dist/client.d.ts +124 -3
- package/dist/client.js +113 -10
- package/dist/client.js.map +1 -1
- package/dist/connection.d.ts +23 -0
- package/dist/connection.js +56 -0
- package/dist/connection.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/rosterAssertion.d.ts +49 -0
- package/dist/forwarding/rosterAssertion.js +89 -0
- package/dist/forwarding/rosterAssertion.js.map +1 -0
- package/dist/forwarding/tunnelClient.js +11 -1
- package/dist/forwarding/tunnelClient.js.map +1 -1
- package/dist/forwarding/tunnelProtocol.d.ts +11 -0
- package/dist/forwarding/tunnelProtocol.js +11 -0
- package/dist/forwarding/tunnelProtocol.js.map +1 -1
- package/dist/identity.d.ts +238 -0
- package/dist/identity.js +273 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +13 -9
- package/dist/index.js +9 -5
- package/dist/index.js.map +1 -1
- package/dist/provenance.d.ts +168 -0
- package/dist/provenance.js +31 -0
- package/dist/provenance.js.map +1 -0
- package/dist/routes.d.ts +53 -0
- package/dist/routes.js +50 -0
- package/dist/routes.js.map +1 -1
- package/dist/suggestions.d.ts +17 -2
- package/dist/types.d.ts +100 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -1
- package/package.json +6 -1
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Per-instance roster assertion (OB-199 — "bind instance ↔ workspace").
|
|
2
|
+
//
|
|
3
|
+
// When a managed instance pulls its bound workspace's roster from the account
|
|
4
|
+
// (`GET /api/workspaces/:id/roster`), it must prove it is the SITE that owns the
|
|
5
|
+
// binding — not an end user. It does so by signing a short-lived assertion with
|
|
6
|
+
// the site's device private key (the same key that double-gates the forwarding
|
|
7
|
+
// tunnel attach). The account verifies the signature against the Site row's
|
|
8
|
+
// registered public key and that the site is bound to the workspace.
|
|
9
|
+
//
|
|
10
|
+
// The signing half runs ONLY where the private key lives — the desktop's OS
|
|
11
|
+
// keychain — so the raw key never enters the (separate) data-server process. The
|
|
12
|
+
// verify half mirrors the relay's `verifyWithSiteKey`; it is provided here so the
|
|
13
|
+
// account can adopt one canonical implementation and so the contract is testable
|
|
14
|
+
// end-to-end (sign → verify) without the account repo.
|
|
15
|
+
//
|
|
16
|
+
// Runtime-agnostic (Web Crypto via {@link signWithSiteKey}/{@link verifyWithSiteKey}).
|
|
17
|
+
import { b64uDecodeString, b64uEncodeString } from './encoding';
|
|
18
|
+
import { signWithSiteKey, verifyWithSiteKey } from './siteKey';
|
|
19
|
+
/** The assertion payload version tag — also the signed-message prefix. */
|
|
20
|
+
export const ROSTER_ASSERTION_VERSION = 'openbook.roster.v1';
|
|
21
|
+
/** Default freshness window for a roster assertion (±5 min — the account's window). */
|
|
22
|
+
export const ROSTER_ASSERTION_SKEW_MS = 5 * 60 * 1000;
|
|
23
|
+
/**
|
|
24
|
+
* The exact bytes signed/verified: `openbook.roster.v1.<base64url(payloadJson)>`.
|
|
25
|
+
* Order and separators are fixed so the site and the account never disagree. The
|
|
26
|
+
* base64url payload carries no `.`, so it never collides with the version prefix's
|
|
27
|
+
* dots when the account reconstructs the message from the assertion's first half.
|
|
28
|
+
*/
|
|
29
|
+
function rosterAssertionMessage(payloadB64) {
|
|
30
|
+
return `${ROSTER_ASSERTION_VERSION}.${payloadB64}`;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Mint a fresh signed roster assertion bearer for `Authorization: Bearer <…>`.
|
|
34
|
+
* Returns `base64url(payloadJson) + '.' + base64url(ed25519Sig)`. Stamps `ts` at
|
|
35
|
+
* call time, so mint one PER fetch (the account's freshness window is ±5 min).
|
|
36
|
+
*/
|
|
37
|
+
export async function signRosterAssertion(input) {
|
|
38
|
+
const ts = input.now ? input.now() : Date.now();
|
|
39
|
+
const payload = {
|
|
40
|
+
v: ROSTER_ASSERTION_VERSION,
|
|
41
|
+
pub: input.publicKey,
|
|
42
|
+
workspaceId: input.workspaceId,
|
|
43
|
+
ts,
|
|
44
|
+
};
|
|
45
|
+
const payloadB64 = b64uEncodeString(JSON.stringify(payload));
|
|
46
|
+
const signature = await signWithSiteKey(input.privateKey, rosterAssertionMessage(payloadB64));
|
|
47
|
+
return `${payloadB64}.${signature}`;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Verify a roster assertion (the account side, mirrored here for one canonical
|
|
51
|
+
* impl + end-to-end tests). Returns the parsed payload on success, or `null` on
|
|
52
|
+
* ANY failure — bad framing, wrong version/workspace/pub, a stale `ts`, or a bad
|
|
53
|
+
* signature. Never throws (so it can sit directly in an auth gate).
|
|
54
|
+
*/
|
|
55
|
+
export async function verifyRosterAssertion(input) {
|
|
56
|
+
try {
|
|
57
|
+
const dot = input.assertion.indexOf('.');
|
|
58
|
+
if (dot <= 0 || dot === input.assertion.length - 1)
|
|
59
|
+
return null;
|
|
60
|
+
const payloadB64 = input.assertion.slice(0, dot);
|
|
61
|
+
const signature = input.assertion.slice(dot + 1);
|
|
62
|
+
if (signature.includes('.'))
|
|
63
|
+
return null; // exactly two base64url parts
|
|
64
|
+
const parsed = JSON.parse(b64uDecodeString(payloadB64));
|
|
65
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
66
|
+
return null;
|
|
67
|
+
const payload = parsed;
|
|
68
|
+
if (payload.v !== ROSTER_ASSERTION_VERSION)
|
|
69
|
+
return null;
|
|
70
|
+
if (typeof payload.pub !== 'string' || payload.pub !== input.publicKey)
|
|
71
|
+
return null;
|
|
72
|
+
if (typeof payload.workspaceId !== 'string' || payload.workspaceId !== input.workspaceId)
|
|
73
|
+
return null;
|
|
74
|
+
if (typeof payload.ts !== 'number' || !Number.isFinite(payload.ts))
|
|
75
|
+
return null;
|
|
76
|
+
const now = input.now ?? Date.now();
|
|
77
|
+
const skewMs = input.skewMs ?? ROSTER_ASSERTION_SKEW_MS;
|
|
78
|
+
if (Math.abs(now - payload.ts) > skewMs)
|
|
79
|
+
return null;
|
|
80
|
+
const ok = await verifyWithSiteKey(input.publicKey, rosterAssertionMessage(payloadB64), signature);
|
|
81
|
+
if (!ok)
|
|
82
|
+
return null;
|
|
83
|
+
return { v: ROSTER_ASSERTION_VERSION, pub: payload.pub, workspaceId: payload.workspaceId, ts: payload.ts };
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=rosterAssertion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rosterAssertion.js","sourceRoot":"","sources":["../../src/forwarding/rosterAssertion.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { globalFetch } from '../client';
|
|
11
11
|
import { buildRelayAttachMessage } from './challenge';
|
|
12
12
|
import { signWithSiteKey } from './siteKey';
|
|
13
|
-
import { decodeBody, decodeControl, encodeBody, encodeControl } from './tunnelProtocol';
|
|
13
|
+
import { decodeBody, decodeControl, encodeBody, encodeControl, FORWARDED_HEADER } from './tunnelProtocol';
|
|
14
14
|
export class TunnelClient {
|
|
15
15
|
constructor(opts) {
|
|
16
16
|
this.opts = opts;
|
|
@@ -164,12 +164,22 @@ export class TunnelClient {
|
|
|
164
164
|
}
|
|
165
165
|
const url = `${this.opts.localOrigin.replace(/\/$/, '')}${frame.path}`;
|
|
166
166
|
const headers = new Headers();
|
|
167
|
+
const forwardedLk = FORWARDED_HEADER.toLowerCase();
|
|
167
168
|
for (const [k, v] of frame.headers) {
|
|
168
169
|
const lk = k.toLowerCase();
|
|
169
170
|
if (lk === 'host' || lk === 'connection' || lk === 'content-length')
|
|
170
171
|
continue;
|
|
172
|
+
// Drop any inbound copy of the forwarded marker — it is OURS to assert, never
|
|
173
|
+
// client-supplied. The origin's exposure backstop trusts it precisely because
|
|
174
|
+
// only this client sets it (OB-209).
|
|
175
|
+
if (lk === forwardedLk)
|
|
176
|
+
continue;
|
|
171
177
|
headers.append(k, v);
|
|
172
178
|
}
|
|
179
|
+
// Mark every forwarded request as exposed. Set unconditionally (after the strip
|
|
180
|
+
// above) so the origin can fail closed on the tunnelled path while the instance
|
|
181
|
+
// is still unclaimed, even if the UI claim-on-publish guard were bypassed.
|
|
182
|
+
headers.set(FORWARDED_HEADER, '1');
|
|
173
183
|
const init = { method: frame.method, headers, signal: controller.signal };
|
|
174
184
|
if (hasBody) {
|
|
175
185
|
init.body = body;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tunnelClient.js","sourceRoot":"","sources":["../../src/forwarding/tunnelClient.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,6EAA6E;AAC7E,iFAAiF;AACjF,+EAA+E;AAC/E,kFAAkF;AAClF,EAAE;AACF,kFAAkF;AAClF,8EAA8E;AAC9E,2CAA2C;AAE3C,OAAO,EAAC,WAAW,EAAiB,MAAM,WAAW,CAAC;AACtD,OAAO,EAAC,uBAAuB,EAAC,MAAM,aAAa,CAAC;AACpD,OAAO,EAAC,eAAe,EAAC,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAC,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,aAAa,EAAoB,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"tunnelClient.js","sourceRoot":"","sources":["../../src/forwarding/tunnelClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marks a request as having arrived through the tunnel — i.e. an EXPOSED inbound
|
|
3
|
+
* request, not a loopback/IPC one (OB-209). Forwarding is an outbound tunnel: the
|
|
4
|
+
* instance stays on loopback, so a forwarded request bypasses the boot exposure
|
|
5
|
+
* backstop (`assertExposureSafe`, which only guards a listener bind). The tunnel
|
|
6
|
+
* client SETS this on every request it forwards (overriding any inbound value — it
|
|
7
|
+
* is never client-supplied), so the origin can recognise the exposed path and fail
|
|
8
|
+
* closed while the instance is still unclaimed (where `authorize()` rule-0 would
|
|
9
|
+
* otherwise serve anonymous world-write). Loopback requests never carry it.
|
|
10
|
+
*/
|
|
11
|
+
export declare const FORWARDED_HEADER = "X-OpenBook-Forwarded";
|
|
1
12
|
export type ControlFrame = {
|
|
2
13
|
t: 'challenge';
|
|
3
14
|
nonce: string;
|
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
// request id. Control frames are JSON (WS text); body chunks are binary (WS
|
|
4
4
|
// binary) prefixed with the id, so large payloads and SSE stream without base64
|
|
5
5
|
// overhead. Shared by the relay (open.book.pub) and the tunnel client below.
|
|
6
|
+
/**
|
|
7
|
+
* Marks a request as having arrived through the tunnel — i.e. an EXPOSED inbound
|
|
8
|
+
* request, not a loopback/IPC one (OB-209). Forwarding is an outbound tunnel: the
|
|
9
|
+
* instance stays on loopback, so a forwarded request bypasses the boot exposure
|
|
10
|
+
* backstop (`assertExposureSafe`, which only guards a listener bind). The tunnel
|
|
11
|
+
* client SETS this on every request it forwards (overriding any inbound value — it
|
|
12
|
+
* is never client-supplied), so the origin can recognise the exposed path and fail
|
|
13
|
+
* closed while the instance is still unclaimed (where `authorize()` rule-0 would
|
|
14
|
+
* otherwise serve anonymous world-write). Loopback requests never carry it.
|
|
15
|
+
*/
|
|
16
|
+
export const FORWARDED_HEADER = 'X-OpenBook-Forwarded';
|
|
6
17
|
export function encodeControl(frame) {
|
|
7
18
|
return JSON.stringify(frame);
|
|
8
19
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tunnelProtocol.js","sourceRoot":"","sources":["../../src/forwarding/tunnelProtocol.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,6EAA6E;AAC7E,4EAA4E;AAC5E,gFAAgF;AAChF,6EAA6E;
|
|
1
|
+
{"version":3,"file":"tunnelProtocol.js","sourceRoot":"","sources":["../../src/forwarding/tunnelProtocol.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,6EAA6E;AAC7E,4EAA4E;AAC5E,gFAAgF;AAChF,6EAA6E;AAE7E;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,sBAAsB,CAAC;AAiBvD,MAAM,UAAU,aAAa,CAAC,KAAmB;IAC/C,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAiB,CAAC;QAC3C,OAAO,CAAC,IAAI,OAAQ,CAAmB,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACpE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,UAAU,CAAC,EAAU,EAAE,KAAiB;IACtD,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC;IACjD,IAAI,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC;IACvD,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAClB,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAe;IACxC,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;IACtE,OAAO,EAAC,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAC,CAAC;AAChE,CAAC"}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verifiable user identity for multi-user OpenBook (OB-165).
|
|
3
|
+
*
|
|
4
|
+
* A user proves who they are with a compact **JWS** (JSON Web Signature) signed
|
|
5
|
+
* by a trusted **issuer** (account.book.pub, or any federated issuer) using
|
|
6
|
+
* **Ed25519 / `alg: EdDSA`** — the same primitive the forwarding site keys and
|
|
7
|
+
* plugin provenance already use, so this adds no dependency. The data server
|
|
8
|
+
* verifies the assertion against the issuer's published JWKS (which it caches,
|
|
9
|
+
* so verification works offline and on any federated instance), then attributes
|
|
10
|
+
* every change to the resulting {@link Principal}.
|
|
11
|
+
*
|
|
12
|
+
* Design: see `docs/multi-user-and-backups-2026-06.md`. This module is
|
|
13
|
+
* isomorphic (Web Crypto only) so it runs in the server, the desktop app, the
|
|
14
|
+
* web shell, and tests alike.
|
|
15
|
+
*/
|
|
16
|
+
/** How an actor's identity was established for a given request/change. */
|
|
17
|
+
export type VerifiedVia =
|
|
18
|
+
/** A fresh, signature-verified identity JWS from a trusted issuer. */
|
|
19
|
+
'jws'
|
|
20
|
+
/** Unauthenticated (or self-named) guest. */
|
|
21
|
+
| 'guest'
|
|
22
|
+
/** A well-formed JWS that no longer verifies fresh (e.g. expired while
|
|
23
|
+
* offline). The claimed identity is recorded but flagged as not vouched-for. */
|
|
24
|
+
| 'unverified'
|
|
25
|
+
/** Attribution carried in from another instance via the sync/merge path
|
|
26
|
+
* (OB-170) — vouched for by the originating instance, not re-verified here. */
|
|
27
|
+
| 'synced'
|
|
28
|
+
/** In-process caller (the embedded `LocalDataClient`) — implicitly the local
|
|
29
|
+
* owner; there is no request to carry a credential. */
|
|
30
|
+
| 'local';
|
|
31
|
+
export type PrincipalKind = 'user' | 'guest';
|
|
32
|
+
/** The actor behind a request, resolved by the server and stamped onto changes. */
|
|
33
|
+
export interface Principal {
|
|
34
|
+
kind: PrincipalKind;
|
|
35
|
+
/** Stable, globally-meaningful id. Users: `iss#sub`. Guests: `guest:<name|anon>`. */
|
|
36
|
+
subject: string;
|
|
37
|
+
/** The issuer URL that vouched for a user (empty for guests/local). */
|
|
38
|
+
issuer: string;
|
|
39
|
+
/** Human-readable display name, when known. */
|
|
40
|
+
name: string;
|
|
41
|
+
/**
|
|
42
|
+
* The single **active-persona email** (lowercased), when the issuer asserts one
|
|
43
|
+
* (OB-182 §1.1/§3). Net-new threading from the verified `email` claim — there is
|
|
44
|
+
* never an `emails[]` array (Fork 4/5): one token carries exactly one persona
|
|
45
|
+
* facet. Only ever *trusted* for persona / email-ACL matching when the principal
|
|
46
|
+
* is `verifiedVia==='jws'` AND `issuer === config.emailAuthority` (the
|
|
47
|
+
* `emailIsAuthoritative` gate, B1); on any other principal it is at most an
|
|
48
|
+
* attribution hint. Absent when the issuer asserts no email. */
|
|
49
|
+
email?: string;
|
|
50
|
+
verifiedVia: VerifiedVia;
|
|
51
|
+
/** Which signed credential authorized this (users only): issuer key id + assertion id. */
|
|
52
|
+
assertion?: {
|
|
53
|
+
kid?: string;
|
|
54
|
+
jti?: string;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** A guest principal with an optional display name. */
|
|
58
|
+
export declare function guestPrincipal(name?: string): Principal;
|
|
59
|
+
/** The implicit local owner (in-process / loopback desktop). */
|
|
60
|
+
export declare function localPrincipal(name?: string): Principal;
|
|
61
|
+
/** Stable short id for a principal — the value embedded in CRDT edit origins. */
|
|
62
|
+
export declare function principalId(p: Principal): string;
|
|
63
|
+
/** JOSE header of an identity assertion. */
|
|
64
|
+
export interface IdentityHeader {
|
|
65
|
+
alg: 'EdDSA';
|
|
66
|
+
typ?: string;
|
|
67
|
+
/** Issuer key id — selects the verifying key from the issuer's JWKS. */
|
|
68
|
+
kid?: string;
|
|
69
|
+
}
|
|
70
|
+
/** Claims carried by an identity assertion (a JWT-shaped payload). */
|
|
71
|
+
export interface IdentityClaims {
|
|
72
|
+
/** Issuer URL (the authenticating source). */
|
|
73
|
+
iss: string;
|
|
74
|
+
/** Stable user id within the issuer. */
|
|
75
|
+
sub: string;
|
|
76
|
+
/** Display name. */
|
|
77
|
+
name?: string;
|
|
78
|
+
/** Email, when the issuer chooses to assert it. */
|
|
79
|
+
email?: string;
|
|
80
|
+
/** Issued-at (epoch seconds). */
|
|
81
|
+
iat?: number;
|
|
82
|
+
/** Expiry (epoch seconds). */
|
|
83
|
+
exp?: number;
|
|
84
|
+
/** Not-before (epoch seconds). */
|
|
85
|
+
nbf?: number;
|
|
86
|
+
/** Unique assertion id — recorded with each change for traceability. */
|
|
87
|
+
jti?: string;
|
|
88
|
+
/**
|
|
89
|
+
* Audience — the data server this assertion is scoped to (OB-177). When
|
|
90
|
+
* present, a verifier MUST reject the token unless `aud` names *itself*, so a
|
|
91
|
+
* server the user connected to can't replay their identity to a different
|
|
92
|
+
* server (confused-deputy / token redirection). Absent on unscoped tokens
|
|
93
|
+
* (the single-server model, where exactly one server trusts the issuer).
|
|
94
|
+
*/
|
|
95
|
+
aud?: string;
|
|
96
|
+
}
|
|
97
|
+
/** An Ed25519 public key in JWK form (`kty:'OKP'`, raw 32-byte `x`, base64url). */
|
|
98
|
+
export interface Jwk {
|
|
99
|
+
kty: 'OKP';
|
|
100
|
+
crv: 'Ed25519';
|
|
101
|
+
/** Raw 32-byte public key, base64url. */
|
|
102
|
+
x: string;
|
|
103
|
+
kid?: string;
|
|
104
|
+
use?: 'sig';
|
|
105
|
+
alg?: 'EdDSA';
|
|
106
|
+
}
|
|
107
|
+
/** A set of public keys an issuer publishes (the cached, offline-verifiable set). */
|
|
108
|
+
export interface Jwks {
|
|
109
|
+
keys: Jwk[];
|
|
110
|
+
}
|
|
111
|
+
/** One revoked subject in an issuer's revocation set. */
|
|
112
|
+
export interface RevocationEntry {
|
|
113
|
+
/** The user id (issuer-local `sub`) whose tokens are revoked. */
|
|
114
|
+
sub: string;
|
|
115
|
+
/** Epoch seconds: a token for this `sub` is revoked when its `iat` is older than this. */
|
|
116
|
+
since: number;
|
|
117
|
+
/** Why the subject was revoked (advisory; not part of the trust decision). */
|
|
118
|
+
reason?: 'deleted' | 'signed-out' | string;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* An issuer's set of revoked subjects (OB-106). The issuer publishes it as an
|
|
122
|
+
* EdDSA-signed JWS (same shape as an identity token) which the consumer verifies
|
|
123
|
+
* with {@link verifyRevocations} against the issuer's JWKS, then hands to
|
|
124
|
+
* {@link verifyIdentity}: a token is revoked iff its `iss` matches {@link iss} and
|
|
125
|
+
* this set lists the token's `sub` with `since` newer than the token's `iat`. A
|
|
126
|
+
* short-lived snapshot — the issuer stamps `iat`/`ttl`; the consumer refreshes on
|
|
127
|
+
* its own cadence (and the short token TTL backstops a stale set).
|
|
128
|
+
*/
|
|
129
|
+
export interface RevocationSet {
|
|
130
|
+
/** Issuer URL these revocations apply to (must equal the token's `iss`). */
|
|
131
|
+
iss: string;
|
|
132
|
+
/** When the issuer produced this snapshot (epoch seconds). */
|
|
133
|
+
iat?: number;
|
|
134
|
+
/** How long the snapshot stays fresh, in seconds (advisory). */
|
|
135
|
+
ttl?: number;
|
|
136
|
+
/** The revoked subjects. */
|
|
137
|
+
revocations: RevocationEntry[];
|
|
138
|
+
}
|
|
139
|
+
/** An issuer signing keypair (dev/test issuer, or the account service). */
|
|
140
|
+
export interface IdentityKeypair {
|
|
141
|
+
/** The public key as a JWK (publish in the JWKS). */
|
|
142
|
+
publicJwk: Jwk;
|
|
143
|
+
/** PKCS#8 private key, base64url — kept secret by the issuer. */
|
|
144
|
+
privateKey: string;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Mint a fresh issuer keypair (Ed25519). Used by the dev issuer and tests; the
|
|
148
|
+
* real issuer (account.book.pub) holds the equivalent and rotates `kid`s.
|
|
149
|
+
*/
|
|
150
|
+
export declare function mintIdentityKeypair(kid?: string): Promise<IdentityKeypair>;
|
|
151
|
+
/**
|
|
152
|
+
* Sign an identity assertion (issuer side / dev / tests). Produces a compact
|
|
153
|
+
* EdDSA JWS: `base64url(header).base64url(claims).base64url(sig)`.
|
|
154
|
+
*/
|
|
155
|
+
export declare function signIdentity(privateKeyPkcs8: string, claims: IdentityClaims, kid?: string): Promise<string>;
|
|
156
|
+
/**
|
|
157
|
+
* Decode an assertion's header + claims **without** verifying the signature.
|
|
158
|
+
* For client-side display, and for the server's "claimed-but-unverified"
|
|
159
|
+
* provenance fallback. Never use this to make a trust decision.
|
|
160
|
+
*/
|
|
161
|
+
export declare function decodeIdentity(jws: string): {
|
|
162
|
+
header: IdentityHeader;
|
|
163
|
+
claims: IdentityClaims;
|
|
164
|
+
} | null;
|
|
165
|
+
/** Reason an assertion failed to verify (for logging / the unverified fallback). */
|
|
166
|
+
export type VerifyFailure = 'malformed' | 'unsupported-alg' | 'unknown-key' | 'bad-signature' | 'expired' | 'not-yet-valid' | 'untrusted-issuer' | 'wrong-audience'
|
|
167
|
+
/** The issuer revoked this subject's tokens issued before the token's `iat` (OB-106). */
|
|
168
|
+
| 'revoked';
|
|
169
|
+
export type VerifyResult = {
|
|
170
|
+
ok: true;
|
|
171
|
+
claims: IdentityClaims;
|
|
172
|
+
header: IdentityHeader;
|
|
173
|
+
} | {
|
|
174
|
+
ok: false;
|
|
175
|
+
reason: VerifyFailure;
|
|
176
|
+
claims?: IdentityClaims;
|
|
177
|
+
};
|
|
178
|
+
export interface VerifyOptions {
|
|
179
|
+
/** Allowed clock skew, in seconds (covers minor offsets + short offline). Default 60. */
|
|
180
|
+
clockToleranceSec?: number;
|
|
181
|
+
/** Current time in epoch ms (injectable for tests). Default `Date.now()`. */
|
|
182
|
+
nowMs?: number;
|
|
183
|
+
/** If set, the `iss` claim must be one of these (issuer-rooted federation). */
|
|
184
|
+
allowedIssuers?: string[];
|
|
185
|
+
/**
|
|
186
|
+
* This data server's own audience identifier (OB-177). When a token carries an
|
|
187
|
+
* `aud`, it must equal this — otherwise the token was scoped to a *different*
|
|
188
|
+
* server and is rejected (`wrong-audience`). Leave unset only for the
|
|
189
|
+
* single-server model, where the issuer never sets `aud`.
|
|
190
|
+
*/
|
|
191
|
+
audience?: string;
|
|
192
|
+
/**
|
|
193
|
+
* Require every token to be audience-bound to {@link audience}: an unscoped
|
|
194
|
+
* (no-`aud`) token is rejected. Set on a multi-server deployment so a server
|
|
195
|
+
* can't be handed an unscoped, freely-replayable assertion.
|
|
196
|
+
*/
|
|
197
|
+
requireAudience?: boolean;
|
|
198
|
+
/**
|
|
199
|
+
* The issuer's (already-signature-verified) revocation set (OB-106). When
|
|
200
|
+
* supplied, a token whose `iss` matches the set and whose `sub` is listed with
|
|
201
|
+
* a `since` newer than the token's `iat` is rejected as `revoked`. Obtain it
|
|
202
|
+
* with {@link verifyRevocations} so the set itself is trusted; omit it to skip
|
|
203
|
+
* the revocation check (fail-open — the short token TTL is the backstop).
|
|
204
|
+
*/
|
|
205
|
+
revocations?: RevocationSet;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Verify an identity assertion against an issuer's JWKS. Pure + offline: the
|
|
209
|
+
* caller supplies the (cached) key set, so no network call happens here.
|
|
210
|
+
*
|
|
211
|
+
* Returns `{ok:true, claims}` only on a fresh, signature-valid, in-window,
|
|
212
|
+
* trusted-issuer, non-revoked assertion. On failure returns a reason (and the
|
|
213
|
+
* decoded claims when they parsed, so the server can record a claimed-but-
|
|
214
|
+
* unverified identity).
|
|
215
|
+
*/
|
|
216
|
+
export declare function verifyIdentity(jws: string, jwks: Jwks, opts?: VerifyOptions): Promise<VerifyResult>;
|
|
217
|
+
/**
|
|
218
|
+
* Verify an issuer's revocation document — a compact EdDSA JWS, the same format
|
|
219
|
+
* as an identity token — against the issuer's JWKS, returning the parsed
|
|
220
|
+
* {@link RevocationSet}. Pure + offline (the caller supplies the cached JWKS).
|
|
221
|
+
* Returns `null` on a malformed document, unsupported alg, or bad signature, so a
|
|
222
|
+
* forged or unsigned document can never revoke anything.
|
|
223
|
+
*/
|
|
224
|
+
export declare function verifyRevocations(jws: string, jwks: Jwks): Promise<RevocationSet | null>;
|
|
225
|
+
/**
|
|
226
|
+
* Sign a revocation document (issuer side / dev / tests) — the counterpart to
|
|
227
|
+
* {@link verifyRevocations}. Produces the same compact EdDSA JWS the account
|
|
228
|
+
* service publishes at its revocations endpoint.
|
|
229
|
+
*/
|
|
230
|
+
export declare function signRevocations(privateKeyPkcs8: string, set: RevocationSet, kid?: string): Promise<string>;
|
|
231
|
+
/** Build a verified user principal from validated claims. */
|
|
232
|
+
export declare function principalFromClaims(claims: IdentityClaims, header?: IdentityHeader): Principal;
|
|
233
|
+
/**
|
|
234
|
+
* Build a claimed-but-unverified principal from an assertion that parsed but
|
|
235
|
+
* didn't verify fresh (e.g. expired while offline). Provenance still names the
|
|
236
|
+
* claimed subject, flagged `unverified`.
|
|
237
|
+
*/
|
|
238
|
+
export declare function unverifiedPrincipalFromClaims(claims: IdentityClaims): Principal;
|