@noy-db/on-magic-link 0.1.0-pre.7 → 0.1.0-pre.8

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/index.d.cts CHANGED
@@ -1,6 +1,214 @@
1
- import { NoydbStore, MagicLinkGrantPayload, UnlockedKeyring, Vault } from '@noy-db/hub';
1
+ import { NoydbStore, createNoydb, Noydb, Role, FactorProof, MagicLinkGrantPayload, UnlockedKeyring, Vault } from '@noy-db/hub';
2
2
  export { MAGIC_LINK_GRANTS_COLLECTION, MagicLinkGrantPayload, deriveMagicLinkContentKey } from '@noy-db/hub';
3
3
 
4
+ /**
5
+ * **Invite + peer-recovery primitives** — issue #32.
6
+ *
7
+ * Layered on top of `db.grant` (for invite, mints a NEW user) and
8
+ * `db.recoverUser` (for peer-recovery, rewraps an EXISTING user under
9
+ * a fresh temp passphrase). These flows are SIBLINGS of the existing
10
+ * delegation-grant primitives in `./index.ts` — different threat
11
+ * model, different on-disk audit shape, no server-held secret.
12
+ *
13
+ * ## Threat model
14
+ *
15
+ * The temp passphrase travels in the **URL fragment** — server-blind
16
+ * transport (fragments don't traverse TLS proxies, don't appear in
17
+ * access logs). Trade-off: any party who sees the URL can claim the
18
+ * invite once. Single-use semantics close the window: `acceptInvite`
19
+ * rotates the passphrase atomically, marking the audit doc accepted —
20
+ * a second `acceptInvite` call rejects.
21
+ *
22
+ * ## What this module does NOT do
23
+ *
24
+ * - Send emails / construct HTTPS URLs (that's the application layer)
25
+ * - Validate the invite under HTTPS (caller's responsibility)
26
+ * - Coordinate with a server-held secret (delegation grants do that;
27
+ * invite is server-blind by design)
28
+ *
29
+ * @see #32 #33 #34
30
+ *
31
+ * @module
32
+ */
33
+
34
+ /** Whether the payload mints a NEW user (invite) or rewraps an existing one (peer-recovery). */
35
+ type InviteKind = 'invite' | 'peer-recovery';
36
+ /**
37
+ * Serializable payload encoded into the URL fragment. The temp
38
+ * passphrase is the secret; the rest is metadata the recipient
39
+ * needs to open the right vault as the right user.
40
+ */
41
+ interface InvitePayload {
42
+ /** ULID identifying this invite — used to look up the audit doc. */
43
+ readonly tokenId: string;
44
+ readonly vault: string;
45
+ readonly userId: string;
46
+ readonly displayName?: string;
47
+ readonly role?: Role;
48
+ readonly kind: InviteKind;
49
+ /** Issuer's userId (for forensics; not enforced cryptographically). */
50
+ readonly issuer: string;
51
+ /** Single-use temporary passphrase — replaced on `acceptInvite`. */
52
+ readonly tempPhrase: string;
53
+ readonly expiresAt: string;
54
+ }
55
+ /** Audit doc persisted at `_meta/invite-audit-<tokenId>`. */
56
+ interface InviteAuditDoc {
57
+ readonly _noydb_invite_audit: 1;
58
+ readonly tokenId: string;
59
+ readonly kind: InviteKind;
60
+ readonly issuer: string;
61
+ readonly target: string;
62
+ readonly expiresAt: string;
63
+ readonly issuedAt: string;
64
+ readonly revokedAt?: string;
65
+ readonly acceptedAt?: string;
66
+ }
67
+ interface IssueInviteOptions {
68
+ readonly userId: string;
69
+ readonly displayName: string;
70
+ readonly role: Role;
71
+ readonly ttlMs?: number;
72
+ /** Override the generated temp phrase (rare; deterministic tests). */
73
+ readonly tempPhrase?: string;
74
+ }
75
+ interface IssuePeerRecoveryOptions {
76
+ readonly userId: string;
77
+ readonly displayName?: string;
78
+ readonly role?: Role;
79
+ readonly ttlMs?: number;
80
+ readonly tempPhrase?: string;
81
+ }
82
+ interface IssueInviteResult {
83
+ readonly payload: InvitePayload;
84
+ /**
85
+ * URL-fragment-safe base64url encoding of the JSON payload. Embed
86
+ * after a `#` in the application's invite URL. Decoded back at
87
+ * accept time via `decodeInvitePayload`.
88
+ */
89
+ readonly encoded: string;
90
+ }
91
+ interface AcceptInviteOptions {
92
+ /**
93
+ * The recipient's NoydbStore. Typically the same shared store the
94
+ * issuer used (e.g. a sync-peer pointing at a common backend) — the
95
+ * recipient must reach the same `_keyring/<userId>` and
96
+ * `_meta/invite-audit-<tokenId>` documents.
97
+ */
98
+ readonly store: NoydbStore;
99
+ /**
100
+ * The recipient's chosen new passphrase. `acceptInvite` rotates
101
+ * the temp phrase to this value atomically before returning. Must
102
+ * pass the vault's phrase strength policy (or the recipient must
103
+ * pass `validatePassphrase: false` via the underlying createNoydb
104
+ * options — currently exposed via `noydbOptions`).
105
+ */
106
+ readonly newPhrase: string;
107
+ /**
108
+ * Reference clock for TTL evaluation. Production callers leave
109
+ * this `undefined`; tests pass a fixed date.
110
+ */
111
+ readonly now?: Date;
112
+ /**
113
+ * Extra options forwarded to `createNoydb` when the recipient's
114
+ * session opens. Useful for `policy`, `validatePassphrase`, or
115
+ * `sessionPolicy` tweaks the application layer wants in scope.
116
+ */
117
+ readonly noydbOptions?: Omit<Parameters<typeof createNoydb>[0], 'store' | 'user' | 'secret'>;
118
+ }
119
+ interface AcceptInviteResult {
120
+ readonly db: Noydb;
121
+ readonly payload: InvitePayload;
122
+ }
123
+ declare class InviteExpiredError extends Error {
124
+ readonly expiresAt: string;
125
+ readonly code: "INVITE_EXPIRED";
126
+ constructor(expiresAt: string);
127
+ }
128
+ declare class InviteRevokedError extends Error {
129
+ readonly tokenId: string;
130
+ readonly revokedAt: string;
131
+ readonly code: "INVITE_REVOKED";
132
+ constructor(tokenId: string, revokedAt: string);
133
+ }
134
+ declare class InviteAlreadyAcceptedError extends Error {
135
+ readonly tokenId: string;
136
+ readonly acceptedAt: string;
137
+ readonly code: "INVITE_ALREADY_ACCEPTED";
138
+ constructor(tokenId: string, acceptedAt: string);
139
+ }
140
+ declare class InviteAuditMissingError extends Error {
141
+ readonly tokenId: string;
142
+ readonly code: "INVITE_AUDIT_MISSING";
143
+ constructor(tokenId: string);
144
+ }
145
+ /**
146
+ * Mint an invite for a NEW user. Generates a random temp phrase,
147
+ * calls `db.grant({ userId, passphrase: tempPhrase })`, writes the
148
+ * audit doc, and returns the URL-encodable payload.
149
+ *
150
+ * The recipient claims via `acceptInvite(encoded, { store, newPhrase })`;
151
+ * the rotation inside `acceptInvite` invalidates the temp phrase
152
+ * (single-use by construction).
153
+ *
154
+ * @throws Whatever `db.grant` throws (PrivilegeEscalationError,
155
+ * WeakPassphraseError if the temp phrase fails policy, …)
156
+ */
157
+ declare function issueInvite(db: Noydb, vault: string, options: IssueInviteOptions): Promise<IssueInviteResult>;
158
+ /**
159
+ * Mint a peer-recovery for an EXISTING user. Generates a random temp
160
+ * phrase, calls `db.recoverUser` (atomic; closes the partial-failure
161
+ * window of compose-from-primitives), writes the audit doc, returns
162
+ * the payload.
163
+ *
164
+ * Owner→owner is allowed (the policy gate `peer-recover-user` carries
165
+ * the freshness factor). The `factors` argument forwards to the gate.
166
+ */
167
+ declare function issuePeerRecovery(db: Noydb, vault: string, options: IssuePeerRecoveryOptions, factors?: {
168
+ factors?: ReadonlyArray<FactorProof>;
169
+ sharedDevice?: boolean;
170
+ }): Promise<IssueInviteResult>;
171
+ /**
172
+ * Mark an outstanding invite as revoked. After this, `acceptInvite`
173
+ * for the same token rejects with `InviteRevokedError` BEFORE opening
174
+ * any session — closes #32's "revoked-link silent shadow keyring"
175
+ * defense (without the audit doc check, a revoked link could fall
176
+ * through to `createNoydb`'s no-keyring auto-create path and create
177
+ * a fresh empty vault).
178
+ *
179
+ * Idempotent — revoking an already-revoked invite is a no-op.
180
+ *
181
+ * Note: this does NOT delete the granted/recovered keyring; it only
182
+ * marks the invite token as unusable. To fully cancel the invite,
183
+ * call `db.revoke(vault, { userId })` separately. The two are split
184
+ * because peer-recovery doesn't have a "cancel" — the target user
185
+ * already existed.
186
+ */
187
+ declare function revokeInvite(db: Noydb, vault: string, encodedOrPayload: string | InvitePayload): Promise<void>;
188
+ /**
189
+ * Open the recipient's session under the invite. Validates TTL +
190
+ * revoke + already-accepted, opens `createNoydb` with the temp
191
+ * phrase, immediately rotates to `newPhrase`, marks the audit doc
192
+ * accepted, returns the live `Noydb` instance.
193
+ *
194
+ * Single-use is enforced two ways:
195
+ * 1. The rotation inside this function invalidates the temp phrase.
196
+ * 2. The audit doc's `acceptedAt` field is set on success — a
197
+ * second `acceptInvite` call sees it and throws
198
+ * `InviteAlreadyAcceptedError`.
199
+ *
200
+ * @throws {@link InviteExpiredError} when TTL has passed.
201
+ * @throws {@link InviteRevokedError} when the issuer has revoked.
202
+ * @throws {@link InviteAlreadyAcceptedError} on second call.
203
+ * @throws {@link InviteAuditMissingError} when no audit doc — closes
204
+ * #32's revoked-link-shadow-keyring defense.
205
+ */
206
+ declare function acceptInvite(encoded: string, options: AcceptInviteOptions): Promise<AcceptInviteResult>;
207
+ /** Encode the payload as a URL-fragment-safe base64url string. */
208
+ declare function encodeInvitePayload(payload: InvitePayload): string;
209
+ /** Decode a base64url string back to an InvitePayload. */
210
+ declare function decodeInvitePayload(encoded: string): InvitePayload;
211
+
4
212
  /**
5
213
  * **@noy-db/on-magic-link** — one-time-link viewer unlock for noy-db.
6
214
  *
@@ -289,4 +497,4 @@ declare function readMagicLinkGrant(options: {
289
497
  readonly recordId: string;
290
498
  }): Promise<MagicLinkGrantPayload | null>;
291
499
 
292
- export { type ClaimMagicLinkDelegationOptions, type ClaimMagicLinkDelegationResult, type ClaimedMagicLinkGrant, type CreateMagicLinkOptions, type IssueMagicLinkDelegationOptions, type IssueMagicLinkDelegationResult, MAGIC_LINK_DEFAULT_TTL_MS, type MagicLinkGrantSpec, type MagicLinkToken, buildMagicLinkKeyring, claimMagicLinkDelegation, createMagicLinkToken, deriveMagicLinkKEK, inspectMagicLinkDelegation, isMagicLinkValid, issueMagicLinkDelegation, readMagicLinkGrant, revokeMagicLinkDelegation };
500
+ export { type AcceptInviteOptions, type AcceptInviteResult, type ClaimMagicLinkDelegationOptions, type ClaimMagicLinkDelegationResult, type ClaimedMagicLinkGrant, type CreateMagicLinkOptions, InviteAlreadyAcceptedError, type InviteAuditDoc, InviteAuditMissingError, InviteExpiredError, type InviteKind, type InvitePayload, InviteRevokedError, type IssueInviteOptions, type IssueInviteResult, type IssueMagicLinkDelegationOptions, type IssueMagicLinkDelegationResult, type IssuePeerRecoveryOptions, MAGIC_LINK_DEFAULT_TTL_MS, type MagicLinkGrantSpec, type MagicLinkToken, acceptInvite, buildMagicLinkKeyring, claimMagicLinkDelegation, createMagicLinkToken, decodeInvitePayload, deriveMagicLinkKEK, encodeInvitePayload, inspectMagicLinkDelegation, isMagicLinkValid, issueInvite, issueMagicLinkDelegation, issuePeerRecovery, readMagicLinkGrant, revokeInvite, revokeMagicLinkDelegation };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,214 @@
1
- import { NoydbStore, MagicLinkGrantPayload, UnlockedKeyring, Vault } from '@noy-db/hub';
1
+ import { NoydbStore, createNoydb, Noydb, Role, FactorProof, MagicLinkGrantPayload, UnlockedKeyring, Vault } from '@noy-db/hub';
2
2
  export { MAGIC_LINK_GRANTS_COLLECTION, MagicLinkGrantPayload, deriveMagicLinkContentKey } from '@noy-db/hub';
3
3
 
4
+ /**
5
+ * **Invite + peer-recovery primitives** — issue #32.
6
+ *
7
+ * Layered on top of `db.grant` (for invite, mints a NEW user) and
8
+ * `db.recoverUser` (for peer-recovery, rewraps an EXISTING user under
9
+ * a fresh temp passphrase). These flows are SIBLINGS of the existing
10
+ * delegation-grant primitives in `./index.ts` — different threat
11
+ * model, different on-disk audit shape, no server-held secret.
12
+ *
13
+ * ## Threat model
14
+ *
15
+ * The temp passphrase travels in the **URL fragment** — server-blind
16
+ * transport (fragments don't traverse TLS proxies, don't appear in
17
+ * access logs). Trade-off: any party who sees the URL can claim the
18
+ * invite once. Single-use semantics close the window: `acceptInvite`
19
+ * rotates the passphrase atomically, marking the audit doc accepted —
20
+ * a second `acceptInvite` call rejects.
21
+ *
22
+ * ## What this module does NOT do
23
+ *
24
+ * - Send emails / construct HTTPS URLs (that's the application layer)
25
+ * - Validate the invite under HTTPS (caller's responsibility)
26
+ * - Coordinate with a server-held secret (delegation grants do that;
27
+ * invite is server-blind by design)
28
+ *
29
+ * @see #32 #33 #34
30
+ *
31
+ * @module
32
+ */
33
+
34
+ /** Whether the payload mints a NEW user (invite) or rewraps an existing one (peer-recovery). */
35
+ type InviteKind = 'invite' | 'peer-recovery';
36
+ /**
37
+ * Serializable payload encoded into the URL fragment. The temp
38
+ * passphrase is the secret; the rest is metadata the recipient
39
+ * needs to open the right vault as the right user.
40
+ */
41
+ interface InvitePayload {
42
+ /** ULID identifying this invite — used to look up the audit doc. */
43
+ readonly tokenId: string;
44
+ readonly vault: string;
45
+ readonly userId: string;
46
+ readonly displayName?: string;
47
+ readonly role?: Role;
48
+ readonly kind: InviteKind;
49
+ /** Issuer's userId (for forensics; not enforced cryptographically). */
50
+ readonly issuer: string;
51
+ /** Single-use temporary passphrase — replaced on `acceptInvite`. */
52
+ readonly tempPhrase: string;
53
+ readonly expiresAt: string;
54
+ }
55
+ /** Audit doc persisted at `_meta/invite-audit-<tokenId>`. */
56
+ interface InviteAuditDoc {
57
+ readonly _noydb_invite_audit: 1;
58
+ readonly tokenId: string;
59
+ readonly kind: InviteKind;
60
+ readonly issuer: string;
61
+ readonly target: string;
62
+ readonly expiresAt: string;
63
+ readonly issuedAt: string;
64
+ readonly revokedAt?: string;
65
+ readonly acceptedAt?: string;
66
+ }
67
+ interface IssueInviteOptions {
68
+ readonly userId: string;
69
+ readonly displayName: string;
70
+ readonly role: Role;
71
+ readonly ttlMs?: number;
72
+ /** Override the generated temp phrase (rare; deterministic tests). */
73
+ readonly tempPhrase?: string;
74
+ }
75
+ interface IssuePeerRecoveryOptions {
76
+ readonly userId: string;
77
+ readonly displayName?: string;
78
+ readonly role?: Role;
79
+ readonly ttlMs?: number;
80
+ readonly tempPhrase?: string;
81
+ }
82
+ interface IssueInviteResult {
83
+ readonly payload: InvitePayload;
84
+ /**
85
+ * URL-fragment-safe base64url encoding of the JSON payload. Embed
86
+ * after a `#` in the application's invite URL. Decoded back at
87
+ * accept time via `decodeInvitePayload`.
88
+ */
89
+ readonly encoded: string;
90
+ }
91
+ interface AcceptInviteOptions {
92
+ /**
93
+ * The recipient's NoydbStore. Typically the same shared store the
94
+ * issuer used (e.g. a sync-peer pointing at a common backend) — the
95
+ * recipient must reach the same `_keyring/<userId>` and
96
+ * `_meta/invite-audit-<tokenId>` documents.
97
+ */
98
+ readonly store: NoydbStore;
99
+ /**
100
+ * The recipient's chosen new passphrase. `acceptInvite` rotates
101
+ * the temp phrase to this value atomically before returning. Must
102
+ * pass the vault's phrase strength policy (or the recipient must
103
+ * pass `validatePassphrase: false` via the underlying createNoydb
104
+ * options — currently exposed via `noydbOptions`).
105
+ */
106
+ readonly newPhrase: string;
107
+ /**
108
+ * Reference clock for TTL evaluation. Production callers leave
109
+ * this `undefined`; tests pass a fixed date.
110
+ */
111
+ readonly now?: Date;
112
+ /**
113
+ * Extra options forwarded to `createNoydb` when the recipient's
114
+ * session opens. Useful for `policy`, `validatePassphrase`, or
115
+ * `sessionPolicy` tweaks the application layer wants in scope.
116
+ */
117
+ readonly noydbOptions?: Omit<Parameters<typeof createNoydb>[0], 'store' | 'user' | 'secret'>;
118
+ }
119
+ interface AcceptInviteResult {
120
+ readonly db: Noydb;
121
+ readonly payload: InvitePayload;
122
+ }
123
+ declare class InviteExpiredError extends Error {
124
+ readonly expiresAt: string;
125
+ readonly code: "INVITE_EXPIRED";
126
+ constructor(expiresAt: string);
127
+ }
128
+ declare class InviteRevokedError extends Error {
129
+ readonly tokenId: string;
130
+ readonly revokedAt: string;
131
+ readonly code: "INVITE_REVOKED";
132
+ constructor(tokenId: string, revokedAt: string);
133
+ }
134
+ declare class InviteAlreadyAcceptedError extends Error {
135
+ readonly tokenId: string;
136
+ readonly acceptedAt: string;
137
+ readonly code: "INVITE_ALREADY_ACCEPTED";
138
+ constructor(tokenId: string, acceptedAt: string);
139
+ }
140
+ declare class InviteAuditMissingError extends Error {
141
+ readonly tokenId: string;
142
+ readonly code: "INVITE_AUDIT_MISSING";
143
+ constructor(tokenId: string);
144
+ }
145
+ /**
146
+ * Mint an invite for a NEW user. Generates a random temp phrase,
147
+ * calls `db.grant({ userId, passphrase: tempPhrase })`, writes the
148
+ * audit doc, and returns the URL-encodable payload.
149
+ *
150
+ * The recipient claims via `acceptInvite(encoded, { store, newPhrase })`;
151
+ * the rotation inside `acceptInvite` invalidates the temp phrase
152
+ * (single-use by construction).
153
+ *
154
+ * @throws Whatever `db.grant` throws (PrivilegeEscalationError,
155
+ * WeakPassphraseError if the temp phrase fails policy, …)
156
+ */
157
+ declare function issueInvite(db: Noydb, vault: string, options: IssueInviteOptions): Promise<IssueInviteResult>;
158
+ /**
159
+ * Mint a peer-recovery for an EXISTING user. Generates a random temp
160
+ * phrase, calls `db.recoverUser` (atomic; closes the partial-failure
161
+ * window of compose-from-primitives), writes the audit doc, returns
162
+ * the payload.
163
+ *
164
+ * Owner→owner is allowed (the policy gate `peer-recover-user` carries
165
+ * the freshness factor). The `factors` argument forwards to the gate.
166
+ */
167
+ declare function issuePeerRecovery(db: Noydb, vault: string, options: IssuePeerRecoveryOptions, factors?: {
168
+ factors?: ReadonlyArray<FactorProof>;
169
+ sharedDevice?: boolean;
170
+ }): Promise<IssueInviteResult>;
171
+ /**
172
+ * Mark an outstanding invite as revoked. After this, `acceptInvite`
173
+ * for the same token rejects with `InviteRevokedError` BEFORE opening
174
+ * any session — closes #32's "revoked-link silent shadow keyring"
175
+ * defense (without the audit doc check, a revoked link could fall
176
+ * through to `createNoydb`'s no-keyring auto-create path and create
177
+ * a fresh empty vault).
178
+ *
179
+ * Idempotent — revoking an already-revoked invite is a no-op.
180
+ *
181
+ * Note: this does NOT delete the granted/recovered keyring; it only
182
+ * marks the invite token as unusable. To fully cancel the invite,
183
+ * call `db.revoke(vault, { userId })` separately. The two are split
184
+ * because peer-recovery doesn't have a "cancel" — the target user
185
+ * already existed.
186
+ */
187
+ declare function revokeInvite(db: Noydb, vault: string, encodedOrPayload: string | InvitePayload): Promise<void>;
188
+ /**
189
+ * Open the recipient's session under the invite. Validates TTL +
190
+ * revoke + already-accepted, opens `createNoydb` with the temp
191
+ * phrase, immediately rotates to `newPhrase`, marks the audit doc
192
+ * accepted, returns the live `Noydb` instance.
193
+ *
194
+ * Single-use is enforced two ways:
195
+ * 1. The rotation inside this function invalidates the temp phrase.
196
+ * 2. The audit doc's `acceptedAt` field is set on success — a
197
+ * second `acceptInvite` call sees it and throws
198
+ * `InviteAlreadyAcceptedError`.
199
+ *
200
+ * @throws {@link InviteExpiredError} when TTL has passed.
201
+ * @throws {@link InviteRevokedError} when the issuer has revoked.
202
+ * @throws {@link InviteAlreadyAcceptedError} on second call.
203
+ * @throws {@link InviteAuditMissingError} when no audit doc — closes
204
+ * #32's revoked-link-shadow-keyring defense.
205
+ */
206
+ declare function acceptInvite(encoded: string, options: AcceptInviteOptions): Promise<AcceptInviteResult>;
207
+ /** Encode the payload as a URL-fragment-safe base64url string. */
208
+ declare function encodeInvitePayload(payload: InvitePayload): string;
209
+ /** Decode a base64url string back to an InvitePayload. */
210
+ declare function decodeInvitePayload(encoded: string): InvitePayload;
211
+
4
212
  /**
5
213
  * **@noy-db/on-magic-link** — one-time-link viewer unlock for noy-db.
6
214
  *
@@ -289,4 +497,4 @@ declare function readMagicLinkGrant(options: {
289
497
  readonly recordId: string;
290
498
  }): Promise<MagicLinkGrantPayload | null>;
291
499
 
292
- export { type ClaimMagicLinkDelegationOptions, type ClaimMagicLinkDelegationResult, type ClaimedMagicLinkGrant, type CreateMagicLinkOptions, type IssueMagicLinkDelegationOptions, type IssueMagicLinkDelegationResult, MAGIC_LINK_DEFAULT_TTL_MS, type MagicLinkGrantSpec, type MagicLinkToken, buildMagicLinkKeyring, claimMagicLinkDelegation, createMagicLinkToken, deriveMagicLinkKEK, inspectMagicLinkDelegation, isMagicLinkValid, issueMagicLinkDelegation, readMagicLinkGrant, revokeMagicLinkDelegation };
500
+ export { type AcceptInviteOptions, type AcceptInviteResult, type ClaimMagicLinkDelegationOptions, type ClaimMagicLinkDelegationResult, type ClaimedMagicLinkGrant, type CreateMagicLinkOptions, InviteAlreadyAcceptedError, type InviteAuditDoc, InviteAuditMissingError, InviteExpiredError, type InviteKind, type InvitePayload, InviteRevokedError, type IssueInviteOptions, type IssueInviteResult, type IssueMagicLinkDelegationOptions, type IssueMagicLinkDelegationResult, type IssuePeerRecoveryOptions, MAGIC_LINK_DEFAULT_TTL_MS, type MagicLinkGrantSpec, type MagicLinkToken, acceptInvite, buildMagicLinkKeyring, claimMagicLinkDelegation, createMagicLinkToken, decodeInvitePayload, deriveMagicLinkKEK, encodeInvitePayload, inspectMagicLinkDelegation, isMagicLinkValid, issueInvite, issueMagicLinkDelegation, issuePeerRecovery, readMagicLinkGrant, revokeInvite, revokeMagicLinkDelegation };