@snagset/server 0.1.11 → 0.1.12

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.
Files changed (81) hide show
  1. package/dist/app.js +18 -1
  2. package/dist/app.js.map +1 -1
  3. package/dist/db/migrate.js +44 -0
  4. package/dist/db/migrate.js.map +1 -1
  5. package/dist/db/migrations/0009_member_invites.sql +84 -0
  6. package/dist/routes/invites.js +417 -0
  7. package/dist/routes/invites.js.map +1 -0
  8. package/dist/routes/members.js +36 -68
  9. package/dist/routes/members.js.map +1 -1
  10. package/dist/routes/owner.js +5 -0
  11. package/dist/routes/owner.js.map +1 -1
  12. package/package.json +1 -1
  13. package/static/dashboard/assets/CompositeItem-CYCgjQYA.js +1 -0
  14. package/static/dashboard/assets/{ConfirmDialog-WnZ92dk2.js → ConfirmDialog-ChIOOj7I.js} +1 -1
  15. package/static/dashboard/assets/{CopyButton-rL26RLOJ.js → CopyButton-V7OJiMLP.js} +1 -1
  16. package/static/dashboard/assets/{FieldItemContext-BFJSEdB-.js → FieldItemContext-DVtdzu3C.js} +1 -1
  17. package/static/dashboard/assets/{InstallTag-BKIArjPk.js → InstallTag-OD-dW_hC.js} +1 -1
  18. package/static/dashboard/assets/{SaveState-DVHi0zeB.js → SaveState-JVgANqCr.js} +1 -1
  19. package/static/dashboard/assets/{_app-D2r-zfbF.js → _app-B0TJyHg-.js} +1 -1
  20. package/static/dashboard/assets/{_app-iK-weT5A.js → _app-Bwss1pXe.js} +12 -12
  21. package/static/dashboard/assets/activity-CbcgWM3L.js +1 -0
  22. package/static/dashboard/assets/{arrow-left-BEHXEKLy.js → arrow-left-BrmSwEeI.js} +1 -1
  23. package/static/dashboard/assets/{avatar-BnPBt2UW.js → avatar-CwD1_B-n.js} +1 -1
  24. package/static/dashboard/assets/{badge-B2rHdH8b.js → badge-DM_IoUbC.js} +1 -1
  25. package/static/dashboard/assets/{card-BNGTf8dq.js → card-DpmJQuox.js} +1 -1
  26. package/static/dashboard/assets/{check-C3gaSIDM.js → check-DNKwULUa.js} +1 -1
  27. package/static/dashboard/assets/{chevron-right-CLLS6JcX.js → chevron-right-ob3VZye2.js} +1 -1
  28. package/static/dashboard/assets/{copy-Dy7nBeu2.js → copy-CyBHfvzN.js} +1 -1
  29. package/static/dashboard/assets/{dialog-q6J2VL2H.js → dialog-Bys-LJLw.js} +2 -2
  30. package/static/dashboard/assets/{dropdown-menu-DFZrgFpI.js → dropdown-menu-Cqas_wS3.js} +2 -2
  31. package/static/dashboard/assets/{image-off-Bug-3iC-.js → image-off-D_65_17F.js} +1 -1
  32. package/static/dashboard/assets/index-Cxnlb88o.js +169 -0
  33. package/static/dashboard/assets/index-Ddi7bl87.css +1 -0
  34. package/static/dashboard/assets/invite-Cc7l9fdY.js +1 -0
  35. package/static/dashboard/assets/label-CwqSv8L3.js +1 -0
  36. package/static/dashboard/assets/{layout-grid-B3BkFR60.js → layout-grid-PTAyhqU8.js} +1 -1
  37. package/static/dashboard/assets/me-kMczOn0k.js +1 -0
  38. package/static/dashboard/assets/{parts-BTWw4Uv-.js → parts-CErghVAB.js} +1 -1
  39. package/static/dashboard/assets/{plus-DqdyxL8q.js → plus-BE-gOo3v.js} +1 -1
  40. package/static/dashboard/assets/s._site-BBRI74gv.js +1 -0
  41. package/static/dashboard/assets/{s._site.comments-kEGEpFR2.js → s._site.comments-CbfjF9-s.js} +3 -3
  42. package/static/dashboard/assets/{s._site.reviewers-C1LLoSS5.js → s._site.reviewers-BMiqVuRm.js} +1 -1
  43. package/static/dashboard/assets/s._site.settings-CEvRwMQt.js +1 -0
  44. package/static/dashboard/assets/{setup-CdZYVZmd.js → setup-CkNn_rwp.js} +1 -1
  45. package/static/dashboard/assets/{sign-in-CvstpETe.js → sign-in-Bz6iRxcc.js} +1 -1
  46. package/static/dashboard/assets/{sign-up-Cq7YvdYN.js → sign-up-QcstGuaV.js} +1 -1
  47. package/static/dashboard/assets/sites-DJjGT-f2.js +1 -0
  48. package/static/dashboard/assets/studio-mKw0u2SL.js +1 -0
  49. package/static/dashboard/assets/studio-qqUvuQo5.js +1 -0
  50. package/static/dashboard/assets/{studio.index-BTF7YIX-.js → studio.index-zjJeqf8B.js} +1 -1
  51. package/static/dashboard/assets/{studio.integrations-Nr_jBcXh.js → studio.integrations-C9-R6CQZ.js} +1 -1
  52. package/static/dashboard/assets/studio.members-BYd5gcvp.js +6 -0
  53. package/static/dashboard/assets/{sun-s_t6Zx3r.js → sun-DTQiNbTt.js} +1 -1
  54. package/static/dashboard/assets/switch-Cqqz6oQN.js +1 -0
  55. package/static/dashboard/assets/{table-CrWNqpP8.js → table-D_36h1Oq.js} +1 -1
  56. package/static/dashboard/assets/{useAriaLabelledBy-CgL3SaJd.js → useAriaLabelledBy-D-dab2TD.js} +1 -1
  57. package/static/dashboard/assets/{useBaseQuery-CBTINzcX.js → useBaseQuery-DmDP3Pxx.js} +1 -1
  58. package/static/dashboard/assets/useQuery-CKn14AcE.js +1 -0
  59. package/static/dashboard/assets/useRender-B_JrSXOO.js +1 -0
  60. package/static/dashboard/assets/{useValueChanged-B20GM2vK.js → useValueChanged--Isnj6ZH.js} +1 -1
  61. package/static/dashboard/assets/{users-CQwkq_Tk.js → users-CJVqlkra.js} +1 -1
  62. package/static/dashboard/assets/{welcome-W1YxYlGW.js → welcome-BMr4vd5V.js} +2 -2
  63. package/static/dashboard/assets/{you-DXZRdbGS.js → you-Bqw_r3i7.js} +1 -1
  64. package/static/dashboard/assets/{zod-DWEgoEmm.js → zod-BMw0fS-7.js} +1 -1
  65. package/static/dashboard/index.html +2 -2
  66. package/static/widget/snag.dock.js +1 -1
  67. package/static/dashboard/assets/CompositeItem-Bg_GF93m.js +0 -1
  68. package/static/dashboard/assets/activity-CCqTW3Th.js +0 -1
  69. package/static/dashboard/assets/index-ClhJtAFR.css +0 -1
  70. package/static/dashboard/assets/index-CtyJ8ep7.js +0 -169
  71. package/static/dashboard/assets/label-DoQ07Q_y.js +0 -1
  72. package/static/dashboard/assets/me-NFjosbdz.js +0 -1
  73. package/static/dashboard/assets/s._site-kVvU6ucw.js +0 -1
  74. package/static/dashboard/assets/s._site.settings-CeKMuuRo.js +0 -1
  75. package/static/dashboard/assets/sites-CUWMKMCM.js +0 -1
  76. package/static/dashboard/assets/studio-BfA8kzFc.js +0 -1
  77. package/static/dashboard/assets/studio-uAl1i7Nd.js +0 -1
  78. package/static/dashboard/assets/studio.members-BtbPt4iX.js +0 -1
  79. package/static/dashboard/assets/switch-DbqX1mWE.js +0 -1
  80. package/static/dashboard/assets/useQuery-BPuxCfhJ.js +0 -1
  81. package/static/dashboard/assets/useRender-BCpbp3WG.js +0 -1
