@celsoft/platform 0.16.0 → 0.17.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.
@@ -1,15 +1,17 @@
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
+ import { applyStaticPoints, createAccess, LINK_HEADER, shareMessage } from "./access.js";
4
4
  import { AUTH_PATH, createAuth } from "./auth.js";
5
5
  import { createEmail } from "./email.js";
6
- import { apiHandler } from "./endpoints.js";
6
+ import { apiHandler, undeclaredAccess } from "./endpoints.js";
7
7
  import { createFiles } from "./files.js";
8
8
  import { isCrossSiteForm, needsSession, rateLimited } from "./guard.js";
9
9
  import { anyMessage, CLIENT_PATH, clientScript, createLive, joinRoom, LIVE_PATH, liveRoom } from "./live.js";
10
10
  import { MCP_PATH, mcpChallenge, mcpUserId, serveMcp, WELL_KNOWN } from "./mcp.js";
11
+ import { createOrganizations, PLATFORM_API, serveOrganizations } from "./organizations.js";
12
+ import { EndpointError } from "./endpoint-error.js";
11
13
  import { isScreen, renderScreen } from "./screens.js";
12
- export { LEVELS, LINK_HEADER, MAX_DEPTH } from "./access.js";
14
+ export { LEVELS, LINK_HEADER, MAX_DEPTH, organizationPoint, teamPoint } from "./access.js";
13
15
  export { EndpointError } from "./endpoint-error.js";
14
16
  export { endpoint } from "./endpoints.js";
