@celsoft/platform 0.14.0 → 0.15.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.
@@ -0,0 +1,76 @@
1
+ import { type SQL } from "drizzle-orm";
2
+ import type { NodePgDatabase } from "drizzle-orm/node-postgres";
3
+ import { LINK_HEADER } from "./link-header.js";
4
+ export { LINK_HEADER };
5
+ export declare const LEVELS: readonly ["read", "create", "write", "delete", "manage"];
6
+ export type Level = (typeof LEVELS)[number];
7
+ export type Grantee = {
8
+ user: string;
9
+ } | {
10
+ team: string;
11
+ } | {
12
+ organization: string;
13
+ } | {
14
+ email: string;
15
+ };
16
+ export interface Grant {
17
+ to: Grantee;
18
+ level: Level;
19
+ grantedBy: string | null;
20
+ }
21
+ export type ShareEmail = {
22
+ path?: string;
23
+ subject?: string;
24
+ text?: string;
25
+ } | false;
26
+ export interface ShareNotice {
27
+ to: string;
28
+ sharedBy: string | null;
29
+ level: Level;
30
+ path: string;
31
+ subject?: string;
32
+ text?: string;
33
+ }
34
+ export interface Link {
35
+ id: string;
36
+ level: Level;
37
+ expiresAt: Date | null;
38
+ }
39
+ export interface Access {
40
+ level(kind: string, id: string): Promise<Level | null>;
41
+ can(kind: string, id: string, level: Level): Promise<boolean>;
42
+ require(kind: string, id: string, level: Level): Promise<void>;
43
+ place(kind: string, id: string, parentKind: string, parentId: string): Promise<void>;
44
+ remove(kind: string, id: string): Promise<void>;
45
+ restrict(kind: string, id: string, restricted?: boolean): Promise<void>;
46
+ share(kind: string, id: string, to: Grantee, level: Level, options?: {
47
+ email?: ShareEmail;
48
+ }): Promise<void>;
49
+ revoke(kind: string, id: string, to: Grantee): Promise<void>;
50
+ grants(kind: string, id: string): Promise<Grant[]>;
51
+ sharedWithMe(kind: string): Promise<string[]>;
52
+ link(kind: string, id: string, level: Level, options?: {
53
+ expires?: Date;
54
+ }): Promise<{
55
+ id: string;
56
+ token: string;
57
+ }>;
58
+ links(kind: string, id: string): Promise<Link[]>;
59
+ revokeLink(kind: string, id: string, linkId: string): Promise<void>;
60
+ accessible(kind: string, level: Level, column: unknown): Promise<SQL>;
61
+ in(tx: unknown): Access;
62
+ }
63
+ export interface Caller {
64
+ userId: string | null;
65
+ link: string | null;
66
+ }
67
+ export declare const MAX_DEPTH = 64;
68
+ type Db = Pick<NodePgDatabase<Record<string, unknown>>, "execute" | "transaction">;
69
+ export declare function rank(level: Level): number;
70
+ export declare function shareMessage(notice: ShareNotice, appName: string, origin: string): {
71
+ to: string;
72
+ subject: string;
73
+ text: string;
74
+ };
75
+ export declare function hashToken(token: string): Promise<string>;
76
+ export declare function createAccess(db: Db, caller: () => Promise<Caller>, notify?: (notice: ShareNotice) => Promise<void>): Access;
@@ -0,0 +1,307 @@
1
+ import { sql } from "drizzle-orm";
2
+ import { EndpointError } from "./endpoint-error.js";
3
+ import { LINK_HEADER } from "./link-header.js";
4
+ export { LINK_HEADER };
5
+ // Who may do what to an app's items; see docs/design.md, App Code, Access, and the cases in
6
+ // docs/access-rules.md. Every check is one query against the app's own Postgres, never cached.
7
+ export const LEVELS = ["read", "create", "write", "delete", "manage"];
8
+ // The longest chain from an item up to its user or organization, counting the item itself.
9
+ export const MAX_DEPTH = 64;
10
+ const ROOTS = ["user", "organization"];
11
+ const KIND = /^[a-z][a-z0-9_-]{0,62}$/;
12
+ const [READ, CREATE, WRITE, DELETE, MANAGE] = [1, 2, 3, 4, 5];
13
+ export function rank(level) {
14
+ const n = LEVELS.indexOf(level);
15
+ if (n < 0)
16
+ throw new Error(`unknown access level ${JSON.stringify(level)}; use ${LEVELS.join(", ")}`);
17
+ return n + 1;
18
+ }
19
+ const named = (n) => (n >= READ ? LEVELS[n - 1] : null);
20
+ function checkKind(kind) {
21
+ if (typeof kind !== "string" || !KIND.test(kind) || ROOTS.includes(kind)) {
22
+ throw new Error(`access kind ${JSON.stringify(kind)} must be lowercase letters, digits, - and _, and not "user" or "organization"`);
23
+ }
24
+ }
25
+ function checkId(id) {
26
+ if (typeof id !== "string" || id.length === 0 || id.length > 200)
27
+ throw new Error(`access id ${JSON.stringify(id)} must be 1 to 200 characters`);
28
+ }
29
+ const notFound = () => new EndpointError(404, "not found");
30
+ export function shareMessage(notice, appName, origin) {
31
+ const who = notice.sharedBy || "Someone";
32
+ const url = new URL(notice.path, origin).toString();
33
+ return {
34
+ to: notice.to,
35
+ subject: notice.subject ?? `${who} shared something with you on ${appName}`,
36
+ text: `${notice.text ?? `${who} gave you ${notice.level} access on ${appName}.`}\n\n${url}\n`,
37
+ };
38
+ }
39
+ export async function hashToken(token) {
40
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(token));
41
+ return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
42
+ }
43
+ function newToken() {
44
+ const bytes = crypto.getRandomValues(new Uint8Array(32));
45
+ return btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
46
+ }
47
+ // The caller's level on one item, 0 for none, as a scalar subquery. It walks up the chain to the
48
+ // user or organization at the top, stops counting grants and links above the first restricted
49
+ // item, and gives nothing at all to a chain that never reaches a user or organization that exists.
50
+ function levelSql(kind, id, who) {
51
+ return sql `(
52
+ with recursive chain as (
53
+ select i.kind, i.id, i.parent_kind, i.parent_id, i.restricted, 0 as depth
54
+ from access_item i where i.kind = ${kind} and i.id = ${id}
55
+ union all
56
+ select p.kind, p.id, p.parent_kind, p.parent_id, p.restricted, c.depth + 1
57
+ from chain c join access_item p on p.kind = c.parent_kind and p.id = c.parent_id
58
+ where c.depth < ${MAX_DEPTH - 1}
59
+ ),
60
+ root as (
61
+ select parent_kind as kind, parent_id as id from chain
62
+ where (parent_kind = 'user' and exists (select 1 from "user" u where u.id::text = parent_id))
63
+ or (parent_kind = 'organization' and exists (select 1 from organization o where o.id::text = parent_id))
64
+ ),
65
+ cut as (select min(depth) as depth from chain where restricted),
66
+ reach as (select chain.kind, chain.id from chain, cut where cut.depth is null or chain.depth <= cut.depth),
67
+ me as (select ${who.userId}::uuid as id),
68
+ levels as (
69
+ select ${MANAGE} as level from root, me where root.kind = 'user' and root.id = me.id::text
70
+ union all
71
+ select case btrim(roles.name)
72
+ when 'owner' then ${MANAGE}
73
+ when 'admin' then ${DELETE}
74
+ when 'member' then (select case when cut.depth is null then ${READ} else 0 end from cut)
75
+ else 0 end
76
+ from root
77
+ join member m on root.kind = 'organization' and m.organization_id::text = root.id
78
+ join me on m.user_id = me.id
79
+ cross join lateral unnest(string_to_array(m.role, ',')) as roles(name)
80
+ union all
81
+ select g.level from access_grant g join reach on g.item_kind = reach.kind and g.item_id = reach.id, me
82
+ where g.user_id = me.id
83
+ or g.team_id in (select tm.team_id from team_member tm where tm.user_id = me.id)
84
+ or g.organization_id in (select mm.organization_id from member mm where mm.user_id = me.id)
85
+ or g.email = (select lower(u.email) from "user" u where u.id = me.id and u.email_verified)
86
+ union all
87
+ select l.level from access_link l join reach on l.item_kind = reach.kind and l.item_id = reach.id
88
+ where l.token_hash = ${who.linkHash} and (l.expires_at is null or l.expires_at > now())
89
+ )
90
+ select case when exists (select 1 from root) then coalesce(max(level), 0) else 0 end from levels
91
+ )`;
92
+ }
93
+ const rows = async (db, query) => (await db.execute(query)).rows;
94
+ function granteeColumns(to) {
95
+ const keys = Object.keys(to ?? {});
96
+ if (keys.length !== 1 || !["user", "team", "organization", "email"].includes(keys[0])) {
97
+ throw new Error("share with exactly one of { user }, { team }, { organization } or { email }");
98
+ }
99
+ const value = Object.values(to)[0];
100
+ if (typeof value !== "string" || value.trim() === "")
101
+ throw new Error("a grantee needs a non-empty id or email");
102
+ return {
103
+ user: "user" in to ? to.user : null,
104
+ team: "team" in to ? to.team : null,
105
+ organization: "organization" in to ? to.organization : null,
106
+ email: "email" in to ? to.email.trim().toLowerCase() : null,
107
+ };
108
+ }
109
+ function grantee(row) {
110
+ if (row.user_id)
111
+ return { user: row.user_id };
112
+ if (row.team_id)
113
+ return { team: row.team_id };
114
+ if (row.organization_id)
115
+ return { organization: row.organization_id };
116
+ return { email: row.email };
117
+ }
118
+ // Postgres refuses a grant to a user, team or organization that does not exist, or whose id is not
119
+ // a UUID at all; both look like any other missing thing.
120
+ async function missingAsNotFound(work) {
121
+ try {
122
+ return await work;
123
+ }
124
+ catch (error) {
125
+ const code = error.code ?? error.cause?.code;
126
+ if (code === "23503" || code === "22P02")
127
+ throw notFound();
128
+ throw error;
129
+ }
130
+ }
131
+ export function createAccess(db, caller, notify) {
132
+ let who;
133
+ const me = () => (who ??= caller().then(async ({ userId, link }) => ({ userId, linkHash: link ? await hashToken(link) : null })));
134
+ async function levelOf(on, kind, id) {
135
+ checkKind(kind);
136
+ checkId(id);
137
+ const [row] = await rows(on, sql `select ${levelSql(sql `${kind}`, sql `${id}`, await me())} as level`);
138
+ const level = Number(row?.level ?? 0);
139
+ if (level === 0)
140
+ await reportTooDeep(on, kind, id);
141
+ return level;
142
+ }
143
+ // A chain longer than place allows can only come from writing the tables directly; it is
144
+ // refused like any other, and logged, since it means a bug.
145
+ async function reportTooDeep(on, kind, id) {
146
+ const [up] = await rows(on, chainUp(kind, id, MAX_DEPTH + 1));
147
+ if (up && Number(up.depth) >= MAX_DEPTH)
148
+ console.error(`access: ${kind} ${id} sits more than ${MAX_DEPTH} items deep; refused`);
149
+ }
150
+ async function need(on, kind, id, level) {
151
+ if ((await levelOf(on, kind, id)) < level)
152
+ throw notFound();
153
+ }
154
+ // The level the top of the tree gives: a user over their own items, a role over an organization's.
155
+ async function topLevel(on, kind, id) {
156
+ const { userId } = await me();
157
+ if (!userId)
158
+ return 0;
159
+ if (kind === "user")
160
+ return id === userId ? MANAGE : 0;
161
+ const [row] = await rows(on, sql `select coalesce(max(case btrim(roles.name) when 'owner' then ${MANAGE} when 'admin' then ${DELETE} when 'member' then ${READ} else 0 end), 0) as level
162
+ from member m cross join lateral unnest(string_to_array(m.role, ',')) as roles(name)
163
+ where m.organization_id::text = ${id} and m.user_id = ${userId}::uuid`);
164
+ return Number(row?.level ?? 0);
165
+ }
166
+ async function place(on, kind, id, parentKind, parentId) {
167
+ checkKind(kind);
168
+ checkId(id);
169
+ checkId(parentId);
170
+ await on.transaction(async (raw) => {
171
+ const tx = raw;
172
+ // One move at a time per database, so two moves can never pass each other's loop check.
173
+ await tx.execute(sql `select pg_advisory_xact_lock(hashtext('@celsoft/platform access place'))`);
174
+ const [existing] = await rows(tx, sql `select 1 from access_item where kind = ${kind} and id = ${id}`);
175
+ if (existing)
176
+ await need(tx, kind, id, WRITE);
177
+ let above = 0;
178
+ if (ROOTS.includes(parentKind)) {
179
+ if ((await topLevel(tx, parentKind, parentId)) < CREATE)
180
+ throw notFound();
181
+ }
182
+ else {
183
+ await need(tx, parentKind, parentId, CREATE);
184
+ const [up] = await rows(tx, sql `${chainUp(parentKind, parentId, MAX_DEPTH)}, (select exists (select 1 from up where kind = ${kind} and id = ${id})) as within`);
185
+ if (up?.within)
186
+ throw new EndpointError(409, "an item cannot be placed inside itself");
187
+ above = Number(up?.depth ?? 0) + 1;
188
+ }
189
+ const [down] = await rows(tx, sql `with recursive down as (
190
+ select kind, id, 1 as depth from access_item where parent_kind = ${kind} and parent_id = ${id}
191
+ union all
192
+ select c.kind, c.id, d.depth + 1 from down d join access_item c on c.parent_kind = d.kind and c.parent_id = d.id
193
+ where d.depth < ${MAX_DEPTH}
194
+ )
195
+ select coalesce(max(depth), 0) as height from down`);
196
+ if (above + 1 + Number(down?.height ?? 0) > MAX_DEPTH)
197
+ throw new EndpointError(409, `items can sit at most ${MAX_DEPTH} deep`);
198
+ await tx.execute(sql `
199
+ insert into access_item (kind, id, parent_kind, parent_id) values (${kind}, ${id}, ${parentKind}, ${parentId})
200
+ on conflict (kind, id) do update set parent_kind = excluded.parent_kind, parent_id = excluded.parent_id, updated_at = now()`);
201
+ });
202
+ }
203
+ function bound(on) {
204
+ return {
205
+ level: async (kind, id) => named(await levelOf(on, kind, id)),
206
+ can: async (kind, id, level) => (await levelOf(on, kind, id)) >= rank(level),
207
+ require: async (kind, id, level) => need(on, kind, id, rank(level)),
208
+ place: (kind, id, parentKind, parentId) => place(on, kind, id, parentKind, parentId),
209
+ async remove(kind, id) {
210
+ await need(on, kind, id, DELETE);
211
+ const [child] = await rows(on, sql `select 1 from access_item where parent_kind = ${kind} and parent_id = ${id} limit 1`);
212
+ if (child)
213
+ throw new EndpointError(409, "move or delete what is inside first");
214
+ await on.execute(sql `delete from access_item where kind = ${kind} and id = ${id}`);
215
+ },
216
+ async restrict(kind, id, restricted = true) {
217
+ await need(on, kind, id, MANAGE);
218
+ await on.execute(sql `update access_item set restricted = ${restricted}, updated_at = now() where kind = ${kind} and id = ${id}`);
219
+ },
220
+ async share(kind, id, to, level, options = {}) {
221
+ const n = rank(level);
222
+ const g = granteeColumns(to);
223
+ const email = options.email ?? {};
224
+ if (email && email.path !== undefined && !/^\/(?![/\\])/.test(email.path))
225
+ throw new Error(`a sharing email's path must start with one /, such as /pages/7; got ${JSON.stringify(email.path)}`);
226
+ await need(on, kind, id, MANAGE);
227
+ const { userId } = await me();
228
+ // xmax is 0 only for a row this statement inserted, so a changed grant sends no second email.
229
+ const [row] = await missingAsNotFound(rows(on, sql `
230
+ insert into access_grant (item_kind, item_id, user_id, team_id, organization_id, email, level, granted_by)
231
+ values (${kind}, ${id}, ${g.user}::uuid, ${g.team}::uuid, ${g.organization}::uuid, ${g.email}, ${n}, ${userId}::uuid)
232
+ on conflict on constraint access_grant_once do update set level = excluded.level, granted_by = excluded.granted_by, updated_at = now()
233
+ returning xmax = 0 as inserted,
234
+ coalesce(email, (select u.email from "user" u where u.id = access_grant.user_id)) as recipient,
235
+ (select u.name from "user" u where u.id = ${userId}::uuid) as shared_by`));
236
+ if (!notify || email === false || !row?.inserted || !row.recipient)
237
+ return;
238
+ await notify({ to: row.recipient, sharedBy: row.shared_by, level, path: email.path ?? "/", subject: email.subject, text: email.text });
239
+ },
240
+ async revoke(kind, id, to) {
241
+ const g = granteeColumns(to);
242
+ await need(on, kind, id, MANAGE);
243
+ await on.execute(sql `
244
+ delete from access_grant where item_kind = ${kind} and item_id = ${id}
245
+ and user_id is not distinct from ${g.user}::uuid and team_id is not distinct from ${g.team}::uuid
246
+ and organization_id is not distinct from ${g.organization}::uuid and email is not distinct from ${g.email}`);
247
+ },
248
+ async grants(kind, id) {
249
+ await need(on, kind, id, MANAGE);
250
+ const found = await rows(on, sql `select user_id, team_id, organization_id, email, level, granted_by from access_grant where item_kind = ${kind} and item_id = ${id} order by created_at, id`);
251
+ return found.map((row) => ({ to: grantee(row), level: named(Number(row.level)), grantedBy: row.granted_by }));
252
+ },
253
+ async sharedWithMe(kind) {
254
+ checkKind(kind);
255
+ const who = await me();
256
+ if (!who.userId)
257
+ return [];
258
+ const found = await rows(on, sql `select distinct g.item_id from access_grant g
259
+ where g.item_kind = ${kind}
260
+ and (g.user_id = ${who.userId}::uuid
261
+ or g.team_id in (select team_id from team_member where user_id = ${who.userId}::uuid)
262
+ or g.organization_id in (select organization_id from member where user_id = ${who.userId}::uuid)
263
+ or g.email = (select lower(email) from "user" where id = ${who.userId}::uuid and email_verified))
264
+ and ${levelSql(sql `g.item_kind`, sql `g.item_id`, who)} >= ${READ}
265
+ order by g.item_id`);
266
+ return found.map((row) => row.item_id);
267
+ },
268
+ async link(kind, id, level, options = {}) {
269
+ const n = rank(level);
270
+ if (n > WRITE)
271
+ throw new Error(`a link can carry read, create or write, never ${level}`);
272
+ await need(on, kind, id, MANAGE);
273
+ const token = newToken();
274
+ const { userId } = await me();
275
+ const [row] = await rows(on, sql `insert into access_link (item_kind, item_id, token_hash, level, expires_at, created_by)
276
+ values (${kind}, ${id}, ${await hashToken(token)}, ${n}, ${options.expires ?? null}, ${userId}::uuid) returning id`);
277
+ return { id: row.id, token };
278
+ },
279
+ async links(kind, id) {
280
+ await need(on, kind, id, MANAGE);
281
+ const found = await rows(on, sql `select id, level, expires_at from access_link where item_kind = ${kind} and item_id = ${id} order by created_at, id`);
282
+ return found.map((row) => ({ id: row.id, level: named(Number(row.level)), expiresAt: row.expires_at ? new Date(row.expires_at) : null }));
283
+ },
284
+ async revokeLink(kind, id, linkId) {
285
+ await need(on, kind, id, MANAGE);
286
+ await on.execute(sql `delete from access_link where item_kind = ${kind} and item_id = ${id} and id::text = ${linkId}`);
287
+ },
288
+ async accessible(kind, level, column) {
289
+ checkKind(kind);
290
+ return sql `(${levelSql(sql `${kind}`, sql `(${column})::text`, await me())} >= ${rank(level)})`;
291
+ },
292
+ in: (tx) => bound(tx),
293
+ };
294
+ }
295
+ return bound(db);
296
+ }
297
+ // The chain above an item, the item included, as `up`, with `depth` the topmost step reached.
298
+ function chainUp(kind, id, limit) {
299
+ return sql `with recursive up as (
300
+ select kind, id, parent_kind, parent_id, 0 as depth from access_item where kind = ${kind} and id = ${id}
301
+ union all
302
+ select p.kind, p.id, p.parent_kind, p.parent_id, u.depth + 1 from up u join access_item p on p.kind = u.parent_kind and p.id = u.parent_id
303
+ where u.depth < ${limit}
304
+ )
305
+ select (select max(depth) from up) as depth`;
306
+ }
307
+ //# sourceMappingURL=access.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access.js","sourceRoot":"","sources":["../../src/runtime/access.ts"],"names":[],"mappings":"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"}
@@ -15,6 +15,8 @@ type OutputOf<E> = E extends {
15
15
  output: infer O extends z.ZodType;
16
16
  } ? Jsonified<z.output<O>> : never;
