@book.dev/sdk 1.60.0 → 1.64.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 +39 -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 +106 -9
- 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 +185 -0
- package/dist/identity.js +181 -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 +155 -0
- package/dist/provenance.js +23 -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 +91 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -1
- package/package.json +6 -1
package/dist/account.d.ts
CHANGED
|
@@ -52,6 +52,17 @@ export declare class AccountClient {
|
|
|
52
52
|
putSettings(token: string, settings: Record<string, unknown>): Promise<{
|
|
53
53
|
updatedAt: string;
|
|
54
54
|
}>;
|
|
55
|
+
/**
|
|
56
|
+
* Mint a verifiable identity assertion (JWS) for the OpenBook data server
|
|
57
|
+
* (OB-165). The data server verifies it against the account's JWKS and
|
|
58
|
+
* attributes the user's changes to `iss#sub`. Returns `{identity, expiresAt}`,
|
|
59
|
+
* or `null` when the account doesn't issue identities (501) — the app then
|
|
60
|
+
* acts as a named guest. Throws `AccountError(401)` on an invalid/revoked token.
|
|
61
|
+
*/
|
|
62
|
+
getIdentityToken(token: string, aud?: string): Promise<{
|
|
63
|
+
identity: string;
|
|
64
|
+
expiresAt: string;
|
|
65
|
+
} | null>;
|
|
55
66
|
/** Cheap token check (a settings GET): true if accepted, false on 401. */
|
|
56
67
|
validate(token: string): Promise<boolean>;
|
|
57
68
|
}
|
package/dist/account.js
CHANGED
|
@@ -88,6 +88,33 @@ export class AccountClient {
|
|
|
88
88
|
const body = (await res.json());
|
|
89
89
|
return { updatedAt: body.updatedAt };
|
|
90
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Mint a verifiable identity assertion (JWS) for the OpenBook data server
|
|
93
|
+
* (OB-165). The data server verifies it against the account's JWKS and
|
|
94
|
+
* attributes the user's changes to `iss#sub`. Returns `{identity, expiresAt}`,
|
|
95
|
+
* or `null` when the account doesn't issue identities (501) — the app then
|
|
96
|
+
* acts as a named guest. Throws `AccountError(401)` on an invalid/revoked token.
|
|
97
|
+
*/
|
|
98
|
+
async getIdentityToken(token, aud) {
|
|
99
|
+
// `aud` scopes the assertion to one data server (OB-177), so it can't be
|
|
100
|
+
// replayed to another. Required by the issuer only when it runs an audience
|
|
101
|
+
// allowlist; harmless (and unscoped) otherwise.
|
|
102
|
+
const url = new URL('/api/identity/token', this.baseUrl + '/');
|
|
103
|
+
if (aud)
|
|
104
|
+
url.searchParams.set('aud', aud);
|
|
105
|
+
const res = await fetch(url, {
|
|
106
|
+
headers: { authorization: `Bearer ${token}` },
|
|
107
|
+
cache: 'no-store',
|
|
108
|
+
});
|
|
109
|
+
if (res.status === 501)
|
|
110
|
+
return null; // issuance not configured on this account
|
|
111
|
+
if (!res.ok)
|
|
112
|
+
throw new AccountError(res.status, `account identity token failed (${res.status})`);
|
|
113
|
+
const body = (await res.json());
|
|
114
|
+
if (!body.identity || !body.expiresAt)
|
|
115
|
+
return null;
|
|
116
|
+
return { identity: body.identity, expiresAt: body.expiresAt };
|
|
117
|
+
}
|
|
91
118
|
/** Cheap token check (a settings GET): true if accepted, false on 401. */
|
|
92
119
|
async validate(token) {
|
|
93
120
|
try {
|
package/dist/account.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account.js","sourceRoot":"","sources":["../src/account.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"account.js","sourceRoot":"","sources":["../src/account.ts"],"names":[],"mappings":"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"}
|
package/dist/ai.d.ts
CHANGED
|
@@ -63,6 +63,16 @@ export interface AiConfig {
|
|
|
63
63
|
* and both UIs read settings through this, so old configs keep working.
|
|
64
64
|
*/
|
|
65
65
|
export declare function providerSettings(config: AiConfig, provider: AiProvider): AiProviderSettings;
|
|
66
|
+
/**
|
|
67
|
+
* Whether a provider is a **paid / hosted** engine — `openai` (an OpenAI-style
|
|
68
|
+
* endpoint that may be `api.openai.com`) or `claude` (Anthropic's hosted API).
|
|
69
|
+
* Both can bill per token and send content off the machine, so paid inference is
|
|
70
|
+
* fenced behind sign-in on a claimed multi-user instance (a guest must not be
|
|
71
|
+
* able to rack up inference cost). The rest — `off`/`mock`/`llama`/`mlx` — run
|
|
72
|
+
* locally and free, and stay open. The single source of truth for "is this
|
|
73
|
+
* inference paid"; routes and UI both classify the configured provider through it.
|
|
74
|
+
*/
|
|
75
|
+
export declare function isPaidProvider(provider: AiProvider): boolean;
|
|
66
76
|
export interface AiStatus {
|
|
67
77
|
config: AiConfig;
|
|
68
78
|
/** The engine can generate text right now. */
|
package/dist/ai.js
CHANGED
|
@@ -33,4 +33,16 @@ export function providerSettings(config, provider) {
|
|
|
33
33
|
}
|
|
34
34
|
return {};
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Whether a provider is a **paid / hosted** engine — `openai` (an OpenAI-style
|
|
38
|
+
* endpoint that may be `api.openai.com`) or `claude` (Anthropic's hosted API).
|
|
39
|
+
* Both can bill per token and send content off the machine, so paid inference is
|
|
40
|
+
* fenced behind sign-in on a claimed multi-user instance (a guest must not be
|
|
41
|
+
* able to rack up inference cost). The rest — `off`/`mock`/`llama`/`mlx` — run
|
|
42
|
+
* locally and free, and stay open. The single source of truth for "is this
|
|
43
|
+
* inference paid"; routes and UI both classify the configured provider through it.
|
|
44
|
+
*/
|
|
45
|
+
export function isPaidProvider(provider) {
|
|
46
|
+
return provider === 'openai' || provider === 'claude';
|
|
47
|
+
}
|
|
36
48
|
//# sourceMappingURL=ai.js.map
|
package/dist/ai.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai.js","sourceRoot":"","sources":["../src/ai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAgDH;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAgB,EAAE,QAAoB;IACrE,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC,QAAQ,CAAC,CAAC;IAC3C,IAAI,KAAK;QAAE,OAAO,KAAK,CAAC;IACxB,IAAI,QAAQ,KAAK,MAAM,CAAC,QAAQ,EAAE,CAAC;QACjC,OAAO,EAAC,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAC,CAAC;IAC5G,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC"}
|
|
1
|
+
{"version":3,"file":"ai.js","sourceRoot":"","sources":["../src/ai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAgDH;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAgB,EAAE,QAAoB;IACrE,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC,QAAQ,CAAC,CAAC;IAC3C,IAAI,KAAK;QAAE,OAAO,KAAK,CAAC;IACxB,IAAI,QAAQ,KAAK,MAAM,CAAC,QAAQ,EAAE,CAAC;QACjC,OAAO,EAAC,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAC,CAAC;IAC5G,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,QAAoB;IACjD,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,QAAQ,CAAC;AACxD,CAAC"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one authorization function (OB-189; contract
|
|
3
|
+
* `docs/sharing-access-contract-spike-OB-182.md` §1).
|
|
4
|
+
*
|
|
5
|
+
* `authorize(principal, page, ctx)` is the single, **pure, isomorphic** access
|
|
6
|
+
* decision for the whole product. The origin calls it on every content
|
|
7
|
+
* request/event; the client reuses it to grey out UI. It has **no I/O**: the
|
|
8
|
+
* roster role, the resolved (post-`inherit`) visibility, and the email-authority
|
|
9
|
+
* gate are all computed by the caller (the store) and handed in via {@link
|
|
10
|
+
* AccessCtx}. This module only composes them through the §1.2 precedence ladder.
|
|
11
|
+
*
|
|
12
|
+
* Wiring it into routes, streams and the request → {@link AccessCtx} build is
|
|
13
|
+
* OB-190 (enforcement) and is intentionally NOT here.
|
|
14
|
+
*/
|
|
15
|
+
import type { Principal } from './identity';
|
|
16
|
+
import type { InstanceConfig } from './provenance';
|
|
17
|
+
import type { AclLevel, MemberRole, PageVisibility } from './types';
|
|
18
|
+
/**
|
|
19
|
+
* Roster role, in the authorize layer (contract §1.1 calls this union `Role`).
|
|
20
|
+
* An ALIAS of {@link MemberRole} — there is exactly one role union in the SDK.
|
|
21
|
+
*/
|
|
22
|
+
export type Role = MemberRole;
|
|
23
|
+
/** A resolved (never `inherit`) page visibility scope. */
|
|
24
|
+
export type EffectiveVisibility = Exclude<PageVisibility, 'inherit'>;
|
|
25
|
+
/**
|
|
26
|
+
* One per-page ACL grant, as seen by {@link authorize} (the in-memory shape of a
|
|
27
|
+
* `page_acl` row, §2.3). Exactly one grantee key is set: `subject` (a grantee
|
|
28
|
+
* already bound to any trusted issuer) XOR `email` (a grantee by persona email,
|
|
29
|
+
* lowercased). An `email` entry MUST carry the pinned `issuer` — the
|
|
30
|
+
* email-authority — or it can never match (B1).
|
|
31
|
+
*/
|
|
32
|
+
export interface AclEntry {
|
|
33
|
+
/** Grantee already bound to a member/handle (any trusted issuer). */
|
|
34
|
+
subject?: string;
|
|
35
|
+
/** Grantee by persona email (lowercased). */
|
|
36
|
+
email?: string;
|
|
37
|
+
/** REQUIRED for an email entry — the pinned email-authority (B1). */
|
|
38
|
+
issuer?: string;
|
|
39
|
+
level: AclLevel;
|
|
40
|
+
}
|
|
41
|
+
/** The per-page inputs to a decision: the page's scope + its ACL grants. */
|
|
42
|
+
export interface AccessPage {
|
|
43
|
+
visibility: PageVisibility;
|
|
44
|
+
acl: AclEntry[];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The non-page, caller-resolved inputs to a decision (contract §1.1). Everything
|
|
48
|
+
* here is computed by the store before the call: the active-persona role, the
|
|
49
|
+
* effective visibility after resolving `inherit`, and whether the principal's
|
|
50
|
+
* email may be trusted.
|
|
51
|
+
*/
|
|
52
|
+
export interface AccessCtx {
|
|
53
|
+
config: Pick<InstanceConfig, 'guestAccess' | 'ownerSubject' | 'defaultVisibility' | 'emailAuthority'>;
|
|
54
|
+
/**
|
|
55
|
+
* Role of the principal's ACTIVE persona, resolved by the store: a roster row
|
|
56
|
+
* with `status='active'` whose BOUND `subject === principal.subject` (and, for
|
|
57
|
+
* an email persona, whose email === principal.email under the pinned authority).
|
|
58
|
+
* `invited`/`suspended` rows resolve to `null` — a dormant row grants nothing
|
|
59
|
+
* (S3). `null` ⇒ the principal is not an active member.
|
|
60
|
+
*/
|
|
61
|
+
role: Role | null;
|
|
62
|
+
/**
|
|
63
|
+
* Effective visibility after resolving `inherit` (up the PARENT chain for an
|
|
64
|
+
* ordinary page, or via the DATABASE HOST PAGE for a database row — N9), down to
|
|
65
|
+
* `config.defaultVisibility` at the root. Never `inherit`.
|
|
66
|
+
*/
|
|
67
|
+
effectiveVisibility: EffectiveVisibility;
|
|
68
|
+
/**
|
|
69
|
+
* True iff the principal's email may be trusted for persona / email-ACL
|
|
70
|
+
* matching: `verifiedVia==='jws'` AND `principal.issuer === config.emailAuthority`
|
|
71
|
+
* (B1). Only then does email matching fire. See {@link isEmailAuthoritative}.
|
|
72
|
+
*/
|
|
73
|
+
emailIsAuthoritative: boolean;
|
|
74
|
+
}
|
|
75
|
+
/** The decision: independent read/write grants plus the deciding rung's reason. */
|
|
76
|
+
export interface Decision {
|
|
77
|
+
canRead: boolean;
|
|
78
|
+
canWrite: boolean;
|
|
79
|
+
reason: string;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The `emailIsAuthoritative` predicate (contract §1.1, B1) as a reusable pure
|
|
83
|
+
* helper the store uses to build {@link AccessCtx.emailIsAuthoritative}. An email
|
|
84
|
+
* is trustworthy ONLY on a fresh JWS from the one pinned email-authority — a
|
|
85
|
+
* federated self-issuer asserting arbitrary emails can never satisfy it.
|
|
86
|
+
*/
|
|
87
|
+
export declare function isEmailAuthoritative(principal: Principal, config: Pick<InstanceConfig, 'emailAuthority'>): boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Decide read+write for a principal on a page (contract §1.2 precedence ladder).
|
|
90
|
+
*
|
|
91
|
+
* Ladder, highest authority first; the first rule that grants a *field* wins, and
|
|
92
|
+
* write is only ever granted by rules 0/1/2/3/4:
|
|
93
|
+
*
|
|
94
|
+
* 0. Unclaimed instance (`ownerSubject` unset) → legacy guest-gate short-circuit
|
|
95
|
+
* (loopback-only by the §2.6 exposure invariant).
|
|
96
|
+
* 1. Loopback owner (`verifiedVia==='local'`) → read+write.
|
|
97
|
+
* 2. Owner (`jws` && `subject===ownerSubject`) → read+write.
|
|
98
|
+
* 3. Per-page ACL: `write` → read+write; `read` → read (write falls through).
|
|
99
|
+
* 4. Roster role: `admin` → read+write; `viewer` → read per scope, write denied.
|
|
100
|
+
* 5. Visibility scope vs principal class → governs read for everyone else.
|
|
101
|
+
* 6. Guest-gate floor (`guestAccess='off'` denies the guest class even `public`).
|
|
102
|
+
* 7. Default deny.
|
|
103
|
+
*/
|
|
104
|
+
export declare function authorize(principal: Principal, page: AccessPage, ctx: AccessCtx): Decision;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one authorization function (OB-189; contract
|
|
3
|
+
* `docs/sharing-access-contract-spike-OB-182.md` §1).
|
|
4
|
+
*
|
|
5
|
+
* `authorize(principal, page, ctx)` is the single, **pure, isomorphic** access
|
|
6
|
+
* decision for the whole product. The origin calls it on every content
|
|
7
|
+
* request/event; the client reuses it to grey out UI. It has **no I/O**: the
|
|
8
|
+
* roster role, the resolved (post-`inherit`) visibility, and the email-authority
|
|
9
|
+
* gate are all computed by the caller (the store) and handed in via {@link
|
|
10
|
+
* AccessCtx}. This module only composes them through the §1.2 precedence ladder.
|
|
11
|
+
*
|
|
12
|
+
* Wiring it into routes, streams and the request → {@link AccessCtx} build is
|
|
13
|
+
* OB-190 (enforcement) and is intentionally NOT here.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* The `emailIsAuthoritative` predicate (contract §1.1, B1) as a reusable pure
|
|
17
|
+
* helper the store uses to build {@link AccessCtx.emailIsAuthoritative}. An email
|
|
18
|
+
* is trustworthy ONLY on a fresh JWS from the one pinned email-authority — a
|
|
19
|
+
* federated self-issuer asserting arbitrary emails can never satisfy it.
|
|
20
|
+
*/
|
|
21
|
+
export function isEmailAuthoritative(principal, config) {
|
|
22
|
+
return (principal.verifiedVia === 'jws' &&
|
|
23
|
+
!!config.emailAuthority &&
|
|
24
|
+
principal.issuer === config.emailAuthority);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The highest ACL grant a principal matches on this page (§1.2 rule 3), or `null`.
|
|
28
|
+
* `'write'` outranks `'read'`. A **subject** entry matches only a `jws` principal
|
|
29
|
+
* with the same subject (any trusted issuer); an **email** entry matches only when
|
|
30
|
+
* the email is authoritative AND the lowercased emails are equal AND the entry's
|
|
31
|
+
* `issuer` is the pinned email-authority (B1) — so a federated issuer can never
|
|
32
|
+
* satisfy an `account.book.pub`-scoped email grant.
|
|
33
|
+
*/
|
|
34
|
+
function matchAcl(principal, acl, ctx) {
|
|
35
|
+
const isJws = principal.verifiedVia === 'jws';
|
|
36
|
+
let best = null;
|
|
37
|
+
for (const entry of acl) {
|
|
38
|
+
let matches = false;
|
|
39
|
+
if (entry.subject) {
|
|
40
|
+
matches = isJws && entry.subject === principal.subject;
|
|
41
|
+
}
|
|
42
|
+
else if (entry.email) {
|
|
43
|
+
matches =
|
|
44
|
+
ctx.emailIsAuthoritative &&
|
|
45
|
+
!!principal.email &&
|
|
46
|
+
entry.email.toLowerCase() === principal.email.toLowerCase() &&
|
|
47
|
+
entry.issuer === ctx.config.emailAuthority;
|
|
48
|
+
}
|
|
49
|
+
if (matches) {
|
|
50
|
+
if (entry.level === 'write')
|
|
51
|
+
return 'write';
|
|
52
|
+
best = 'read';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return best;
|
|
56
|
+
}
|
|
57
|
+
/** Does the principal get READ purely from the visibility scope (§1.2 rule 5)? */
|
|
58
|
+
function scopeAllowsRead(principal, ctx, guestBlocked) {
|
|
59
|
+
switch (ctx.effectiveVisibility) {
|
|
60
|
+
case 'public':
|
|
61
|
+
// Read for ALL — incl. anonymous — overriding the guest gate, EXCEPT a
|
|
62
|
+
// guest when `guestAccess='off'` (rule 6 / footnote ¹).
|
|
63
|
+
return !guestBlocked;
|
|
64
|
+
case 'authenticated':
|
|
65
|
+
// Any signed-in (jws) user; guests denied (N8).
|
|
66
|
+
return principal.verifiedVia === 'jws';
|
|
67
|
+
case 'members':
|
|
68
|
+
// Only active roster members — an active row resolves to a non-null role.
|
|
69
|
+
// A signed-in non-member or invited/suspended persona (role null) gets
|
|
70
|
+
// nothing here; it may still read via an ACL above.
|
|
71
|
+
return ctx.role !== null;
|
|
72
|
+
case 'restricted':
|
|
73
|
+
// Only owner/admin/ACL, all handled above; everyone else: no.
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Decide read+write for a principal on a page (contract §1.2 precedence ladder).
|
|
79
|
+
*
|
|
80
|
+
* Ladder, highest authority first; the first rule that grants a *field* wins, and
|
|
81
|
+
* write is only ever granted by rules 0/1/2/3/4:
|
|
82
|
+
*
|
|
83
|
+
* 0. Unclaimed instance (`ownerSubject` unset) → legacy guest-gate short-circuit
|
|
84
|
+
* (loopback-only by the §2.6 exposure invariant).
|
|
85
|
+
* 1. Loopback owner (`verifiedVia==='local'`) → read+write.
|
|
86
|
+
* 2. Owner (`jws` && `subject===ownerSubject`) → read+write.
|
|
87
|
+
* 3. Per-page ACL: `write` → read+write; `read` → read (write falls through).
|
|
88
|
+
* 4. Roster role: `admin` → read+write; `viewer` → read per scope, write denied.
|
|
89
|
+
* 5. Visibility scope vs principal class → governs read for everyone else.
|
|
90
|
+
* 6. Guest-gate floor (`guestAccess='off'` denies the guest class even `public`).
|
|
91
|
+
* 7. Default deny.
|
|
92
|
+
*/
|
|
93
|
+
export function authorize(principal, page, ctx) {
|
|
94
|
+
const { config } = ctx;
|
|
95
|
+
const isLocal = principal.verifiedVia === 'local';
|
|
96
|
+
const isJws = principal.verifiedVia === 'jws';
|
|
97
|
+
// Rule 0 — unclaimed instance: legacy short-circuit, preserving today's
|
|
98
|
+
// loopback behaviour exactly. Roster/scope/ACL don't exist until claimed; the
|
|
99
|
+
// in-process (`local`) and any `jws` caller always read+write (truth-table
|
|
100
|
+
// footer), everyone else is judged by the guest gate. Reachable only on
|
|
101
|
+
// loopback (the §2.6 exposure invariant — a claim is required before exposure).
|
|
102
|
+
if (config.ownerSubject === undefined) {
|
|
103
|
+
const privileged = isJws || isLocal;
|
|
104
|
+
return {
|
|
105
|
+
canRead: config.guestAccess !== 'off' || privileged,
|
|
106
|
+
canWrite: config.guestAccess === 'write' || privileged,
|
|
107
|
+
reason: 'legacy-guest-gate',
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
// ── Claimed instance ──────────────────────────────────────────────────────
|
|
111
|
+
const isOwner = isJws && principal.subject === config.ownerSubject; // rule 2
|
|
112
|
+
const aclMatch = matchAcl(principal, page.acl, ctx); // rule 3
|
|
113
|
+
const isAdmin = ctx.role === 'admin'; // rule 4
|
|
114
|
+
const isViewer = ctx.role === 'viewer'; // rule 4
|
|
115
|
+
// Rule 6 floor: the guest class is denied even `public` when guests are
|
|
116
|
+
// disabled. Hardened (OB-190, OB-189 security review #1) to the WHOLE
|
|
117
|
+
// unauthenticated class — any principal that is neither an authenticated `jws`
|
|
118
|
+
// user nor the loopback owner. `kind==='guest'` is the common case, but a
|
|
119
|
+
// non-request-assertable `unverified`/`synced` `user` principal must never slip
|
|
120
|
+
// past the `guestAccess='off'` floor onto a `public` page either (defence in
|
|
121
|
+
// depth behind the middleware, which already rejects such principals at the gate
|
|
122
|
+
// on an identity-enabled instance).
|
|
123
|
+
const guestBlocked = !isJws && !isLocal && config.guestAccess === 'off';
|
|
124
|
+
const scopeRead = scopeAllowsRead(principal, ctx, guestBlocked); // rule 5
|
|
125
|
+
// WRITE — only rules 1/2/3(write)/4(admin) ever grant it. `viewer` is locked,
|
|
126
|
+
// jws-non-members and guests are read-only on a claimed instance.
|
|
127
|
+
const canWrite = isLocal || isOwner || aclMatch === 'write' || isAdmin;
|
|
128
|
+
// READ — granted by the first rung that allows it.
|
|
129
|
+
const canRead = isLocal || isOwner || aclMatch !== null || isAdmin || scopeRead;
|
|
130
|
+
let reason;
|
|
131
|
+
if (isLocal)
|
|
132
|
+
reason = 'local-owner';
|
|
133
|
+
else if (isOwner)
|
|
134
|
+
reason = 'owner';
|
|
135
|
+
else if (aclMatch === 'write')
|
|
136
|
+
reason = 'acl-write';
|
|
137
|
+
else if (isAdmin)
|
|
138
|
+
reason = 'admin';
|
|
139
|
+
else if (aclMatch === 'read')
|
|
140
|
+
reason = 'acl-read';
|
|
141
|
+
else if (isViewer)
|
|
142
|
+
reason = canRead ? 'viewer-readonly' : 'no-grant';
|
|
143
|
+
else if (canRead)
|
|
144
|
+
reason = 'visibility-scope';
|
|
145
|
+
else if (guestBlocked)
|
|
146
|
+
reason = 'guest-disabled';
|
|
147
|
+
else
|
|
148
|
+
reason = 'no-grant';
|
|
149
|
+
return { canRead, canWrite, reason };
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=authorize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authorize.js","sourceRoot":"","sources":["../src/authorize.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { PageSnapshot } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Compute the `[blockId, subject]` authorship for `next`. Unchanged blocks keep
|
|
4
|
+
* their prior author; changed/new blocks get `authorSubject` when it is a
|
|
5
|
+
* verified identity (a non-empty subject), and are otherwise left unattributed
|
|
6
|
+
* (an anonymous/guest edit honestly clears a block's verified author rather than
|
|
7
|
+
* falsely keeping the previous one). Returns a sparse map — only blocks with a
|
|
8
|
+
* known author — or `null` when none are attributed (so the field stays absent
|
|
9
|
+
* on single-user / unverified documents).
|
|
10
|
+
*/
|
|
11
|
+
export declare function computeBlockAuthors(prev: PageSnapshot | null | undefined, next: PageSnapshot, authorSubject: string): Array<[string, string]> | null;
|
|
12
|
+
/**
|
|
13
|
+
* Return `next` with its `authors` stamped relative to `prev`. `authorSubject` is
|
|
14
|
+
* the request's *verified* principal subject (`iss#sub`), or `''` for an
|
|
15
|
+
* unverified/guest/local write (which carries no new attribution). Idempotent
|
|
16
|
+
* when the document is unchanged. Omits the `authors` key entirely when nothing
|
|
17
|
+
* is attributed, so it never appears on single-user/unverified snapshots.
|
|
18
|
+
*/
|
|
19
|
+
export declare function stampSnapshotAuthors(prev: PageSnapshot | null | undefined, next: PageSnapshot, authorSubject: string): PageSnapshot;
|
|
20
|
+
/**
|
|
21
|
+
* The verified author of a snapshot's most-recently-changed attributed block —
|
|
22
|
+
* the snapshot's "last verified editor", read on the receiving instance to
|
|
23
|
+
* attribute a synced edit. Uses `mtimes` to find the newest block, falling back
|
|
24
|
+
* to any attributed block. Returns `null` when nothing is attributed.
|
|
25
|
+
*/
|
|
26
|
+
export declare function latestSnapshotAuthor(data: PageSnapshot | null | undefined): string | null;
|
package/dist/authors.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-block authorship that travels WITH a page snapshot (OB-170), so an edit
|
|
3
|
+
* made on one instance is correctly attributed when the snapshot reaches another
|
|
4
|
+
* (the sync/merge path). Parallel to `mtimes` (which records *when* each block
|
|
5
|
+
* changed): `authors` records *who* — but only for **verified** identities, so
|
|
6
|
+
* guest/local/unverified edits never pollute the carried attribution.
|
|
7
|
+
*
|
|
8
|
+
* A snapshot's `authors` is a sparse `[blockId, subject]` map (only blocks with
|
|
9
|
+
* a known verified author). The server stamps it on write from the request's
|
|
10
|
+
* verified principal, carrying forward each unchanged block's prior author and
|
|
11
|
+
* setting the current author on changed/new blocks. Pure + isomorphic, mirroring
|
|
12
|
+
* `mtime.ts` so it runs on the server write path and in tests alike.
|
|
13
|
+
*/
|
|
14
|
+
import { snapshotBlocks } from './mtime';
|
|
15
|
+
/**
|
|
16
|
+
* Compute the `[blockId, subject]` authorship for `next`. Unchanged blocks keep
|
|
17
|
+
* their prior author; changed/new blocks get `authorSubject` when it is a
|
|
18
|
+
* verified identity (a non-empty subject), and are otherwise left unattributed
|
|
19
|
+
* (an anonymous/guest edit honestly clears a block's verified author rather than
|
|
20
|
+
* falsely keeping the previous one). Returns a sparse map — only blocks with a
|
|
21
|
+
* known author — or `null` when none are attributed (so the field stays absent
|
|
22
|
+
* on single-user / unverified documents).
|
|
23
|
+
*/
|
|
24
|
+
export function computeBlockAuthors(prev, next, authorSubject) {
|
|
25
|
+
const prevBlocks = snapshotBlocks(prev);
|
|
26
|
+
const prevHash = new Map();
|
|
27
|
+
for (const b of prevBlocks)
|
|
28
|
+
prevHash.set(b.id, b.hash);
|
|
29
|
+
const prevAuthor = new Map(prev?.authors ?? []);
|
|
30
|
+
const verified = authorSubject.length > 0;
|
|
31
|
+
const out = [];
|
|
32
|
+
for (const b of snapshotBlocks(next)) {
|
|
33
|
+
const unchanged = prevHash.get(b.id) === b.hash;
|
|
34
|
+
const author = unchanged ? prevAuthor.get(b.id) ?? '' : verified ? authorSubject : '';
|
|
35
|
+
if (author)
|
|
36
|
+
out.push([b.id, author]);
|
|
37
|
+
}
|
|
38
|
+
return out.length > 0 ? out : null;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Return `next` with its `authors` stamped relative to `prev`. `authorSubject` is
|
|
42
|
+
* the request's *verified* principal subject (`iss#sub`), or `''` for an
|
|
43
|
+
* unverified/guest/local write (which carries no new attribution). Idempotent
|
|
44
|
+
* when the document is unchanged. Omits the `authors` key entirely when nothing
|
|
45
|
+
* is attributed, so it never appears on single-user/unverified snapshots.
|
|
46
|
+
*/
|
|
47
|
+
export function stampSnapshotAuthors(prev, next, authorSubject) {
|
|
48
|
+
const authors = computeBlockAuthors(prev, next, authorSubject);
|
|
49
|
+
if (!authors) {
|
|
50
|
+
// Nothing attributed: drop any stale `authors` rather than carry an empty map.
|
|
51
|
+
if (next.authors === undefined)
|
|
52
|
+
return next;
|
|
53
|
+
const rest = { ...next };
|
|
54
|
+
delete rest.authors;
|
|
55
|
+
return rest;
|
|
56
|
+
}
|
|
57
|
+
return { ...next, authors };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The verified author of a snapshot's most-recently-changed attributed block —
|
|
61
|
+
* the snapshot's "last verified editor", read on the receiving instance to
|
|
62
|
+
* attribute a synced edit. Uses `mtimes` to find the newest block, falling back
|
|
63
|
+
* to any attributed block. Returns `null` when nothing is attributed.
|
|
64
|
+
*/
|
|
65
|
+
export function latestSnapshotAuthor(data) {
|
|
66
|
+
const authors = new Map(data?.authors ?? []);
|
|
67
|
+
if (authors.size === 0)
|
|
68
|
+
return null;
|
|
69
|
+
let latestIso = null;
|
|
70
|
+
let latestSubject = null;
|
|
71
|
+
for (const [blockId, iso] of data?.mtimes ?? []) {
|
|
72
|
+
const subject = authors.get(blockId);
|
|
73
|
+
if (subject && (latestIso === null || iso > latestIso)) {
|
|
74
|
+
latestIso = iso;
|
|
75
|
+
latestSubject = subject;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// No mtimes overlap (e.g. a snapshot without mtimes) → any attributed author.
|
|
79
|
+
return latestSubject ?? authors.values().next().value ?? null;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=authors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authors.js","sourceRoot":"","sources":["../src/authors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAC,cAAc,EAAC,MAAM,SAAS,CAAC;AAGvC;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAAqC,EACrC,IAAkB,EAClB,aAAqB;IAErB,MAAM,UAAU,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC3C,KAAK,MAAM,CAAC,IAAI,UAAU;QAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IACvD,MAAM,UAAU,GAAG,IAAI,GAAG,CAAiB,IAAI,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC;IAChE,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC;IAE1C,MAAM,GAAG,GAA4B,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,IAAI,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,MAAM,SAAS,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC;QAChD,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;QACtF,IAAI,MAAM;YAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;AACrC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAClC,IAAqC,EACrC,IAAkB,EAClB,aAAqB;IAErB,MAAM,OAAO,GAAG,mBAAmB,CAAC,IAAI,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC;IAC/D,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,+EAA+E;QAC/E,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QAC5C,MAAM,IAAI,GAAG,EAAC,GAAG,IAAI,EAAC,CAAC;QACvB,OAAO,IAAI,CAAC,OAAO,CAAC;QACpB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,EAAC,GAAG,IAAI,EAAE,OAAO,EAAC,CAAC;AAC5B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAqC;IACxE,MAAM,OAAO,GAAG,IAAI,GAAG,CAAiB,IAAI,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC;IAC7D,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,IAAI,SAAS,GAAkB,IAAI,CAAC;IACpC,IAAI,aAAa,GAAkB,IAAI,CAAC;IACxC,KAAK,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,MAAM,IAAI,EAAE,EAAE,CAAC;QAChD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,OAAO,IAAI,CAAC,SAAS,KAAK,IAAI,IAAI,GAAG,GAAG,SAAS,CAAC,EAAE,CAAC;YACvD,SAAS,GAAG,GAAG,CAAC;YAChB,aAAa,GAAG,OAAO,CAAC;QAC1B,CAAC;IACH,CAAC;IACD,8EAA8E;IAC9E,OAAO,aAAa,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,IAAI,CAAC;AAChE,CAAC"}
|
package/dist/backup.d.ts
CHANGED
|
@@ -37,6 +37,45 @@ export interface ImportResult {
|
|
|
37
37
|
/** old page id → new page id (copy mode; identity in overwrite). */
|
|
38
38
|
idMap: Record<string, string>;
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Backup cadences, in increasing interval. The server keeps a rolling set per
|
|
42
|
+
* cadence (grandfather-father-son rotation), so short cadences churn fast and
|
|
43
|
+
* long ones are retained sparsely — automatic, tiered data safety on top of the
|
|
44
|
+
* ad-hoc export.
|
|
45
|
+
*/
|
|
46
|
+
export type BackupCadence = 'daily' | 'weekly' | 'monthly' | 'yearly';
|
|
47
|
+
export declare const BACKUP_CADENCES: readonly BackupCadence[];
|
|
48
|
+
/** Interval of each cadence, in milliseconds. */
|
|
49
|
+
export declare const BACKUP_CADENCE_MS: Record<BackupCadence, number>;
|
|
50
|
+
/** Scheduled-backup policy, persisted server-side in the `settings` table. */
|
|
51
|
+
export interface BackupConfig {
|
|
52
|
+
/** Master switch — when false the scheduler is idle (the default). */
|
|
53
|
+
enabled: boolean;
|
|
54
|
+
/** Where backups are written; `null` = the server default (`<dataDir>/backups`). */
|
|
55
|
+
dir: string | null;
|
|
56
|
+
/** Which cadences are active. */
|
|
57
|
+
cadences: Record<BackupCadence, boolean>;
|
|
58
|
+
/** How many snapshots to retain per cadence before pruning the oldest. */
|
|
59
|
+
keep: Record<BackupCadence, number>;
|
|
60
|
+
/** Last successful run per cadence (ISO), so a reboot catches up overdue ones. */
|
|
61
|
+
lastRun: Partial<Record<BackupCadence, string>>;
|
|
62
|
+
}
|
|
63
|
+
export declare const DEFAULT_BACKUP_CONFIG: BackupConfig;
|
|
64
|
+
/** A derived, per-cadence view for the UI (last/next run + how many are on disk). */
|
|
65
|
+
export interface BackupCadenceStatus {
|
|
66
|
+
cadence: BackupCadence;
|
|
67
|
+
enabled: boolean;
|
|
68
|
+
lastRun: string | null;
|
|
69
|
+
nextDue: string | null;
|
|
70
|
+
count: number;
|
|
71
|
+
}
|
|
72
|
+
/** What `GET /api/backups` returns: the policy + resolved dir + derived status. */
|
|
73
|
+
export interface BackupStatus {
|
|
74
|
+
config: BackupConfig;
|
|
75
|
+
/** The resolved output directory (config.dir, or the server default). */
|
|
76
|
+
resolvedDir: string | null;
|
|
77
|
+
cadences: BackupCadenceStatus[];
|
|
78
|
+
}
|
|
40
79
|
/**
|
|
41
80
|
* Pure: re-key a bundle for copy-mode import. Mints a fresh id for every page and
|
|
42
81
|
* database, remaps every internal reference (`parentId`, `databaseId`,
|
package/dist/backup.js
CHANGED
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
export const BACKUP_VERSION = 1;
|
|
2
|
+
export const BACKUP_CADENCES = ['daily', 'weekly', 'monthly', 'yearly'];
|
|
3
|
+
/** Interval of each cadence, in milliseconds. */
|
|
4
|
+
export const BACKUP_CADENCE_MS = {
|
|
5
|
+
daily: 24 * 60 * 60 * 1000,
|
|
6
|
+
weekly: 7 * 24 * 60 * 60 * 1000,
|
|
7
|
+
monthly: 30 * 24 * 60 * 60 * 1000,
|
|
8
|
+
yearly: 365 * 24 * 60 * 60 * 1000,
|
|
9
|
+
};
|
|
10
|
+
export const DEFAULT_BACKUP_CONFIG = {
|
|
11
|
+
enabled: false,
|
|
12
|
+
dir: null,
|
|
13
|
+
cadences: { daily: true, weekly: true, monthly: true, yearly: true },
|
|
14
|
+
keep: { daily: 7, weekly: 5, monthly: 12, yearly: 3 },
|
|
15
|
+
lastRun: {},
|
|
16
|
+
};
|
|
2
17
|
/**
|
|
3
18
|
* Pure: re-key a bundle for copy-mode import. Mints a fresh id for every page and
|
|
4
19
|
* database, remaps every internal reference (`parentId`, `databaseId`,
|
package/dist/backup.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backup.js","sourceRoot":"","sources":["../src/backup.ts"],"names":[],"mappings":"AAcA,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"backup.js","sourceRoot":"","sources":["../src/backup.ts"],"names":[],"mappings":"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"}
|