15
17
  function messageCheck(schema) {
@@ -32,12 +34,19 @@ export function defineApp(handler, options = {}) {
32
34
  const check = messageCheck(options.live?.message);
33
35
  const handle = handler ?? notFound;
34
36
  const api = apiHandler(options.endpoints ?? {});
37
+ const once = {};
38
+ const undeclared = undeclaredAccess(options.endpoints ?? {});
35
39
  return {
36
40
  // Read by the package to generate the browser client's routes; the app's own types come from here.
37
41
  endpoints: (options.endpoints ?? {}),
38
42
  async fetch(request, env, ctx) {
39
43
  const url = new URL(request.url);
40
- const { app, close } = createContext(request, env, ctx, options);
44
+ // With access on, every endpoint says what it checks, so a forgotten check never ships open.
45
+ if (env.ACCESS === "on" && undeclared.length > 0) {
46
+ console.error(`endpoints ${undeclared.join(", ")} declare no access; add access: { point, level } or access: "none"`);
47
+ return json({ error: "this app is misconfigured; see its logs" }, 500);
48
+ }
49
+ const { app, close } = createContext(request, env, ctx, options, once);
41
50
  try {
42
51
  if (isCrossSiteForm(request, url))
43
52
  return json({ error: "cross-site form submissions are refused" }, 403);
@@ -45,7 +54,7 @@ export function defineApp(handler, options = {}) {
45
54
  if (live)
46
55
  return live;
47
56
  if (env.MCP === "on" && env.BETTER_AUTH_SECRET) {
48
- const answered = await mcpRoute(request, url, env, ctx, app, options);
57
+ const answered = await mcpRoute(request, url, env, ctx, app, options, once);
49
58
  if (answered)
50
59
  return answered;
51
60
  }
@@ -62,6 +71,9 @@ export function defineApp(handler, options = {}) {
62
71
  catch (thrown) {
63
72
  if (thrown instanceof Response)
64
73
  return thrown;
74
+ // A refusal from app.access or app.organizations in a hand-written route answers like an endpoint's.
75
+ if (thrown instanceof EndpointError)
76
+ return json({ error: thrown.message }, thrown.status);
65
77
  throw thrown;
66
78
  }
67
79
  finally {
@@ -76,7 +88,7 @@ export function defineApp(handler, options = {}) {
76
88
  const env = rawEnv;
77
89
  return async (room, message, from) => {
78
90
  const pending = [];
79
- const { app, close } = createContext(new Request(env.APP_URL ?? "https://app.invalid/"), env, { waitUntil: (p) => void pending.push(p) }, options);
91
+ const { app, close } = createContext(new Request(env.APP_URL ?? "https://app.invalid/"), env, { waitUntil: (p) => void pending.push(p) }, options, once);
80
92
  try {
81
93
  await onMessage(room, message, from, app);
82
94
  await Promise.allSettled(pending);
@@ -88,7 +100,7 @@ export function defineApp(handler, options = {}) {
88
100
  }, check),
89
101
  };
90
102
  }
91
- function createContext(request, env, ctx, options,
103
+ function createContext(request, env, ctx, options, once,
92
104
  // Set when the caller is already known, as for an MCP client signed in with a token.
93
105
  knownSession) {
94
106
  const openPool = options.pool ?? defaultPool;
@@ -99,6 +111,7 @@ knownSession) {
99
111
  let auth;
100
112
  let live;
101
113
  let access;
114
+ let organizations;
102
115
  let session;
103
116
  const app = {
104
117
  env,
@@ -131,9 +144,29 @@ knownSession) {
131
144
  if (env.ACCESS !== "on")
132
145
  throw new Error("this app has no access; set `access: true` in platform.jsonc");
133
146
  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)))));
147
+ const db = app.db;
148
+ return (access ??= createAccess(db, async () => ({ userId: (await app.session())?.user.id ?? null, link: request.headers.get(LINK_HEADER) }), {
149
+ // Sent after the answer, so a slow or failing email never fails the grant itself.
150
+ notify: 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))),
151
+ // Once per Worker instance; a failure is retried by the next request.
152
+ ready: () => (once.staticPoints ??= applyStaticPoints(db, options.access?.points ?? {}).catch((error) => {
153
+ once.staticPoints = undefined;
154
+ throw error;
155
+ })),
156
+ }));
157
+ },
158
+ get organizations() {
159
+ if (!env.BETTER_AUTH_SECRET)
160
+ throw new Error("this app has no auth; set `auth: true` in platform.jsonc");
161
+ const origin = env.APP_URL ?? new URL(request.url).origin;
162
+ const appName = env.APP_NAME ?? new URL(origin).hostname;
163
+ return (organizations ??= createOrganizations(app.db, async () => ({ userId: (await app.session())?.user.id ?? null, link: null }), async (notice) => app.waitUntil(app.email
164
+ .send({
165
+ to: notice.to,
166
+ subject: `Join ${notice.organization} on ${appName}`,
167
+ text: `${notice.invitedBy ?? "Someone"} invited you to join ${notice.organization}:\n\n${new URL(`/login?invitation=${notice.invitationId}`, origin)}\n`,
168
+ })
169
+ .catch((error) => console.error("invitation email failed", error)))));
137
170
  },
138
171
  session() {
139
172
  return (session ??= knownSession ? knownSession() : env.BETTER_AUTH_SECRET ? app.auth.api.getSession({ headers: request.headers }) : Promise.resolve(null));
@@ -152,7 +185,7 @@ knownSession) {
152
185
  const MCP_AUTH_PATHS = ["/oauth2/", "/admin/oauth2/", "/jwks", "/.well-known/"];
153
186
  // MCP for AI clients: OAuth discovery under /.well-known/, and /mcp, where each call runs as the
154
187
  // user its token was issued to.
155
- async function mcpRoute(request, url, env, ctx, app, options) {
188
+ async function mcpRoute(request, url, env, ctx, app, options, once) {
156
189
  if (url.pathname.startsWith(WELL_KNOWN))
157
190
  return app.auth.handler(request);
158
191
  if (url.pathname !== MCP_PATH)
@@ -165,7 +198,7 @@ async function mcpRoute(request, url, env, ctx, app, options) {
165
198
  const user = await (await app.auth.$context).internalAdapter.findUserById(userId);
166
199
  return user ? { user, session: { id: "mcp", userId, token: "", expiresAt: new Date(Date.now() + 60_000), createdAt: new Date(), updatedAt: new Date() } } : null;
167
200
  };
168
- const { app: asUser, close } = createContext(request, env, ctx, options, known);
201
+ const { app: asUser, close } = createContext(request, env, ctx, options, once, known);
169
202
  try {
170
203
  if (!(await asUser.session()))
171
204
  return mcpChallenge(origin);
@@ -198,8 +231,12 @@ async function platformRoute(request, url, env, app, options) {
198
231
  return renderScreen(path, env.APP_NAME ?? url.hostname, options.screens?.stylesheet);
199
232
  if (await rateLimited(request, path, env.AUTH_LIMIT))
200
233
  return json({ error: "too many attempts; wait a minute" }, 429);
201
- if (path.startsWith(`${AUTH_PATH}/organization/`) && env.AUTH_ORGANIZATIONS !== "on")
202
- return json({ error: "not found" }, 404);
234
+ if (path.startsWith(PLATFORM_API)) {
235
+ if (env.AUTH_ORGANIZATIONS !== "on")
236
+ return json({ error: "not found" }, 404);
237
+ await app.requireUser();
238
+ return serveOrganizations(request, path, app.organizations);
239
+ }
203
240
  if (env.MCP !== "on" && MCP_AUTH_PATHS.some((p) => path.startsWith(`${AUTH_PATH}${p}`)))
204
241
  return json({ error: "not found" }, 404);
205
242
  if (path.startsWith(`${AUTH_PATH}/`))
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,77 @@
1
+ import type { NodePgDatabase } from "drizzle-orm/node-postgres";
2
+ import { type Caller } from "./access.js";
3
+ export declare const INVITATION_DAYS = 7;
4
+ export declare const ADMINS = "Admins";
5
+ export interface Organization {
6
+ id: string;
7
+ name: string;
8
+ slug: string;
9
+ }
10
+ export interface Member {
11
+ userId: string;
12
+ name: string;
13
+ email: string;
14
+ admin: boolean;
15
+ }
16
+ export interface Team {
17
+ id: string;
18
+ name: string;
19
+ admins: boolean;
20
+ }
21
+ export interface Invitation {
22
+ id: string;
23
+ email: string;
24
+ teamId: string | null;
25
+ expiresAt: Date;
26
+ }
27
+ export interface MyInvitation {
28
+ id: string;
29
+ organizationId: string;
30
+ organization: string;
31
+ teamId: string | null;
32
+ team: string | null;
33
+ invitedBy: string | null;
34
+ expiresAt: Date;
35
+ }
36
+ export interface InvitationNotice {
37
+ to: string;
38
+ organization: string;
39
+ invitedBy: string | null;
40
+ invitationId: string;
41
+ }
42
+ export interface Organizations {
43
+ create(input: {
44
+ name: string;
45
+ slug?: string;
46
+ }): Promise<Organization>;
47
+ list(): Promise<Organization[]>;
48
+ get(id: string): Promise<Organization>;
49
+ rename(id: string, name: string): Promise<void>;
50
+ remove(id: string): Promise<void>;
51
+ requireMember(id: string): Promise<void>;
52
+ members(id: string): Promise<Member[]>;
53
+ removeMember(id: string, userId: string): Promise<void>;
54
+ invite(id: string, email: string, options?: {
55
+ team?: string;
56
+ }): Promise<Invitation>;
57
+ invitations(id: string): Promise<Invitation[]>;
58
+ revokeInvitation(id: string, invitationId: string): Promise<void>;
59
+ accept(invitationId: string): Promise<string>;
60
+ myInvitations(): Promise<MyInvitation[]>;
61
+ decline(invitationId: string): Promise<void>;
62
+ teams(id: string): Promise<Team[]>;
63
+ team(teamId: string): Promise<Team & {
64
+ organizationId: string;
65
+ }>;
66
+ createTeam(id: string, name: string): Promise<Team>;
67
+ renameTeam(teamId: string, name: string): Promise<void>;
68
+ removeTeam(teamId: string): Promise<void>;
69
+ teamMembers(teamId: string): Promise<Member[]>;
70
+ addToTeam(teamId: string, userId: string): Promise<void>;
71
+ removeFromTeam(teamId: string, userId: string): Promise<void>;
72
+ }
73
+ type Db = Pick<NodePgDatabase<Record<string, unknown>>, "execute" | "transaction">;
74
+ export declare function createOrganizations(db: Db, caller: () => Promise<Caller>, notify?: (notice: InvitationNotice) => Promise<void>): Organizations;
75
+ export declare const PLATFORM_API = "/api/platform/";
76
+ export declare function serveOrganizations(request: Request, path: string, orgs: Organizations): Promise<Response>;
77
+ export {};
@@ -0,0 +1,326 @@
1
+ import { sql } from "drizzle-orm";
2
+ import { createAccess, organizationPoint, teamPoint } from "./access.js";
3
+ import { EndpointError } from "./endpoint-error.js";
4
+ // The platform's own organizations, teams, members and invitations; see docs/design.md, Auth
5
+ // behavior, Organizations. Members carry no roles: who may do what is decided by control points,
6
+ // the organization's point for the organization and each team's point for the team, and the
7
+ // built-in Admins team gets `manage` on everything under its organization.
8
+ export const INVITATION_DAYS = 7;
9
+ export const ADMINS = "Admins";
10
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
11
+ const notFound = () => new EndpointError(404, "not found");
12
+ const conflict = (message) => new EndpointError(409, message);
13
+ const invalid = (message) => new EndpointError(400, message);
14
+ const rows = async (db, query) => (await db.execute(query)).rows;
15
+ // Ids come from URLs and bodies; anything that is not a UUID names nothing.
16
+ function uuid(value) {
17
+ if (typeof value !== "string" || !UUID.test(value))
18
+ throw notFound();
19
+ return value.toLowerCase();
20
+ }
21
+ function name(value) {
22
+ if (typeof value !== "string" || value.trim().length === 0 || value.trim().length > 100)
23
+ throw invalid("a name must be 1 to 100 characters");
24
+ return value.trim();
25
+ }
26
+ function slugOf(value, fallback) {
27
+ const slug = typeof value === "string" ? value : `${fallback.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40) || "org"}-${crypto.randomUUID().slice(0, 8)}`;
28
+ if (!/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/.test(slug))
29
+ throw invalid("a slug is 1 to 64 lowercase letters, digits and dashes, not starting or ending with a dash");
30
+ return slug;
31
+ }
32
+ function email(value) {
33
+ if (typeof value !== "string" || value.length > 254 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim()))
34
+ throw invalid("an email address is needed");
35
+ return value.trim().toLowerCase();
36
+ }
37
+ export function createOrganizations(db, caller, notify) {
38
+ const access = createAccess(db, caller);
39
+ async function me() {
40
+ const { userId } = await caller();
41
+ if (!userId || !UUID.test(userId))
42
+ throw new EndpointError(401, "sign in first");
43
+ return userId;
44
+ }
45
+ // The team's organization, when the caller can see the team at all.
46
+ async function teamOrganization(on, teamId) {
47
+ const [team] = await rows(on, sql `select organization_id, admins from team where id = ${teamId}::uuid`);
48
+ if (!team)
49
+ throw notFound();
50
+ return { organizationId: team.organization_id, admins: team.admins };
51
+ }
52
+ // Refuses a change that would leave an organization with no admin.
53
+ async function keepAnAdmin(on, organizationId, userId) {
54
+ const [row] = await rows(on, sql `select exists (select 1 from team_member tm join team t on t.id = tm.team_id where t.organization_id = ${organizationId}::uuid and t.admins and tm.user_id = ${userId}::uuid) as admin,
55
+ (select count(*) from team_member tm join team t on t.id = tm.team_id join member m on m.user_id = tm.user_id and m.organization_id = t.organization_id
56
+ where t.organization_id = ${organizationId}::uuid and t.admins)::int as admins`);
57
+ if (row?.admin && Number(row.admins) <= 1)
58
+ throw conflict("an organization needs at least one admin; add another first");
59
+ }
60
+ async function makeTeam(tx, organizationId, teamName, admins, creator) {
61
+ const [team] = await rows(tx, sql `insert into team (name, organization_id, admins) values (${teamName}, ${organizationId}::uuid, ${admins}) returning id`);
62
+ await tx.execute(sql `
63
+ insert into access_point (id, parent_id, organization_id, for_team, created_by)
64
+ values (${teamPoint(team.id)}, ${organizationPoint(organizationId)}, ${organizationId}::uuid, ${team.id}::uuid, ${creator}::uuid)`);
65
+ await tx.execute(sql `
66
+ insert into access_grant (point_id, team_id, user_id, level, granted_by)
67
+ values (${teamPoint(team.id)}, ${team.id}::uuid, null, 3, ${creator}::uuid), (${teamPoint(team.id)}, null, ${creator}::uuid, 5, ${creator}::uuid)`);
68
+ return { id: team.id, name: teamName, admins };
69
+ }
70
+ const listMembers = (on, where) => rows(on, sql `select u.id as user_id, u.name, u.email,
71
+ exists (select 1 from team_member tm join team t on t.id = tm.team_id where t.organization_id = m.organization_id and t.admins and tm.user_id = u.id) as admin
72
+ from member m join "user" u on u.id = m.user_id where ${where} order by u.name, u.id`).then((found) => found.map((r) => ({ userId: r.user_id, name: r.name, email: r.email, admin: Boolean(r.admin) })));
73
+ return {
74
+ async create(input) {
75
+ const creator = await me();
76
+ const orgName = name(input?.name);
77
+ const slug = slugOf(input?.slug, orgName);
78
+ return db.transaction(async (raw) => {
79
+ const tx = raw;
80
+ let org;
81
+ try {
82
+ [org] = await rows(tx, sql `insert into organization (name, slug) values (${orgName}, ${slug}) returning id`);
83
+ }
84
+ catch (error) {
85
+ if (error.code === "23505" || error.cause?.code === "23505")
86
+ throw conflict("that slug is taken");
87
+ throw error;
88
+ }
89
+ const id = org.id;
90
+ await tx.execute(sql `insert into member (organization_id, user_id) values (${id}::uuid, ${creator}::uuid)`);
91
+ await tx.execute(sql `
92
+ insert into access_point (id, organization_id, for_organization, created_by) values (${organizationPoint(id)}, ${id}::uuid, ${id}::uuid, ${creator}::uuid)`);
93
+ await tx.execute(sql `
94
+ insert into access_grant (point_id, organization_id, level, granted_by) values (${organizationPoint(id)}, ${id}::uuid, 1, ${creator}::uuid)`);
95
+ await tx.execute(sql `
96
+ insert into access_grant (point_id, user_id, level, granted_by) values (${organizationPoint(id)}, ${creator}::uuid, 5, ${creator}::uuid)`);
97
+ const admins = await makeTeam(tx, id, ADMINS, true, creator);
98
+ await tx.execute(sql `insert into team_member (team_id, user_id) values (${admins.id}::uuid, ${creator}::uuid)`);
99
+ return { id, name: orgName, slug };
100
+ });
101
+ },
102
+ async list() {
103
+ const userId = await me();
104
+ return rows(db, sql `select o.id, o.name, o.slug from organization o join member m on m.organization_id = o.id where m.user_id = ${userId}::uuid order by o.name, o.id`);
105
+ },
106
+ async get(id) {
107
+ await access.check(organizationPoint(uuid(id)));
108
+ const [org] = await rows(db, sql `select id, name, slug from organization where id = ${uuid(id)}::uuid`);
109
+ if (!org)
110
+ throw notFound();
111
+ return org;
112
+ },
113
+ async rename(id, newName) {
114
+ await access.require(organizationPoint(uuid(id)), "write");
115
+ await db.execute(sql `update organization set name = ${name(newName)}, updated_at = now() where id = ${uuid(id)}::uuid`);
116
+ },
117
+ async remove(id) {
118
+ await access.require(organizationPoint(uuid(id)), "delete");
119
+ await db.execute(sql `delete from organization where id = ${uuid(id)}::uuid`);
120
+ },
121
+ async requireMember(id) {
122
+ const userId = await me();
123
+ const [row] = await rows(db, sql `select 1 from member where organization_id = ${uuid(id)}::uuid and user_id = ${userId}::uuid`);
124
+ if (!row)
125
+ throw notFound();
126
+ },
127
+ async members(id) {
128
+ await access.check(organizationPoint(uuid(id)));
129
+ return listMembers(db, sql `m.organization_id = ${uuid(id)}::uuid`);
130
+ },
131
+ async removeMember(id, userId) {
132
+ const orgId = uuid(id);
133
+ const leaving = uuid(userId);
134
+ const self = (await me()) === leaving;
135
+ // Anyone may leave; removing someone else needs `manage` on the organization.
136
+ if (self)
137
+ await access.check(organizationPoint(orgId));
138
+ else
139
+ await access.require(organizationPoint(orgId), "manage");
140
+ await db.transaction(async (raw) => {
141
+ const tx = raw;
142
+ await tx.execute(sql `select pg_advisory_xact_lock(hashtext(${`@celsoft/platform admins ${orgId}`}))`);
143
+ const [row] = await rows(tx, sql `select 1 from member where organization_id = ${orgId}::uuid and user_id = ${leaving}::uuid`);
144
+ if (!row)
145
+ throw notFound();
146
+ await keepAnAdmin(tx, orgId, leaving);
147
+ await tx.execute(sql `delete from team_member where user_id = ${leaving}::uuid and team_id in (select id from team where organization_id = ${orgId}::uuid)`);
148
+ await tx.execute(sql `delete from member where organization_id = ${orgId}::uuid and user_id = ${leaving}::uuid`);
149
+ });
150
+ },
151
+ async invite(id, address, options = {}) {
152
+ const orgId = uuid(id);
153
+ await access.require(organizationPoint(orgId), "manage");
154
+ const to = email(address);
155
+ const teamId = options.team === undefined || options.team === null ? null : uuid(options.team);
156
+ if (teamId && (await teamOrganization(db, teamId)).organizationId !== orgId)
157
+ throw notFound();
158
+ const inviter = await me();
159
+ const [row] = await rows(db, sql `insert into invitation (organization_id, email, team_id, expires_at, inviter_id)
160
+ values (${orgId}::uuid, ${to}, ${teamId}::uuid, now() + make_interval(days => ${INVITATION_DAYS}), ${inviter}::uuid)
161
+ on conflict on constraint invitation_once do update set team_id = excluded.team_id, expires_at = excluded.expires_at, inviter_id = excluded.inviter_id, updated_at = now()
162
+ returning id, expires_at, (select name from "user" where id = ${inviter}::uuid) as inviter, (select name from organization where id = ${orgId}::uuid) as org`);
163
+ await notify?.({ to, organization: row.org, invitedBy: row.inviter, invitationId: row.id });
164
+ return { id: row.id, email: to, teamId, expiresAt: new Date(row.expires_at) };
165
+ },
166
+ async invitations(id) {
167
+ await access.require(organizationPoint(uuid(id)), "manage");
168
+ const found = await rows(db, sql `select id, email, team_id, expires_at from invitation where organization_id = ${uuid(id)}::uuid and expires_at > now() order by created_at, id`);
169
+ return found.map((r) => ({ id: r.id, email: r.email, teamId: r.team_id, expiresAt: new Date(r.expires_at) }));
170
+ },
171
+ async revokeInvitation(id, invitationId) {
172
+ await access.require(organizationPoint(uuid(id)), "manage");
173
+ await db.execute(sql `delete from invitation where id = ${uuid(invitationId)}::uuid and organization_id = ${uuid(id)}::uuid`);
174
+ },
175
+ async accept(invitationId) {
176
+ const userId = await me();
177
+ const invitationKey = uuid(invitationId);
178
+ return db.transaction(async (raw) => {
179
+ const tx = raw;
180
+ const [inv] = await rows(tx, sql `select i.organization_id, i.team_id from invitation i join "user" u on u.id = ${userId}::uuid
181
+ where i.id = ${invitationKey}::uuid and i.expires_at > now() and u.email_verified and lower(u.email) = i.email
182
+ for update of i`);
183
+ if (!inv)
184
+ throw notFound();
185
+ await tx.execute(sql `insert into member (organization_id, user_id) values (${inv.organization_id}::uuid, ${userId}::uuid) on conflict on constraint member_once do nothing`);
186
+ if (inv.team_id)
187
+ await tx.execute(sql `insert into team_member (team_id, user_id) values (${inv.team_id}::uuid, ${userId}::uuid) on conflict on constraint team_member_once do nothing`);
188
+ await tx.execute(sql `delete from invitation where id = ${invitationKey}::uuid`);
189
+ return inv.organization_id;
190
+ });
191
+ },
192
+ async myInvitations() {
193
+ const userId = await me();
194
+ const found = await rows(db, sql `select i.id, i.organization_id, o.name as organization, i.team_id, t.name as team, inviter.name as invited_by, i.expires_at
195
+ from invitation i join "user" u on u.id = ${userId}::uuid and u.email_verified and lower(u.email) = i.email
196
+ join organization o on o.id = i.organization_id
197
+ left join team t on t.id = i.team_id
198
+ left join "user" inviter on inviter.id = i.inviter_id
199
+ where i.expires_at > now() order by i.created_at, i.id`);
200
+ return found.map((r) => ({ id: r.id, organizationId: r.organization_id, organization: r.organization, teamId: r.team_id, team: r.team, invitedBy: r.invited_by, expiresAt: new Date(r.expires_at) }));
201
+ },
202
+ async decline(invitationId) {
203
+ const userId = await me();
204
+ const [row] = await rows(db, sql `delete from invitation i using "user" u where i.id = ${uuid(invitationId)}::uuid and u.id = ${userId}::uuid and u.email_verified and lower(u.email) = i.email returning i.id`);
205
+ if (!row)
206
+ throw notFound();
207
+ },
208
+ async team(teamId) {
209
+ await access.check(teamPoint(uuid(teamId)));
210
+ const [found] = await rows(db, sql `select id, name, admins, organization_id from team where id = ${uuid(teamId)}::uuid`);
211
+ if (!found)
212
+ throw notFound();
213
+ return { id: found.id, name: found.name, admins: found.admins, organizationId: found.organization_id };
214
+ },
215
+ async teams(id) {
216
+ await access.check(organizationPoint(uuid(id)));
217
+ return rows(db, sql `select id, name, admins from team where organization_id = ${uuid(id)}::uuid order by admins desc, name, id`);
218
+ },
219
+ async createTeam(id, teamName) {
220
+ const orgId = uuid(id);
221
+ const n = name(teamName);
222
+ await access.require(organizationPoint(orgId), "create");
223
+ const creator = await me();
224
+ return db.transaction(async (raw) => makeTeam(raw, orgId, n, false, creator));
225
+ },
226
+ async renameTeam(teamId, newName) {
227
+ await access.require(teamPoint(uuid(teamId)), "write");
228
+ await db.execute(sql `update team set name = ${name(newName)}, updated_at = now() where id = ${uuid(teamId)}::uuid`);
229
+ },
230
+ async removeTeam(teamId) {
231
+ await access.require(teamPoint(uuid(teamId)), "delete");
232
+ if ((await teamOrganization(db, uuid(teamId))).admins)
233
+ throw conflict("the Admins team stays as long as its organization");
234
+ await db.execute(sql `delete from team where id = ${uuid(teamId)}::uuid`);
235
+ },
236
+ async teamMembers(teamId) {
237
+ await access.check(teamPoint(uuid(teamId)));
238
+ return listMembers(db, sql `exists (select 1 from team_member tm where tm.team_id = ${uuid(teamId)}::uuid and tm.user_id = m.user_id) and m.organization_id = (select organization_id from team where id = ${uuid(teamId)}::uuid)`);
239
+ },
240
+ async addToTeam(teamId, userId) {
241
+ const team = uuid(teamId);
242
+ const adding = uuid(userId);
243
+ await access.require(teamPoint(team), "manage");
244
+ const { organizationId } = await teamOrganization(db, team);
245
+ const [row] = await rows(db, sql `select 1 from member where organization_id = ${organizationId}::uuid and user_id = ${adding}::uuid`);
246
+ if (!row)
247
+ throw notFound();
248
+ await db.execute(sql `insert into team_member (team_id, user_id) values (${team}::uuid, ${adding}::uuid) on conflict on constraint team_member_once do nothing`);
249
+ },
250
+ async removeFromTeam(teamId, userId) {
251
+ const team = uuid(teamId);
252
+ const leaving = uuid(userId);
253
+ if ((await me()) === leaving)
254
+ await access.check(teamPoint(team));
255
+ else
256
+ await access.require(teamPoint(team), "manage");
257
+ const { organizationId, admins } = await teamOrganization(db, team);
258
+ await db.transaction(async (raw) => {
259
+ const tx = raw;
260
+ await tx.execute(sql `select pg_advisory_xact_lock(hashtext(${`@celsoft/platform admins ${organizationId}`}))`);
261
+ if (admins)
262
+ await keepAnAdmin(tx, organizationId, leaving);
263
+ await tx.execute(sql `delete from team_member where team_id = ${team}::uuid and user_id = ${leaving}::uuid`);
264
+ });
265
+ },
266
+ };
267
+ }
268
+ export const PLATFORM_API = "/api/platform/";
269
+ const ROUTES = [
270
+ ["GET", /^organizations$/, (o) => o.list()],
271
+ ["POST", /^organizations$/, (o, _, b) => o.create({ name: b.name, slug: b.slug })],
272
+ ["GET", /^organizations\/([^/]+)$/, (o, [id]) => o.get(id)],
273
+ ["PATCH", /^organizations\/([^/]+)$/, (o, [id], b) => o.rename(id, b.name)],
274
+ ["DELETE", /^organizations\/([^/]+)$/, (o, [id]) => o.remove(id)],
275
+ ["GET", /^organizations\/([^/]+)\/members$/, (o, [id]) => o.members(id)],
276
+ ["DELETE", /^organizations\/([^/]+)\/members\/([^/]+)$/, (o, [id, user]) => o.removeMember(id, user)],
277
+ ["GET", /^organizations\/([^/]+)\/invitations$/, (o, [id]) => o.invitations(id)],
278
+ ["POST", /^organizations\/([^/]+)\/invitations$/, (o, [id], b) => o.invite(id, b.email, { team: b.team })],
279
+ ["DELETE", /^organizations\/([^/]+)\/invitations\/([^/]+)$/, (o, [id, inv]) => o.revokeInvitation(id, inv)],
280
+ ["GET", /^invitations$/, (o) => o.myInvitations()],
281
+ ["POST", /^invitations\/([^/]+)\/accept$/, async (o, [inv]) => ({ organizationId: await o.accept(inv) })],
282
+ ["DELETE", /^invitations\/([^/]+)$/, (o, [inv]) => o.decline(inv)],
283
+ ["GET", /^organizations\/([^/]+)\/teams$/, (o, [id]) => o.teams(id)],
284
+ ["POST", /^organizations\/([^/]+)\/teams$/, (o, [id], b) => o.createTeam(id, b.name)],
285
+ ["GET", /^teams\/([^/]+)$/, (o, [team]) => o.team(team)],
286
+ ["PATCH", /^teams\/([^/]+)$/, (o, [team], b) => o.renameTeam(team, b.name)],
287
+ ["DELETE", /^teams\/([^/]+)$/, (o, [team]) => o.removeTeam(team)],
288
+ ["GET", /^teams\/([^/]+)\/members$/, (o, [team]) => o.teamMembers(team)],
289
+ ["POST", /^teams\/([^/]+)\/members$/, (o, [team], b) => o.addToTeam(team, b.user)],
290
+ ["DELETE", /^teams\/([^/]+)\/members\/([^/]+)$/, (o, [team, user]) => o.removeFromTeam(team, user)],
291
+ ];
292
+ // The organization routes browsers call, under /api/platform/, answering JSON like every API
293
+ // route; an unknown path or method under the prefix is a 404.
294
+ export async function serveOrganizations(request, path, orgs) {
295
+ const rest = path.slice(PLATFORM_API.length);
296
+ for (const [method, pattern, run] of ROUTES) {
297
+ const match = pattern.exec(rest);
298
+ if (!match || request.method !== method)
299
+ continue;
300
+ let body = {};
301
+ if (method === "POST" || method === "PATCH") {
302
+ const parsed = await request.json().catch(() => undefined);
303
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
304
+ return Response.json({ error: "send a JSON object" }, { status: 400 });
305
+ body = parsed;
306
+ }
307
+ try {
308
+ let params;
309
+ try {
310
+ params = match.slice(1).map(decodeURIComponent);
311
+ }
312
+ catch {
313
+ return Response.json({ error: "not found" }, { status: 404 });
314
+ }
315
+ const result = await run(orgs, params, body);
316
+ return Response.json(result ?? {}, { status: method === "POST" && !rest.endsWith("/accept") ? 201 : 200 });
317
+ }
318
+ catch (error) {
319
+ if (error instanceof EndpointError)
320
+ return Response.json({ error: error.message }, { status: error.status });
321
+ throw error;
322
+ }
323
+ }
324
+ return Response.json({ error: "not found" }, { status: 404 });
325
+ }
326
+ //# sourceMappingURL=organizations.js.map