17
17
  export type Call<App> = <Name extends keyof EndpointsOf<App> & string>(name: Name, input: InputOf<EndpointsOf<App>[Name]>) => Promise<OutputOf<EndpointsOf<App>[Name]>>;
18
+ export declare function linkFromPage(): string | null;
18
19
  export declare function createCall<App>(routes: Record<string, readonly [Method, string]>, options?: {
19
20
  url?: string;
21
+ link?: () => string | null;
20
22
  }): Call<App>;
@@ -2,15 +2,30 @@ import { createORPCClient, ORPCError } from "@orpc/client";
2
2
  import { oc } from "@orpc/contract";
3
3
  import { OpenAPILink } from "@orpc/openapi-client/fetch";
4
4
  import { EndpointError } from "./endpoint-error.js";
5
+ import { LINK_HEADER } from "./link-header.js";
5
6
  export { EndpointError };
7
+ // The access link token in the page's address, written `#link=<token>`; a fragment never reaches
8
+ // the server, so tokens stay out of its logs and Referer headers.
9
+ export function linkFromPage() {
10
+ if (typeof location === "undefined")
11
+ return null;
12
+ return new URLSearchParams(location.hash.replace(/^#/, "")).get("link");
13
+ }
6
14
  // Calls the app's endpoints by name over its REST API; the generated .platform/client.ts passes
7
- // each endpoint's method and path, and the app's own type for the inputs and outputs.
15
+ // each endpoint's method and path, and the app's own type for the inputs and outputs. Every call
16
+ // carries the page's access link token, or the one `link` returns.
8
17
  export function createCall(routes, options = {}) {
9
18
  const contract = Object.fromEntries(Object.entries(routes).map(([name, [method, path]]) => [name, oc.route({ method, path: path })]));
10
19
  const url = options.url ?? (typeof location === "undefined" ? undefined : location.origin);
11
20
  if (!url)
12
21
  throw new Error("createCall needs a url outside a browser");
13
- const client = createORPCClient(new OpenAPILink(contract, { url }));
22
+ const client = createORPCClient(new OpenAPILink(contract, {
23
+ url,
24
+ headers: () => {
25
+ const token = (options.link ?? linkFromPage)();
26
+ return token ? { [LINK_HEADER]: token } : {};
27
+ },
28
+ }));
14
29
  return (async (name, input) => {
15
30
  const fn = routes[name] && client[name];
16
31
  if (!fn)
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/runtime/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAEzD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,EAAE,aAAa,EAAE,CAAC;AAezB,gGAAgG;AAChG,sFAAsF;AACtF,MAAM,UAAU,UAAU,CAAM,MAAiD,EAAE,OAAO,GAAqB,EAAE;IAC/G,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,IAAoB,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACtJ,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,QAAQ,KAAK,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC3F,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;IACtE,MAAM,MAAM,GAAG,gBAAgB,CAAuD,IAAI,WAAW,CAAC,QAAQ,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;IAE1H,OAAO,CAAC,KAAK,EAAE,IAAY,EAAE,KAAc,EAAE,EAAE;QAC7C,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,IAAI,EAAE,CAAC,CAAC;QAC1D,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC;QACzB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,SAAS;gBAAE,MAAM,IAAI,aAAa,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;YACrF,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC,CAAc,CAAC;AAClB,CAAC"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/runtime/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAEzD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,OAAO,EAAE,aAAa,EAAE,CAAC;AAezB,iGAAiG;AACjG,kEAAkE;AAClE,MAAM,UAAU,YAAY;IAC1B,IAAI,OAAO,QAAQ,KAAK,WAAW;QAAE,OAAO,IAAI,CAAC;IACjD,OAAO,IAAI,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AAC1E,CAAC;AAED,gGAAgG;AAChG,iGAAiG;AACjG,mEAAmE;AACnE,MAAM,UAAU,UAAU,CAAM,MAAiD,EAAE,OAAO,GAAiD,EAAE;IAC3I,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,IAAoB,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACtJ,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,QAAQ,KAAK,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC3F,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;IACtE,MAAM,MAAM,GAAG,gBAAgB,CAAuD,IAAI,WAAW,CAAC,QAAQ,EAAE;QAC5G,GAAG;QACH,OAAO,EAAE,GAA2B,EAAE;YACpC,MAAM,KAAK,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,YAAY,CAAC,EAAE,CAAC;YAC/C,OAAO,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/C,CAAC;KACF,CAAC,CAAC,CAAC;IAEN,OAAO,CAAC,KAAK,EAAE,IAAY,EAAE,KAAc,EAAE,EAAE;QAC7C,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,IAAI,EAAE,CAAC,CAAC;QAC1D,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC;QACzB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,SAAS;gBAAE,MAAM,IAAI,aAAa,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;YACrF,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC,CAAc,CAAC;AAClB,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import type { z } from "zod";
2
+ import type { Level } from "./access.js";
2
3
  import type { AppContext } from "./index.js";
3
4
  export type AnyApp = AppContext<any, any>;
4
5
  export type Method = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
@@ -10,11 +11,17 @@ export interface EndpointDefinition<Input extends z.ZodType, Output extends z.Zo
10
11
  description?: string;
11
12
  input: Input;
12
13
  output: Output;
14
+ access?: {
15
+ kind: string;
16
+ id: string;
17
+ level: Level;
18
+ };
13
19
  }
14
20
  export interface Endpoint<Input extends z.ZodType = z.ZodType, Output extends z.ZodType = z.ZodType, App = AnyApp> extends EndpointDefinition<Input, Output> {
15
21
  handler: (input: z.output<Input>, app: App) => z.input<Output> | Promise<z.input<Output>>;
16
22
  }
17
23
  export declare function endpoint<Input extends z.ZodType, Output extends z.ZodType, App = AnyApp>(definition: EndpointDefinition<Input, Output>, handler: (input: z.output<Input>, app: App) => z.input<Output> | Promise<z.input<Output>>): Endpoint<Input, Output, App>;
24
+ export declare function runEndpoint<App>(e: Endpoint<z.ZodType, z.ZodType, App>, input: unknown, app: App): Promise<unknown>;
18
25
  export type Endpoints = Record<string, Endpoint<z.ZodType, z.ZodType, never>>;
19
26
  export interface Route {
20
27
  method: Method;
@@ -4,8 +4,21 @@ import { experimental_ZodSmartCoercionPlugin } from "@orpc/zod/zod4";
4
4
  import { EndpointError } from "./endpoint-error.js";
5
5
  // One typed function: its Zod schemas are its only types, wherever it is served.
6
6
  export function endpoint(definition, handler) {
7
+ if (definition.access) {
8
+ const shape = definition.input.shape;
9
+ if (!shape || !(definition.access.id in shape))
10
+ throw new Error(`an endpoint's access names input field ${definition.access.id}, which its input schema does not have`);
11
+ }
7
12
  return { ...definition, handler };
8
13
  }
14
+ // Runs one endpoint's function for either door, after its access check.
15
+ export async function runEndpoint(e, input, app) {
16
+ if (e.access) {
17
+ const { kind, id, level } = e.access;
18
+ await app.access.require(kind, String(input[id]), level);
19
+ }
20
+ return e.handler(input, app);
21
+ }
9
22
  export const servesApi = (e) => (e.serve ?? ["api"]).includes("api");
10
23
  // The method and path of every endpoint served over the API, checked once: each must say both,
11
24
  // stay under /api/, and use only input fields as path parameters.
@@ -43,7 +56,7 @@ export function apiHandler(endpoints) {
43
56
  .output(e.output)
44
57
  .handler(async ({ input, context }) => {
45
58
  try {
46
- return await e.handler(input, context.app);
59
+ return await runEndpoint(e, input, context.app);
47
60
  }
48
61
  catch (error) {
49
62
  if (error instanceof EndpointError)
@@ -1 +1 @@
1
- {"version":3,"file":"endpoints.js","sourceRoot":"","sources":["../../src/runtime/endpoints.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"endpoints.js","sourceRoot":"","sources":["../../src/runtime/endpoints.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { type NodePgDatabase } from "drizzle-orm/node-postgres";
2
2
  import { Pool } from "pg";
3
+ import { type Access } from "./access.js";
3
4
  import { type Auth } from "./auth.js";
4
5
  import { type Email, type EmailBindings } from "./email.js";
5
6
  import { type Endpoint } from "./endpoints.js";
@@ -8,6 +9,7 @@ import { type RateLimiter } from "./guard.js";
8
9
  import type { z } from "zod";
9
10
  import { type Live, type LiveNamespace } from "./live.js";
10
11
  import type { LivePeer } from "./live-client.js";
12
+ export { LEVELS, LINK_HEADER, MAX_DEPTH, type Access, type Grant, type Grantee, type Level, type Link, type ShareEmail } from "./access.js";
11
13
  export type { Auth } from "./auth.js";
12
14
  export { EndpointError } from "./endpoint-error.js";
13
15
  export { endpoint, type Door, type Endpoint, type EndpointDefinition, type Method } from "./endpoints.js";
@@ -27,6 +29,7 @@ export interface PlatformBindings extends EmailBindings, FilesBindings {
27
29
  LIVE?: LiveNamespace;
28
30
  LIVE_ROOMS?: string;
29
31
  MCP?: string;
32
+ ACCESS?: string;
30
33
  }
31
34
  export interface ExecutionContext {
32
35
  waitUntil(promise: Promise<unknown>): void;
@@ -40,6 +43,7 @@ export interface AppContext<Env, Schema extends Record<string, unknown>> {
40
43
  readonly files: Files;
41
44
  readonly auth: Auth;
42
45
  readonly live: Live;
46
+ readonly access: Access;
43
47
  session(): Promise<Session | null>;
44
48
  requireUser(): Promise<User>;
45
49
  waitUntil(promise: Promise<unknown>): void;
@@ -1,5 +1,6 @@
1
1
  import { drizzle } from "drizzle-orm/node-postgres";
2
2
  import { Pool } from "pg";
3
+ import { createAccess, LINK_HEADER, shareMessage } from "./access.js";
3
4
  import { AUTH_PATH, createAuth } from "./auth.js";
4
5
  import { createEmail } from "./email.js";
5
6
  import { apiHandler } from "./endpoints.js";
@@ -8,6 +9,7 @@ import { isCrossSiteForm, needsSession, rateLimited } from "./guard.js";
8
9
  import { anyMessage, CLIENT_PATH, clientScript, createLive, joinRoom, LIVE_PATH, liveRoom } from "./live.js";
9
10
  import { MCP_PATH, mcpChallenge, mcpUserId, serveMcp, WELL_KNOWN } from "./mcp.js";
10
11
  import { isScreen, renderScreen } from "./screens.js";
12
+ export { LEVELS, LINK_HEADER, MAX_DEPTH } from "./access.js";
11
13
  export { EndpointError } from "./endpoint-error.js";
12
14
  export { endpoint } from "./endpoints.js";
13
15
  function messageCheck(schema) {
@@ -96,6 +98,7 @@ knownSession) {
96
98
  let files;
97
99
  let auth;
98
100
  let live;
101
+ let access;
99
102
  let session;
100
103
  const app = {
101
104
  env,
@@ -124,6 +127,14 @@ knownSession) {
124
127
  get live() {
125
128
  return (live ??= createLive(env.LIVE, env.LIVE_ROOMS === "on", messageCheck(options.live?.message)));
126
129
  },
130
+ get access() {
131
+ if (env.ACCESS !== "on")
132
+ throw new Error("this app has no access; set `access: true` in platform.jsonc");
133
+ const origin = env.APP_URL ?? new URL(request.url).origin;
134
+ return (access ??= createAccess(app.db, async () => ({ userId: (await app.session())?.user.id ?? null, link: request.headers.get(LINK_HEADER) }),
135
+ // Sent after the answer, so a slow or failing email never fails the share itself.
136
+ async (notice) => app.waitUntil(app.email.send(shareMessage(notice, env.APP_NAME ?? new URL(origin).hostname, origin)).catch((error) => console.error("sharing email failed", error)))));
137
+ },
127
138
  session() {
128
139
  return (session ??= knownSession ? knownSession() : env.BETTER_AUTH_SECRET ? app.auth.api.getSession({ headers: request.headers }) : Promise.resolve(null));
129
140
  },