@@ -0,0 +1,417 @@
1
+ import { Hono } from "hono";
2
+ import { randomUUID, randomBytes, createHash, timingSafeEqual } from "node:crypto";
3
+ import { ensureStudioReviewers } from "../studioReviewers.js";
4
+ import { hashPassword, setSessionCookies, verifyPassword } from "./owner.js";
5
+ import { LimitReached, planLimitReached, usage } from "../plan.js";
6
+ import { openSession, SESSION_S } from "../orgs.js";
7
+ import { inOrg, maintenance } from "../db/scope.js";
8
+ import { isConfigured } from "../mail/index.js";
9
+ /** Seven days, as the request asked. A constant rather than a variable: nobody has asked to tune it, and a setting nobody tunes is a row in 08 §4 for nothing. */
10
+ export const INVITE_TTL_DAYS = 7;
11
+ const DAY_MS = 86_400_000;
12
+ /**
13
+ * The public routes' budget: twenty an hour per address, lookups and accepts
14
+ * together — enough for a person who mistypes a password a few times and
15
+ * reloads the page, nowhere near enough to farm scrypt verifications. In
16
+ * process memory, as `/auth/register`'s ten-an-hour is (orgs.ts says why that
17
+ * is a weaker limit on serverless rather than a missing one).
18
+ */
19
+ const WINDOW_MS = 60 * 60 * 1000;
20
+ const PER_WINDOW = 20;
21
+ const EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
22
+ /** Bare uuids on the wire, as the agent tokens' (mcpTokens.ts): foundations §2.10's prefixes are for ids that travel in webhooks and issue markers, and an invite never leaves the dashboard. */
23
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
24
+ const sha256 = (s) => createHash("sha256").update(s, "utf8").digest();
25
+ function fail(c, status, code, message, details) {
26
+ return c.json({ error: { code, message, ...(details ? { details } : {}), requestId: c.get("requestId") } }, status);
27
+ }
28
+ /** The link. One function, so the dialog and the mail cannot point at different places. */
29
+ export function inviteUrl(base, token) {
30
+ return `${base.replace(/\/+$/, "")}/invite#${token}`;
31
+ }
32
+ /** D85's rule, said the same way members.ts says it. */
33
+ function parseRole(v) {
34
+ if (v !== "owner" && v !== "admin") {
35
+ return v === "member" || v === "viewer"
36
+ ? `"${v}" is not available yet — a read-only seat needs visibility rules that are not built. Use owner or admin.`
37
+ : "role must be owner or admin.";
38
+ }
39
+ return v;
40
+ }
41
+ /** Never the token, never its hash. */
42
+ const shape = (r) => ({
43
+ id: r.id,
44
+ role: r.role,
45
+ email: r.email,
46
+ createdAt: r.created_at.toISOString(),
47
+ expiresAt: r.expires_at.toISOString(),
48
+ createdBy: r.creator_email ? { name: r.creator_name ?? "", email: r.creator_email } : null,
49
+ });
50
+ function gone(r, at) {
51
+ if (r.accepted_at)
52
+ return "invite_used";
53
+ if (r.revoked_at)
54
+ return "invite_revoked";
55
+ if (r.expires_at <= at)
56
+ return "invite_expired";
57
+ return null;
58
+ }
59
+ const SENTENCE = {
60
+ invite_not_found: () => "This invite link is not valid. Check that you copied all of it, or ask for a new one.",
61
+ invite_used: () => "This invite has already been used. If that was you, sign in instead.",
62
+ invite_revoked: () => "This invite was withdrawn. Ask whoever sent it for a new one.",
63
+ invite_expired: (r) => `This invite expired${r ? ` on ${r.expires_at.toISOString().slice(0, 10)}` : ""}. Ask whoever sent it for a new one.`,
64
+ };
65
+ /** 404 for a token nobody minted; 410 for one that existed and no longer works — the client can tell "typo" from "too late". */
66
+ function refuse(c, why, r) {
67
+ return fail(c, why === "invite_not_found" ? 404 : 410, why, SENTENCE[why](r));
68
+ }
69
+ /** Raised inside the accept transaction; everything before it rolls back with it. */
70
+ class Refused extends Error {
71
+ status;
72
+ code;
73
+ constructor(status, code, message) {
74
+ super(message);
75
+ this.status = status;
76
+ this.code = code;
77
+ }
78
+ }
79
+ export function inviteRoutes(deps) {
80
+ const { sql } = deps;
81
+ const now = deps.now ?? (() => Date.now());
82
+ const app = new Hono();
83
+ const attempts = new Map();
84
+ /** orgs.ts's limiter, the same shape: counted before any work, so a flood costs a Map lookup. */
85
+ const overLimit = (c) => {
86
+ const ip = c.req.header("x-forwarded-for")?.split(",")[0]?.trim() || c.req.header("x-real-ip")?.trim() || "local";
87
+ const t = now();
88
+ const recent = (attempts.get(ip) ?? []).filter((at) => t - at < WINDOW_MS);
89
+ recent.push(t);
90
+ attempts.set(ip, recent);
91
+ if (attempts.size > 10_000)
92
+ attempts.clear();
93
+ return recent.length > PER_WINDOW;
94
+ };
95
+ /** `/auth/login`'s origin rule: an accept signs a browser in, so a cross-site page must not be able to make one. */
96
+ const foreign = (c) => {
97
+ const origin = c.req.header("Origin");
98
+ const host = c.req.header("Host");
99
+ const ours = new Set([deps.origin, host ? `http://${host}` : "", host ? `https://${host}` : ""]);
100
+ return !!origin && !ours.has(origin);
101
+ };
102
+ const mustBeOwner = (c) => {
103
+ const owner = c.get("owner");
104
+ if (!owner)
105
+ return fail(c, 401, "unauthorized", "Sign in to the dashboard first.");
106
+ if (owner.role !== "owner")
107
+ return fail(c, 403, "forbidden", "Only an owner can change who is in the studio.");
108
+ return owner;
109
+ };
110
+ const canMail = async () => isConfigured(await deps.mailer().catch(() => ({ kind: "none", reason: "" })));
111
+ const pending = (orgId) => inOrg(sql, orgId, (tx) => tx `
112
+ SELECT i.id, i.role, i.email, i.created_at, i.expires_at, u.name AS creator_name, u.email AS creator_email
113
+ FROM member_invites i
114
+ LEFT JOIN users u ON u.id = i.created_by
115
+ WHERE i.org_id = ${orgId}
116
+ AND i.accepted_at IS NULL AND i.revoked_at IS NULL AND i.expires_at > ${new Date(now())}
117
+ ORDER BY i.created_at DESC
118
+ `);
119
+ // ── The studio's side ───────────────────────────────────────────────────
120
+ app.get("/api/v1/members/invites", async (c) => {
121
+ const owner = c.get("owner");
122
+ if (!owner)
123
+ return fail(c, 401, "unauthorized", "Sign in to the dashboard first.");
124
+ // `mail` rides along so the dialog knows whether to offer sending before
125
+ // it is asked — the same answer Studio › General's mail row gives.
126
+ return c.json({ data: (await pending(owner.orgId)).map(shape), mail: await canMail() });
127
+ });
128
+ app.post("/api/v1/members/invites", async (c) => {
129
+ const owner = mustBeOwner(c);
130
+ if (owner instanceof Response)
131
+ return owner;
132
+ const body = (await c.req.json().catch(() => null));
133
+ const role = parseRole(body?.role);
134
+ if (role !== "owner" && role !== "admin")
135
+ return fail(c, 400, "validation_failed", role);
136
+ const raw = typeof body?.email === "string" ? body.email.trim().toLowerCase() : "";
137
+ if (raw && !EMAIL.test(raw))
138
+ return fail(c, 400, "validation_failed", "That does not look like an email address.", { field: "email" });
139
+ const email = raw || null;
140
+ // A full studio is told now, not after the invitee has typed a password.
141
+ // The accept counts again inside its own transaction, which is the check
142
+ // that holds; this one only saves somebody a wasted link.
143
+ const cap = (await deps.limits(owner.orgId)).members;
144
+ const already = await inOrg(sql, owner.orgId, async (tx) => {
145
+ if (cap !== null) {
146
+ const used = (await usage(tx, owner.orgId)).members;
147
+ if (used >= cap)
148
+ throw new LimitReached("members", cap, used);
149
+ }
150
+ if (!email)
151
+ return false;
152
+ const [hit] = await tx `
153
+ SELECT count(*)::int AS n FROM memberships m JOIN users u ON u.id = m.user_id AND u.deleted_at IS NULL
154
+ WHERE m.org_id = ${owner.orgId} AND u.email = ${email}
155
+ `;
156
+ return (hit?.n ?? 0) > 0;
157
+ }).catch((e) => {
158
+ if (e instanceof LimitReached)
159
+ return e;
160
+ throw e;
161
+ });
162
+ if (already instanceof LimitReached)
163
+ return planLimitReached(c, already);
164
+ if (already)
165
+ return fail(c, 409, "already_a_member", "Somebody with that email is already in the studio.", { field: "email" });
166
+ const token = randomBytes(32).toString("base64url");
167
+ const id = randomUUID();
168
+ const expires = new Date(now() + INVITE_TTL_DAYS * DAY_MS);
169
+ await inOrg(sql, owner.orgId, (tx) => tx `
170
+ INSERT INTO member_invites (id, org_id, token_hash, role, email, created_by, expires_at)
171
+ VALUES (${id}, ${owner.orgId}, ${sha256(token)}, ${role}::snag_member_role, ${email}, ${owner.userId}, ${expires})
172
+ `);
173
+ return c.json({
174
+ invite: shape({ id, role, email, created_at: new Date(now()), expires_at: expires, creator_name: owner.name, creator_email: owner.email }),
175
+ // The one and only time, as with a review link.
176
+ token,
177
+ url: inviteUrl(await deps.address(), token),
178
+ mail: await canMail(),
179
+ }, 201);
180
+ });
181
+ app.delete("/api/v1/members/invites/:id", async (c) => {
182
+ const owner = mustBeOwner(c);
183
+ if (owner instanceof Response)
184
+ return owner;
185
+ const id = c.req.param("id");
186
+ if (!UUID.test(id))
187
+ return fail(c, 404, "not_found", "No pending invite with that id.");
188
+ // Org-scoped in the WHERE as well as by the claim: on a role that
189
+ // bypasses RLS (every dev machine) the claim alone filters nothing.
190
+ const [hit] = await inOrg(sql, owner.orgId, (tx) => tx `
191
+ UPDATE member_invites SET revoked_at = now()
192
+ WHERE id = ${id} AND org_id = ${owner.orgId} AND accepted_at IS NULL AND revoked_at IS NULL
193
+ RETURNING id
194
+ `);
195
+ if (!hit)
196
+ return fail(c, 404, "not_found", "No pending invite with that id.");
197
+ return c.json({ data: (await pending(owner.orgId)).map(shape) });
198
+ });
199
+ /**
200
+ * Mail the link. SENT HERE, NOT QUEUED, and it has to be: an outbox row
201
+ * would hold the raw link in `outbox.event` until it was delivered and
202
+ * after (done rows stay), which is the copy of the token at rest this
203
+ * whole table was shaped to avoid. So it goes the way Studio › General's
204
+ * test message goes — inline, to a person who is watching, with the
205
+ * provider's own sentence when it fails — and the link in the dialog works
206
+ * whether or not it did.
207
+ *
208
+ * The caller presents the token it was shown, and it must hash to this
209
+ * row: the server cannot rebuild a link it never kept, and taking a token
210
+ * on trust would let an owner mail a link to an invite it does not name.
211
+ */
212
+ app.post("/api/v1/members/invites/:id/send", async (c) => {
213
+ const owner = mustBeOwner(c);
214
+ if (owner instanceof Response)
215
+ return owner;
216
+ const id = c.req.param("id");
217
+ const body = (await c.req.json().catch(() => null));
218
+ const token = typeof body?.token === "string" ? body.token : "";
219
+ if (!UUID.test(id))
220
+ return fail(c, 404, "not_found", "No pending invite with that id.");
221
+ const [row] = await inOrg(sql, owner.orgId, (tx) => tx `
222
+ SELECT i.token_hash, i.email, i.role, i.expires_at, i.accepted_at, i.revoked_at, o.name AS org_name
223
+ FROM member_invites i JOIN orgs o ON o.id = i.org_id
224
+ WHERE i.id = ${id} AND i.org_id = ${owner.orgId}
225
+ `);
226
+ if (!row || gone(row, new Date(now())))
227
+ return fail(c, 404, "not_found", "No pending invite with that id.");
228
+ const want = sha256(token);
229
+ if (!token || !timingSafeEqual(want, row.token_hash)) {
230
+ return fail(c, 403, "forbidden", "That is not this invite's link. Copy it from the invite you just made.");
231
+ }
232
+ if (!row.email)
233
+ return fail(c, 400, "validation_failed", "This invite has no email address to send it to. Copy the link instead.");
234
+ const mailer = await deps.mailer();
235
+ if (!isConfigured(mailer))
236
+ return fail(c, 409, "integration_not_configured", "No mail transport is connected yet.");
237
+ const url = inviteUrl(await deps.address(), token);
238
+ const who = owner.name || owner.email;
239
+ const studio = row.org_name || "a studio";
240
+ try {
241
+ await mailer.send({
242
+ to: row.email,
243
+ subject: `${who} invited you to ${studio}`,
244
+ text: `${who} has invited you to join ${studio} as ${row.role === "owner" ? "an owner" : "an admin"}.\n\n` +
245
+ `Open this link to choose your password and sign in:\n${url}\n\n` +
246
+ `It works once and expires on ${row.expires_at.toISOString().slice(0, 10)}. ` +
247
+ `If you were not expecting it, you can ignore this email.\n`,
248
+ });
249
+ }
250
+ catch (e) {
251
+ return fail(c, 502, "mail_send_failed", e instanceof Error ? e.message : String(e));
252
+ }
253
+ return c.json({ to: row.email });
254
+ });
255
+ // ── The invitee's side — public, rate-limited, bodies never logged ──────
256
+ app.use("/auth/invites/*", async (c, next) => {
257
+ c.set("noBodyLogging", true);
258
+ await next();
259
+ });
260
+ /** Find a live invite by its token. The maintenance claim: the org is what this is finding out (D56). */
261
+ const byToken = (token) => maintenance(sql, (tx) => tx `
262
+ SELECT i.id, i.org_id, i.role, i.email, i.expires_at, i.accepted_at, i.revoked_at,
263
+ o.name AS org_name, o.slug AS org_slug, u.name AS creator_name, u.email AS creator_email
264
+ FROM member_invites i
265
+ JOIN orgs o ON o.id = i.org_id AND o.deleted_at IS NULL
266
+ LEFT JOIN users u ON u.id = i.created_by
267
+ WHERE i.token_hash = ${sha256(token)}
268
+ `).then((r) => r[0] ?? null);
269
+ app.post("/auth/invites/lookup", async (c) => {
270
+ if (overLimit(c))
271
+ return fail(c, 429, "quota_exceeded", "Too many attempts from this address. Try again in an hour.");
272
+ const body = (await c.req.json().catch(() => null));
273
+ const token = typeof body?.token === "string" ? body.token.trim() : "";
274
+ const inv = token ? await byToken(token) : null;
275
+ if (!inv)
276
+ return refuse(c, "invite_not_found");
277
+ const why = gone(inv, new Date(now()));
278
+ if (why)
279
+ return refuse(c, why, inv);
280
+ return c.json({
281
+ org: { name: inv.org_name },
282
+ role: inv.role,
283
+ // Present only when the invite fixed it; the page then shows it rather than asking.
284
+ email: inv.email,
285
+ invitedBy: inv.creator_email ? inv.creator_name || inv.creator_email : null,
286
+ expiresAt: inv.expires_at.toISOString(),
287
+ });
288
+ });
289
+ app.post("/auth/invites/accept", async (c) => {
290
+ if (foreign(c))
291
+ return fail(c, 403, "forbidden", "That request came from another site.");
292
+ if (overLimit(c))
293
+ return fail(c, 429, "quota_exceeded", "Too many attempts from this address. Try again in an hour.");
294
+ const body = (await c.req.json().catch(() => null));
295
+ const str = (k) => (typeof body?.[k] === "string" ? body[k].trim() : "");
296
+ const token = str("token");
297
+ const name = str("name").slice(0, 120);
298
+ const password = typeof body?.password === "string" ? body.password : "";
299
+ const inv = token ? await byToken(token) : null;
300
+ if (!inv)
301
+ return refuse(c, "invite_not_found");
302
+ const early = gone(inv, new Date(now()));
303
+ if (early)
304
+ return refuse(c, early, inv);
305
+ const typed = str("email").toLowerCase();
306
+ if (inv.email && typed && typed !== inv.email) {
307
+ return fail(c, 403, "invite_email_mismatch", `This invite is for ${inv.email}. Join with that address, or ask for an invite to yours.`, { field: "email" });
308
+ }
309
+ const email = inv.email ?? typed;
310
+ if (!EMAIL.test(email))
311
+ return fail(c, 400, "validation_failed", "That does not look like an email address.", { field: "email" });
312
+ // Who this is, read before the transaction so the expensive half of the
313
+ // work — scrypt, a tenth of a second by design — is not done holding a
314
+ // row lock that a racing accept is waiting on.
315
+ const [existing] = await maintenance(sql, (tx) => tx `
316
+ SELECT u.id, u.name, u.password_hash,
317
+ EXISTS (SELECT 1 FROM memberships m WHERE m.user_id = u.id AND m.org_id = ${inv.org_id}) AS member
318
+ FROM users u WHERE u.email = ${email} AND u.deleted_at IS NULL
319
+ `);
320
+ if (existing?.member) {
321
+ return fail(c, 409, "already_a_member", `${email} is already in ${inv.org_name || "this studio"}. Sign in instead.`, { field: "email" });
322
+ }
323
+ let passwordHash = null;
324
+ if (existing) {
325
+ // An account on this instance already: the invite adds a studio to it,
326
+ // and its own password is what proves the holder is its owner.
327
+ if (!verifyPassword(password, existing.password_hash)) {
328
+ return fail(c, 409, "account_exists", `There is already an account for ${email} on this instance. Enter its password to join ${inv.org_name || "the studio"} with it.`, { field: "password" });
329
+ }
330
+ }
331
+ else {
332
+ // 05 §4.7: twelve, no composition rules — said as setup and sign-up say it.
333
+ if (password.length < 12)
334
+ return fail(c, 400, "validation_failed", "The password must be at least 12 characters. A short phrase of real words is fine.", { field: "password" });
335
+ passwordHash = hashPassword(password);
336
+ }
337
+ const cap = (await deps.limits(inv.org_id)).members;
338
+ let out;
339
+ try {
340
+ out = await sql.begin(async (tx) => {
341
+ // The maintenance claim for the consume (found by hash, cross-tenant
342
+ // by nature) and for a new users row, which no membership makes
343
+ // visible yet — the reason setup and members.ts took it too.
344
+ await tx `SET LOCAL app.maintenance = 'on'`;
345
+ await tx `SELECT set_config('app.org_id', ${inv.org_id}, true)`;
346
+ // THE consume. One statement on the whole pending predicate: a racing
347
+ // accept blocks on this row's lock, re-reads it once we commit, and
348
+ // finds accepted_at set.
349
+ const [won] = await tx `
350
+ UPDATE member_invites SET accepted_at = now()
351
+ WHERE id = ${inv.id} AND accepted_at IS NULL AND revoked_at IS NULL AND expires_at > now()
352
+ RETURNING id
353
+ `;
354
+ if (!won) {
355
+ const [again] = await tx `
356
+ SELECT accepted_at, revoked_at, expires_at FROM member_invites WHERE id = ${inv.id}
357
+ `;
358
+ const why = again ? gone(again, new Date()) ?? "invite_used" : "invite_used";
359
+ throw new Refused(410, why, SENTENCE[why](again));
360
+ }
361
+ if (cap !== null) {
362
+ const used = (await usage(tx, inv.org_id)).members;
363
+ if (used >= cap)
364
+ throw new LimitReached("members", cap, used);
365
+ }
366
+ const userId = existing?.id ?? randomUUID();
367
+ if (!existing) {
368
+ // The invite's org is the HOME org (§3.0). email_verified only when
369
+ // the owner named the address: an open invite's address is the
370
+ // invitee's own say-so, which is all the column has ever meant.
371
+ await tx `
372
+ INSERT INTO users (id, org_id, email, name, password_hash, email_verified)
373
+ VALUES (${userId}, ${inv.org_id}, ${email}, ${name}, ${passwordHash}, ${inv.email !== null})
374
+ `;
375
+ }
376
+ await tx `INSERT INTO memberships (org_id, user_id, role) VALUES (${inv.org_id}, ${userId}, ${inv.role}::snag_member_role)`;
377
+ await tx `UPDATE member_invites SET accepted_by = ${userId} WHERE id = ${inv.id}`;
378
+ // 05 §6.4b: assignable in every site from the moment they join. A
379
+ // person who was in this studio before and was removed kept their
380
+ // reviewer rows, blocked (members.ts); coming back unblocks them.
381
+ await ensureStudioReviewers(tx, { orgId: inv.org_id, userId });
382
+ if (existing) {
383
+ await tx `UPDATE reviewers SET blocked_at = NULL WHERE org_id = ${inv.org_id} AND user_id = ${userId} AND blocked_at IS NOT NULL`;
384
+ }
385
+ // Signed in exactly as `/auth/login` signs in: a user_sessions row is
386
+ // the authority, the raw token goes into the cookie once.
387
+ const session = await openSession(tx, { orgId: inv.org_id, userId, userAgent: c.req.header("User-Agent") ?? "" });
388
+ return { userId, userName: existing?.name ?? name, session };
389
+ });
390
+ }
391
+ catch (e) {
392
+ if (e instanceof Refused)
393
+ return fail(c, e.status, e.code, e.message);
394
+ if (e instanceof LimitReached)
395
+ return planLimitReached(c, e);
396
+ const pg = e;
397
+ if (pg?.code === "23505") {
398
+ // Lost a race to the same address: a sign-up, another invite, or
399
+ // this one's twin with a different token. Said as the sentence the
400
+ // slower request would have got had it arrived a moment later.
401
+ return pg.constraint_name === "memberships_pkey"
402
+ ? fail(c, 409, "already_a_member", `${email} is already in ${inv.org_name || "this studio"}. Sign in instead.`, { field: "email" })
403
+ : fail(c, 409, "account_exists", `There is already an account for ${email} on this instance. Enter its password to join with it.`, { field: "password" });
404
+ }
405
+ throw e;
406
+ }
407
+ setSessionCookies(c, out.session.token, out.session.csrf, deps.secure, SESSION_S);
408
+ return c.json({
409
+ org: { name: inv.org_name, slug: inv.org_slug },
410
+ user: { email, name: out.userName },
411
+ role: inv.role,
412
+ csrfToken: out.session.csrf,
413
+ }, 201);
414
+ });
415
+ return app;
416
+ }
417
+ //# sourceMappingURL=invites.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invites.js","sourceRoot":"","sources":["../../src/routes/invites.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,5 @@
1
1
  import { Hono } from "hono";
2
- import { randomUUID } from "node:crypto";
3
- import { ensureStudioReviewers } from "../studioReviewers.js";
4
- import { hashPassword } from "./owner.js";
5
- import { LimitReached, planLimitReached, usage } from "../plan.js";
2
+ import { maintenance } from "../db/scope.js";
6
3
  function fail(c, status, code, message) {
7
4
  return c.json({ error: { code, message, requestId: c.get("requestId") } }, status);
8
5
  }
@@ -51,63 +48,12 @@ export function memberRoutes(deps) {
51
48
  return fail(c, 401, "unauthorized", "Sign in to the dashboard first.");
52
49
  return c.json({ data: await list(owner.orgId, owner.userId) });
53
50
  });
54
- app.post("/api/v1/members", async (c) => {
55
- const owner = mustBeOwner(c);
56
- if (owner instanceof Response)
57
- return owner;
58
- const body = (await c.req.json().catch(() => null));
59
- const email = typeof body?.email === "string" ? body.email.trim().toLowerCase() : "";
60
- const name = typeof body?.name === "string" ? body.name.trim().slice(0, 120) : "";
61
- const password = typeof body?.password === "string" ? body.password : "";
62
- if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email))
63
- return fail(c, 400, "validation_failed", "That does not look like an email address.");
64
- const role = parseRole(body?.role);
65
- if (!WIRED.includes(role))
66
- return fail(c, 400, "validation_failed", role);
67
- // 05 §4.7: minimum 12, no composition rules — the same rule setup and
68
- // `passwd` apply, stated here before the server has to refuse it.
69
- if (password.length < 12)
70
- return fail(c, 400, "validation_failed", "The initial password needs at least 12 characters. A short phrase of real words is fine.");
71
- const userId = randomUUID();
72
- // The plan's number (plan/09), read before the transaction; counted inside it.
73
- const cap = (await deps.limits(owner.orgId)).members;
74
- try {
75
- await sql.begin(async (tx) => {
76
- if (cap !== null) {
77
- await tx `SELECT set_config('app.org_id', ${owner.orgId}, true)`;
78
- const used = (await usage(tx, owner.orgId)).members;
79
- if (used >= cap)
80
- throw new LimitReached("members", cap, used);
81
- }
82
- // The maintenance claim (0002, D56) for the users INSERT: a user with
83
- // no membership row yet is invisible to users_org, so under a role
84
- // that honours RLS the insert would fail its own WITH CHECK. Setup
85
- // and boot take the same claim for the same row. The membership then
86
- // goes in under the org scope like any other write.
87
- await tx `SET LOCAL app.maintenance = 'on'`;
88
- await tx `SELECT set_config('app.org_id', ${owner.orgId}, true)`;
89
- await tx `
90
- INSERT INTO users (id, org_id, email, name, password_hash, email_verified)
91
- VALUES (${userId}, ${owner.orgId}, ${email}, ${name}, ${hashPassword(password)}, true)
92
- `;
93
- await tx `INSERT INTO memberships (org_id, user_id, role) VALUES (${owner.orgId}, ${userId}, ${role}::snag_member_role)`;
94
- // 05 §6.4b: a member is assignable in every site from the moment
95
- // they join — the other half of what createProject does.
96
- await ensureStudioReviewers(tx, { orgId: owner.orgId, userId });
97
- });
98
- }
99
- catch (e) {
100
- if (e instanceof LimitReached)
101
- return planLimitReached(c, e);
102
- // users_email_uniq — the one collision this route can have, said as a
103
- // sentence rather than a 500.
104
- if (e?.code === "23505") {
105
- return fail(c, 409, "already_a_member", "Somebody with that email is already in the studio.");
106
- }
107
- throw e;
108
- }
109
- return c.json({ data: await list(owner.orgId, owner.userId) }, 201);
110
- });
51
+ /**
52
+ * Gone, said rather than 404'd: a script written against D85's
53
+ * create-with-password reads where members come from now, instead of
54
+ * "no route", which reads as a typo.
55
+ */
56
+ app.post("/api/v1/members", (c) => fail(c, 404, "not_found", "Members join through an invite now: POST /api/v1/members/invites, then send them the link."));
111
57
  app.patch("/api/v1/members/:email", async (c) => {
112
58
  const owner = mustBeOwner(c);
113
59
  if (owner instanceof Response)
@@ -163,18 +109,40 @@ export function memberRoutes(deps) {
163
109
  if ((owners?.n ?? 0) <= 1)
164
110
  return "last_owner";
165
111
  }
166
- // Soft on the user, hard on the membership: users_email_uniq is partial
167
- // on deleted_at IS NULL, so the same email can be added again later;
168
- // every session goes with it, because the session middleware requires
169
- // a membership row and this is the row.
170
112
  // 05 §6.4b: they leave the assignee pool and keep what was already
171
113
  // assigned to them — blocked, which is the one mechanism the spec has
172
114
  // for that, and never un-assigned, which would rewrite history on
173
115
  // threads somebody is mid-conversation on.
174
116
  await tx `UPDATE reviewers SET blocked_at = now() WHERE org_id = ${owner.orgId} AND user_id = ${target.user_id} AND blocked_at IS NULL`;
175
- await tx `UPDATE user_sessions SET revoked_at = now() WHERE user_id = ${target.user_id} AND revoked_at IS NULL`;
176
- await tx `DELETE FROM memberships WHERE org_id = ${owner.orgId} AND user_id = ${target.user_id}`;
177
- await tx `UPDATE users SET deleted_at = now(), updated_at = now() WHERE id = ${target.user_id}`;
117
+ // THIS STUDIO'S sessions, and the person only if this was their last
118
+ // studio (D121). Before invites could add an existing account to a
119
+ // second studio, every user here had one membership and "remove from
120
+ // the studio" and "delete the account" were the same act; now a person
121
+ // in two studios removed from one keeps the other, and a soft-delete
122
+ // here would have signed them out of a studio that never removed them.
123
+ await tx `UPDATE user_sessions SET revoked_at = now() WHERE user_id = ${target.user_id} AND org_id = ${owner.orgId} AND revoked_at IS NULL`;
124
+ const [other] = await maintenance(tx, (m) => m `
125
+ SELECT org_id FROM memberships WHERE user_id = ${target.user_id} AND org_id <> ${owner.orgId}
126
+ ORDER BY created_at LIMIT 1
127
+ `);
128
+ if (other) {
129
+ // Still somebody's member: re-home them if this was the home org
130
+ // (§3.0), because /auth/login opens a session in the HOME org and a
131
+ // session in a studio with no membership row is refused on arrival —
132
+ // they would be unable to sign in at all. Done before the membership
133
+ // goes, while users_org can still see the row.
134
+ await tx `UPDATE users SET org_id = ${other.org_id}, updated_at = now() WHERE id = ${target.user_id} AND org_id = ${owner.orgId}`;
135
+ await tx `DELETE FROM memberships WHERE org_id = ${owner.orgId} AND user_id = ${target.user_id}`;
136
+ }
137
+ else {
138
+ // Their last studio: soft on the user, hard on the membership, as
139
+ // before. users_email_uniq is partial on deleted_at IS NULL, so the
140
+ // same email can be invited again later; every session goes with it,
141
+ // because the session middleware requires a membership row.
142
+ await tx `UPDATE user_sessions SET revoked_at = now() WHERE user_id = ${target.user_id} AND revoked_at IS NULL`;
143
+ await tx `DELETE FROM memberships WHERE org_id = ${owner.orgId} AND user_id = ${target.user_id}`;
144
+ await tx `UPDATE users SET deleted_at = now(), updated_at = now() WHERE id = ${target.user_id}`;
145
+ }
178
146
  return "ok";
179
147
  });
180
148
  if (result === "missing")
@@ -1 +1 @@
1
- {"version":3,"file":"members.js","sourceRoot":"","sources":["../../src/routes/members.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"members.js","sourceRoot":"","sources":["../../src/routes/members.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAgB,MAAM,MAAM,CAAC;AAG1C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AA2C7C,SAAS,IAAI,CAAC,CAAM,EAAE,MAAc,EAAE,IAAY,EAAE,OAAe;IACjE,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,MAAa,CAAC,CAAC;AAC5F,CAAC;AAED,qFAAqF;AACrF,SAAS,WAAW,CAAC,CAAM;IACzB,MAAM,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC7B,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,cAAc,EAAE,iCAAiC,CAAC,CAAC;IACnF,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC3B,OAAO,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,WAAW,EAAE,gDAAgD,CAAC,CAAC;IACrF,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,KAAK,GAA0B,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AAExD,SAAS,SAAS,CAAC,CAAU;IAC3B,IAAI,CAAC,KAAK,OAAO,IAAI,CAAC,KAAK,OAAO,EAAE,CAAC;QACnC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,QAAQ;YACrC,CAAC,CAAC,IAAI,CAAC,0GAA0G;YACjH,CAAC,CAAC,8BAA8B,CAAC;IACrC,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAWD,MAAM,UAAU,YAAY,CAAC,IAA2B;IACtD,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IACrB,MAAM,GAAG,GAAG,IAAI,IAAI,EAAO,CAAC;IAE5B,+EAA+E;IAC/E,MAAM,KAAK,GAAG,CAAI,KAAa,EAAE,EAA+C,EAAE,EAAE,CAClF,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;QACrB,MAAM,EAAE,CAAA,mCAAmC,KAAK,SAAS,CAAC;QAC1D,OAAO,EAAE,CAAC,EAA6B,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEL,MAAM,IAAI,GAAG,KAAK,EAAE,KAAa,EAAE,EAAU,EAAwB,EAAE;QACrE,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,CACrC,EAAE,CAAwF;;;;4BAIpE,KAAK;;OAE1B,CACF,CAAC;QACF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACtB,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,UAAU,CAAC,WAAW,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,OAAO,KAAK,EAAE;SACzG,CAAC,CAAC,CAAC;IACN,CAAC,CAAC;IAEF,GAAG,CAAC,GAAG,CAAC,iBAAiB,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACrC,MAAM,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC7B,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,cAAc,EAAE,iCAAiC,CAAC,CAAC;QACnF,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH;;;;OAIG;IACH,GAAG,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC,CAAC,EAAE,EAAE,CAChC,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,WAAW,EAAE,4FAA4F,CAAC,CACxH,CAAC;IAEF,GAAG,CAAC,KAAK,CAAC,wBAAwB,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QAC9C,MAAM,KAAK,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;QAC7B,IAAI,KAAK,YAAY,QAAQ;YAAE,OAAO,KAAK,CAAC;QAC5C,MAAM,KAAK,GAAG,kBAAkB,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC5E,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAmC,CAAC;QACtF,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAkB,CAAC;YAAE,OAAO,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,mBAAmB,EAAE,IAAI,CAAC,CAAC;QAExF,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE;YACnD,MAAM,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAAyC;;4BAE5C,KAAK,CAAC,KAAK,kBAAkB,KAAK;OACvD,CAAC;YACF,IAAI,CAAC,MAAM;gBAAE,OAAO,SAAkB,CAAC;YACvC,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;gBAChD,oEAAoE;gBACpE,wDAAwD;gBACxD,MAAM,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAAiB,6DAA6D,KAAK,CAAC,KAAK,qBAAqB,CAAC;gBACxI,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC;oBAAE,OAAO,YAAqB,CAAC;YAC1D,CAAC;YACD,MAAM,EAAE,CAAA,iCAAiC,IAAkB,qCAAqC,KAAK,CAAC,KAAK,kBAAkB,MAAM,CAAC,OAAO,EAAE,CAAC;YAC9I,OAAO,IAAa,CAAC;QACvB,CAAC,CAAC,CAAC;QACH,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,WAAW,EAAE,0CAA0C,CAAC,CAAC;QACvG,IAAI,MAAM,KAAK,YAAY;YAAE,OAAO,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,YAAY,EAAE,4DAA4D,CAAC,CAAC;QAC7H,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,MAAM,CAAC,wBAAwB,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QAC/C,MAAM,KAAK,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;QAC7B,IAAI,KAAK,YAAY,QAAQ;YAAE,OAAO,KAAK,CAAC;QAC5C,MAAM,KAAK,GAAG,kBAAkB,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC5E,IAAI,KAAK,KAAK,KAAK,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxC,qEAAqE;YACrE,qEAAqE;YACrE,wDAAwD;YACxD,OAAO,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,gDAAgD,CAAC,CAAC;QAChF,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE;YACnD,MAAM,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAAyC;;4BAE5C,KAAK,CAAC,KAAK,kBAAkB,KAAK;OACvD,CAAC;YACF,IAAI,CAAC,MAAM;gBAAE,OAAO,SAAkB,CAAC;YACvC,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;gBAC5B,MAAM,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAAiB,6DAA6D,KAAK,CAAC,KAAK,qBAAqB,CAAC;gBACxI,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC;oBAAE,OAAO,YAAqB,CAAC;YAC1D,CAAC;YACD,mEAAmE;YACnE,sEAAsE;YACtE,kEAAkE;YAClE,2CAA2C;YAC3C,MAAM,EAAE,CAAA,0DAA0D,KAAK,CAAC,KAAK,kBAAkB,MAAM,CAAC,OAAO,yBAAyB,CAAC;YACvI,qEAAqE;YACrE,mEAAmE;YACnE,qEAAqE;YACrE,uEAAuE;YACvE,qEAAqE;YACrE,uEAAuE;YACvE,MAAM,EAAE,CAAA,+DAA+D,MAAM,CAAC,OAAO,iBAAiB,KAAK,CAAC,KAAK,yBAAyB,CAAC;YAC3I,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,WAAW,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAsB;yDACjB,MAAM,CAAC,OAAO,kBAAkB,KAAK,CAAC,KAAK;;OAE7F,CAAC,CAAC;YACH,IAAI,KAAK,EAAE,CAAC;gBACV,iEAAiE;gBACjE,oEAAoE;gBACpE,qEAAqE;gBACrE,qEAAqE;gBACrE,+CAA+C;gBAC/C,MAAM,EAAE,CAAA,6BAA6B,KAAK,CAAC,MAAM,mCAAmC,MAAM,CAAC,OAAO,iBAAiB,KAAK,CAAC,KAAK,EAAE,CAAC;gBACjI,MAAM,EAAE,CAAA,0CAA0C,KAAK,CAAC,KAAK,kBAAkB,MAAM,CAAC,OAAO,EAAE,CAAC;YAClG,CAAC;iBAAM,CAAC;gBACN,kEAAkE;gBAClE,oEAAoE;gBACpE,qEAAqE;gBACrE,4DAA4D;gBAC5D,MAAM,EAAE,CAAA,+DAA+D,MAAM,CAAC,OAAO,yBAAyB,CAAC;gBAC/G,MAAM,EAAE,CAAA,0CAA0C,KAAK,CAAC,KAAK,kBAAkB,MAAM,CAAC,OAAO,EAAE,CAAC;gBAChG,MAAM,EAAE,CAAA,sEAAsE,MAAM,CAAC,OAAO,EAAE,CAAC;YACjG,CAAC;YACD,OAAO,IAAa,CAAC;QACvB,CAAC,CAAC,CAAC;QACH,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,WAAW,EAAE,0CAA0C,CAAC,CAAC;QACvG,IAAI,MAAM,KAAK,YAAY;YAAE,OAAO,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,YAAY,EAAE,4DAA4D,CAAC,CAAC;QAC7H,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -5,6 +5,7 @@ import { usage } from "../plan.js";
5
5
  import { studiosOf } from "../orgs.js";
6
6
  import { inOrg, maintenance } from "../db/scope.js";
7
7
  import { effectiveWriteMode } from "./sessions.js";
8
+ import { pendingMigrations } from "../db/migrate.js";
8
9
  /** 05 §4.7: 30-day absolute, 7-day idle, rolling refresh on use. */
9
10
  const ABSOLUTE_MS = 30 * 24 * 60 * 60 * 1000;
10
11
  const IDLE_MS = 7 * 24 * 60 * 60 * 1000;
@@ -344,6 +345,10 @@ export function ownerRoutes(deps) {
344
345
  org: { name: org?.name ?? "", slug: org?.slug ?? "" },
345
346
  orgs: studios.map((s) => ({ slug: s.slug, name: s.name, role: s.role, current: s.id === owner.orgId })),
346
347
  signupOpen: deps.signupOpen?.() ?? false,
348
+ // The migrations this build ships that the database has not run
349
+ // (db/migrate.ts says why a serverless deploy can be behind). Empty
350
+ // when current; the dashboard shows a banner otherwise.
351
+ pendingMigrations: await pendingMigrations(sql),
347
352
  projects: projects.map((p) => ({
348
353
  uid: p.uid,
349
354
  name: p.name,