@snagset/server 0.0.1

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 (105) hide show
  1. package/README.md +83 -0
  2. package/dist/app.js +453 -0
  3. package/dist/app.js.map +1 -0
  4. package/dist/bin.js +951 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/boot.js +321 -0
  7. package/dist/boot.js.map +1 -0
  8. package/dist/config.generated.js +116 -0
  9. package/dist/config.generated.js.map +1 -0
  10. package/dist/config.js +180 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/crypto.js +26 -0
  13. package/dist/crypto.js.map +1 -0
  14. package/dist/db/connect.js +49 -0
  15. package/dist/db/connect.js.map +1 -0
  16. package/dist/db/migrate.js +210 -0
  17. package/dist/db/migrate.js.map +1 -0
  18. package/dist/db/migrations/0001_init.sql +1060 -0
  19. package/dist/db/migrations/0002_maintenance_claim.sql +109 -0
  20. package/dist/db/migrations/0003_email_provider.sql +35 -0
  21. package/dist/db/migrations/0004_unsubscribe.sql +24 -0
  22. package/dist/db/rls.js +58 -0
  23. package/dist/db/rls.js.map +1 -0
  24. package/dist/jobs/index.js +171 -0
  25. package/dist/jobs/index.js.map +1 -0
  26. package/dist/jobs/reaper.js +182 -0
  27. package/dist/jobs/reaper.js.map +1 -0
  28. package/dist/keys.js +77 -0
  29. package/dist/keys.js.map +1 -0
  30. package/dist/mail/index.js +75 -0
  31. package/dist/mail/index.js.map +1 -0
  32. package/dist/mail/mailer.js +27 -0
  33. package/dist/mail/mailer.js.map +1 -0
  34. package/dist/mail/notify.js +405 -0
  35. package/dist/mail/notify.js.map +1 -0
  36. package/dist/mail/resend.js +95 -0
  37. package/dist/mail/resend.js.map +1 -0
  38. package/dist/mail/smtp.js +315 -0
  39. package/dist/mail/smtp.js.map +1 -0
  40. package/dist/mail/unsubscribe.js +134 -0
  41. package/dist/mail/unsubscribe.js.map +1 -0
  42. package/dist/projectCache.js +50 -0
  43. package/dist/projectCache.js.map +1 -0
  44. package/dist/projects.js +101 -0
  45. package/dist/projects.js.map +1 -0
  46. package/dist/routes/attachments.js +159 -0
  47. package/dist/routes/attachments.js.map +1 -0
  48. package/dist/routes/auth.js +55 -0
  49. package/dist/routes/auth.js.map +1 -0
  50. package/dist/routes/commentEdit.js +201 -0
  51. package/dist/routes/commentEdit.js.map +1 -0
  52. package/dist/routes/comments.js +313 -0
  53. package/dist/routes/comments.js.map +1 -0
  54. package/dist/routes/cors.js +174 -0
  55. package/dist/routes/cors.js.map +1 -0
  56. package/dist/routes/jobs.js +56 -0
  57. package/dist/routes/jobs.js.map +1 -0
  58. package/dist/routes/mcpAuth.js +187 -0
  59. package/dist/routes/mcpAuth.js.map +1 -0
  60. package/dist/routes/mcpToken.js +94 -0
  61. package/dist/routes/mcpToken.js.map +1 -0
  62. package/dist/routes/owner.js +288 -0
  63. package/dist/routes/owner.js.map +1 -0
  64. package/dist/routes/reviewLinks.js +169 -0
  65. package/dist/routes/reviewLinks.js.map +1 -0
  66. package/dist/routes/rounds.js +137 -0
  67. package/dist/routes/rounds.js.map +1 -0
  68. package/dist/routes/sessions.js +349 -0
  69. package/dist/routes/sessions.js.map +1 -0
  70. package/dist/routes/sites.js +192 -0
  71. package/dist/routes/sites.js.map +1 -0
  72. package/dist/routes/threads.js +1080 -0
  73. package/dist/routes/threads.js.map +1 -0
  74. package/dist/routes/widget.js +132 -0
  75. package/dist/routes/widget.js.map +1 -0
  76. package/dist/serverless.js +97 -0
  77. package/dist/serverless.js.map +1 -0
  78. package/dist/session.js +152 -0
  79. package/dist/session.js.map +1 -0
  80. package/dist/storage/bucket.js +116 -0
  81. package/dist/storage/bucket.js.map +1 -0
  82. package/dist/storage/settings.js +132 -0
  83. package/dist/storage/settings.js.map +1 -0
  84. package/dist/vendor/adapters/index.js +1 -0
  85. package/dist/vendor/adapters/outbound/fence.js +44 -0
  86. package/dist/vendor/adapters/outbound/sanitise.js +233 -0
  87. package/dist/vendor/types/index.js +4 -0
  88. package/dist/vendor/types/names.js +254 -0
  89. package/dist/vendor/widget/object.js +38 -0
  90. package/dist/vendor/widget/predicates.js +377 -0
  91. package/package.json +53 -0
  92. package/static/dashboard/assets/index-CaPfKhFP.css +1 -0
  93. package/static/dashboard/assets/index-DfTNjHCK.js +1 -0
  94. package/static/dashboard/index.html +14 -0
  95. package/static/widget/snag.anchor-ext.js +1 -0
  96. package/static/widget/snag.anchor-fuzzy.js +1 -0
  97. package/static/widget/snag.capture.js +4 -0
  98. package/static/widget/snag.crumbs.js +4 -0
  99. package/static/widget/snag.dock.js +5 -0
  100. package/static/widget/snag.js +5 -0
  101. package/static/widget/snag.panels.js +2 -0
  102. package/static/widget/snag.runtime.js +2 -0
  103. package/static/widget/snag.screenshot.js +1 -0
  104. package/static/widget/snag.text.js +2 -0
  105. package/vercel.json +6 -0
@@ -0,0 +1,349 @@
1
+ import { Hono } from "hono";
2
+ import { randomUUID, randomBytes, createHash } from "node:crypto";
3
+ import { ID_PREFIX } from "../vendor/types/names.js";
4
+ import { ProjectUnavailable } from "../projectCache.js";
5
+ import { mintSession, verifySession, withinRefreshWindow } from "../session.js";
6
+ function fail(c, status, code, message) {
7
+ return c.json({ error: { code, message, requestId: c.get("requestId") } }, status);
8
+ }
9
+ /** Every secret in this product: 32 bytes, unpadded base64url, 43 chars. The
10
+ * hash is taken over the string that arrived on the wire, never a re-decode. */
11
+ const sha256 = (s) => createHash("sha256").update(s, "utf8").digest();
12
+ const mintSecret = () => randomBytes(32).toString("base64url");
13
+ /** `prj_…`/`rev_…` — the row id is bare, the wire form is prefixed (§2.10). */
14
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
15
+ function rowId(wire, prefix) {
16
+ if (typeof wire !== "string")
17
+ return null;
18
+ const bare = wire.startsWith(prefix) ? wire.slice(prefix.length) : wire;
19
+ return UUID.test(bare) ? bare.toLowerCase() : null;
20
+ }
21
+ /** §3.4's default: what an ordinary client link may do. */
22
+ const CLIENT_PERMISSIONS = ["thread:create", "comment:create", "thread:resolve"];
23
+ /**
24
+ * 05 §4.2's read-time downgrade. There is no jobs engine yet (05 §3.11 item
25
+ * 12, the reaper), so `write_mode = 'open'` past its `write_mode_downgrade_at`
26
+ * is evaluated HERE, on every session mint, rather than flipped by a
27
+ * background pass. When M2-31's `runDueJobs` lands it should ALSO persist the
28
+ * downgrade (`UPDATE projects SET write_mode = 'link-required', … WHERE …`) so
29
+ * this stays a fallback rather than the only enforcement — but a session
30
+ * request must never trust a `write_mode` column that could be 24 hours
31
+ * stale, which is why this function exists independently of that job.
32
+ */
33
+ export function effectiveWriteMode(row, at) {
34
+ if (row.write_mode === "open" &&
35
+ row.write_mode_downgrade_at &&
36
+ row.write_mode_downgrade_at.getTime() <= at.getTime()) {
37
+ return "link-required";
38
+ }
39
+ return row.write_mode;
40
+ }
41
+ /**
42
+ * `perm` is computed ONCE, from the credential, here — §4.4's table.
43
+ *
44
+ * *"Nothing about visibility is ever taken from the request. Not a header, not
45
+ * a query parameter, not a field on the body, not the reviewer's own claim
46
+ * about themselves."* `thread:private` and `thread:assign` decide whether a
47
+ * session sees team-only threads, so the only route by which a widget session
48
+ * gets them is a review link whose stored `permissions` already contain them —
49
+ * the "team link" of §3.4.
50
+ */
51
+ function permissionsFor(kind, link) {
52
+ if (kind === "guest")
53
+ return CLIENT_PERMISSIONS.filter((p) => p !== "thread:resolve");
54
+ if (kind === "link" && link) {
55
+ const stored = link.permissions.filter((p) => ["thread:create", "comment:create", "thread:resolve", "thread:delete:own", "thread:private", "thread:assign"].includes(p));
56
+ return stored.length ? stored : CLIENT_PERMISSIONS;
57
+ }
58
+ return CLIENT_PERMISSIONS;
59
+ }
60
+ export function sessionRoutes(deps) {
61
+ const app = new Hono();
62
+ const { sql, keys, projects, ttls } = deps;
63
+ const now = deps.now ?? (() => new Date());
64
+ app.post("/api/v1/projects/:uid/sessions", async (c) => {
65
+ const uid = c.req.param("uid");
66
+ let project;
67
+ try {
68
+ project = await projects.get(uid);
69
+ }
70
+ catch (e) {
71
+ // Fail closed. A stale cache entry is a stale session_epoch, and that
72
+ // means a revoked reviewer keeps working (projectCache.ts).
73
+ if (e instanceof ProjectUnavailable) {
74
+ c.header("Retry-After", "5");
75
+ return fail(c, 503, "database_unavailable", "The server cannot reach its database right now.");
76
+ }
77
+ throw e;
78
+ }
79
+ if (!project)
80
+ return fail(c, 404, "project_not_found", "No project with that uid.");
81
+ // §6.9's codes, not §4.5.1's blanket 401: a paused project is 403 and a
82
+ // disabled one 410 everywhere else in this API, and a client branches on
83
+ // the status class. "Your token is wrong" and "this project is switched
84
+ // off" are different problems with different next steps.
85
+ if (project.status === "paused") {
86
+ return fail(c, 403, "project_paused", "This project is paused. Its owner can resume it.");
87
+ }
88
+ if (project.status === "disabled" || project.status === "deleting") {
89
+ return fail(c, 410, "project_disabled", "This project is no longer accepting comments.");
90
+ }
91
+ // Computed once, up front, from the row the cache just gave us — see
92
+ // `effectiveWriteMode`'s own comment for why this can't wait for a reaper.
93
+ const writeMode = effectiveWriteMode(project, now());
94
+ const body = (await c.req.json().catch(() => null));
95
+ if (!body)
96
+ return fail(c, 400, "validation_failed", "A JSON body is required.");
97
+ // §4.4, and A31's shape of rule: a request that tries to grant itself
98
+ // visibility is refused rather than ignored, because silently dropping the
99
+ // field teaches the caller it worked.
100
+ for (const forbidden of ["role", "isTeam", "includePrivate", "permissions", "perm"]) {
101
+ if (forbidden in body || (body.identity && forbidden in body.identity)) {
102
+ return fail(c, 422, "field_not_permitted", `"${forbidden}" is not accepted here. What a session may see is decided by its credential, never by the request.`);
103
+ }
104
+ }
105
+ const bearer = /^Bearer (.+)$/i.exec(c.req.header("Authorization") ?? "")?.[1];
106
+ const identity = body.identity;
107
+ // ── REFRESH: a bearer and no identity (§4.4). Silent, and the whole reason
108
+ // a reviewer is not asked to re-open their link four times an afternoon.
109
+ if (bearer && !identity) {
110
+ const v = await verifySession(keys, bearer, {
111
+ projectUid: uid,
112
+ expectedEpoch: project.session_epoch,
113
+ allowExpired: true,
114
+ absoluteTtlS: ttls.absoluteS,
115
+ now,
116
+ });
117
+ if (!v.ok) {
118
+ const code = v.reason === "session_revoked" ? "session_revoked" : "unauthorized";
119
+ return fail(c, 401, code, "That session is no longer valid. Re-open your review link to continue.");
120
+ }
121
+ // A link session re-checks the link on refresh (§4.4): revocation is
122
+ // instant at exchange AND on every refresh, which is what bounds how long
123
+ // a revoked link keeps working to one sliding window.
124
+ let linkExpiresAt;
125
+ if (v.claims.lnk) {
126
+ const [link] = await sql `
127
+ SELECT expires_at, revoked_at FROM review_links WHERE id = ${v.claims.lnk}
128
+ `;
129
+ if (!link || link.revoked_at || link.expires_at <= now()) {
130
+ return fail(c, 401, "token_revoked", "This review link has been turned off.");
131
+ }
132
+ linkExpiresAt = Math.floor(link.expires_at.getTime() / 1000);
133
+ }
134
+ if (!withinRefreshWindow(v.claims, ttls, linkExpiresAt, now)) {
135
+ return fail(c, 401, "session_expired", "This review session has reached its limit. Re-open your review link.");
136
+ }
137
+ const [reviewer] = await sql `
138
+ SELECT identity_valid_after FROM reviewers WHERE id = ${v.claims.sub}
139
+ `;
140
+ if (reviewer?.identity_valid_after && v.claims.iat * 1000 < reviewer.identity_valid_after.getTime()) {
141
+ return fail(c, 401, "session_revoked", "This reviewer has been signed out.");
142
+ }
143
+ const minted = await mintSession(keys, {
144
+ projectUid: uid,
145
+ reviewerId: v.claims.sub,
146
+ environmentKey: v.claims.env,
147
+ kind: v.claims.kind,
148
+ reviewLinkId: v.claims.lnk,
149
+ permissions: v.claims.perm,
150
+ sessionEpoch: project.session_epoch,
151
+ // Carried forward — the cap does not slide (session.ts).
152
+ sid: v.claims.sid,
153
+ iat: v.claims.iat,
154
+ ...(v.claims.iex ? { iex: v.claims.iex } : {}),
155
+ ...(linkExpiresAt ? { linkExpiresAt } : {}),
156
+ }, ttls, now);
157
+ return c.json({
158
+ sessionToken: minted.token,
159
+ expiresAt: minted.expiresAt.toISOString(),
160
+ refreshableUntil: minted.refreshableUntil.toISOString(),
161
+ serverTime: now().toISOString(),
162
+ });
163
+ }
164
+ if (!identity || typeof identity.kind !== "string") {
165
+ return fail(c, 400, "validation_failed", "identity.kind is required (guest or link).");
166
+ }
167
+ if (identity.kind === "verified") {
168
+ // §4.6's HMAC handshake is M2-19. Saying so beats a 500.
169
+ return fail(c, 501, "not_implemented", "Verified identity is not available on this build yet.");
170
+ }
171
+ if (identity.kind !== "guest" && identity.kind !== "link") {
172
+ return fail(c, 400, "validation_failed", `Unknown identity kind "${identity.kind}".`);
173
+ }
174
+ // ── §4.2's write-mode gate. This is what closed the hole: a project past
175
+ // `open`'s grace window (or minted `link-required`/`verified-only`)
176
+ // used to mint a guest session anyway, because nothing here ever read
177
+ // `write_mode`. `verified-only` is checked FIRST: neither a `guest`
178
+ // identity (no credential at all) nor a `link` identity is ever
179
+ // `verified`, so both must be refused before the guest/link check
180
+ // below gets a chance to mislabel a verified-only rejection as
181
+ // "get a link" (specs/05-api-data.md §6.9 registers the two codes
182
+ // this way, and §4.2's table maps mode -> code the same way; a
183
+ // guest-first ordering here made a verified-only project answer
184
+ // guests with 403 link_required instead of verified_identity_required
185
+ // — a link would not have helped them). Only once verified-only is
186
+ // out of the way does a guest identity fall through to link_required
187
+ // for anything short of `open`.
188
+ if (writeMode === "verified-only") {
189
+ return fail(c, 403, "verified_identity_required", "This project requires a verified identity to comment.");
190
+ }
191
+ if (identity.kind === "guest" && writeMode !== "open") {
192
+ return fail(c, 403, "link_required", "This project needs a review link to comment. Ask the studio for one.");
193
+ }
194
+ // ── The environment. Absent means the project's default (the widget only
195
+ // knows a name if the host put one on the script tag).
196
+ const envKey = typeof body.environment === "string" && body.environment ? body.environment : null;
197
+ const [environment] = await sql `
198
+ SELECT id, key, label FROM environments
199
+ WHERE project_id = ${project.id} AND deleted_at IS NULL
200
+ AND ${envKey ? sql `key = ${envKey}` : sql `is_default`}
201
+ LIMIT 1
202
+ `;
203
+ if (!environment) {
204
+ return fail(c, 400, "environment_not_found", envKey ? `This project has no environment called "${envKey}".` : "This project has no default environment.");
205
+ }
206
+ // ── §4.5.1's validation table, IN ORDER.
207
+ let link = null;
208
+ if (identity.kind === "link") {
209
+ if (typeof identity.token !== "string" || !identity.token) {
210
+ return fail(c, 401, "token_invalid", "That review link is not valid.");
211
+ }
212
+ // Looked up BY HASH, so an unknown token and a wrong one take the same
213
+ // path — there is no row to compare against and therefore nothing to
214
+ // compare in variable time.
215
+ // T8: LEFT JOIN rounds so a round-pinned link can report the round
216
+ // NUMBER (what a reviewer and the widget both mean by "round"), not
217
+ // just its opaque id -- rounds.id never left the server before this.
218
+ const [found] = await sql `
219
+ SELECT rl.id, rl.round_id, rd.number AS round_number, rl.permissions, rl.environments,
220
+ rl.allow_name_override, rl.name, rl.email,
221
+ rl.max_uses, rl.use_count, rl.expires_at, rl.revoked_at
222
+ FROM review_links rl
223
+ LEFT JOIN rounds rd ON rd.id = rl.round_id
224
+ WHERE rl.project_id = ${project.id} AND rl.token_hash = ${sha256(identity.token)}
225
+ `;
226
+ if (!found)
227
+ return fail(c, 401, "token_invalid", "That review link is not valid.");
228
+ if (found.revoked_at)
229
+ return fail(c, 401, "token_revoked", "This review link has been turned off.");
230
+ if (found.expires_at <= now()) {
231
+ return fail(c, 401, "token_expired", "This review link has expired. Ask for a new one.");
232
+ }
233
+ if (!found.environments.includes("*") && !found.environments.includes(environment.key)) {
234
+ return fail(c, 401, "token_wrong_environment", "This review link is not for this environment.");
235
+ }
236
+ link = found;
237
+ }
238
+ const kind = identity.kind;
239
+ const permissions = permissionsFor(kind, link);
240
+ // ── The reviewer. §4.4.1: the SERVER mints the holder secret and returns
241
+ // it once; later exchanges present it back.
242
+ const presented = typeof identity.holderSecret === "string" ? identity.holderSecret : null;
243
+ const presentedId = rowId(identity.holderId, ID_PREFIX.reviewer);
244
+ let holderSecret = null;
245
+ let reviewerId = null;
246
+ let reviewerName;
247
+ // `allow_name_override = false` ⇒ the link's own name is authoritative and
248
+ // a typed one is silently IGNORED, never a 422: the reviewer then sees the
249
+ // name they will be posting under rather than the one they asked for.
250
+ const typedName = typeof identity.name === "string" ? identity.name.trim().slice(0, 80) : "";
251
+ reviewerName = link && !link.allow_name_override ? link.name : typedName || link?.name || "Anonymous";
252
+ if (presented && presentedId) {
253
+ const [existing] = await sql `
254
+ SELECT id, name FROM reviewers
255
+ WHERE id = ${presentedId} AND project_id = ${project.id}
256
+ AND holder_secret_hash = ${sha256(presented)}
257
+ AND blocked_at IS NULL AND erased_at IS NULL
258
+ `;
259
+ // §4.4.1: "a mismatch does NOT error — it silently mints a new guest
260
+ // identity, because a stale storage entry must never lock a reviewer
261
+ // out." So this is an `if`, not a guard clause.
262
+ if (existing) {
263
+ reviewerId = existing.id;
264
+ if (typedName && (!link || link.allow_name_override)) {
265
+ await sql `UPDATE reviewers SET name = ${reviewerName}, last_seen_at = now() WHERE id = ${reviewerId}`;
266
+ }
267
+ else {
268
+ await sql `UPDATE reviewers SET last_seen_at = now() WHERE id = ${reviewerId}`;
269
+ }
270
+ }
271
+ }
272
+ if (!reviewerId) {
273
+ // §4.5.1's `max_uses` check happens HERE, not above, because it counts
274
+ // PEOPLE: a holder who already has a row is readmitted even on an
275
+ // exhausted link, and only a genuinely new person is refused.
276
+ if (link && link.max_uses !== null && link.use_count >= link.max_uses) {
277
+ return fail(c, 401, "token_exhausted", "This review link has reached its limit.");
278
+ }
279
+ holderSecret = mintSecret();
280
+ const id = randomUUID();
281
+ const inserted = await sql.begin(async (tx) => {
282
+ await tx `SELECT set_config('app.org_id', ${project.org_id}, true)`;
283
+ const rows = await tx `
284
+ INSERT INTO reviewers (id, org_id, project_id, kind, name, email, review_link_id, holder_secret_hash)
285
+ VALUES (${id}, ${project.org_id}, ${project.id}, ${kind}, ${reviewerName},
286
+ ${link?.email ?? null}, ${link?.id ?? null}, ${sha256(holderSecret)})
287
+ ON CONFLICT (project_id, review_link_id, holder_secret_hash) WHERE kind = 'link'
288
+ DO NOTHING
289
+ RETURNING id
290
+ `;
291
+ // §4.5: use_count increments ONLY on a returned id — that is what makes
292
+ // it count people rather than exchanges. `last_used_at` rides along, so
293
+ // an exchange is one extra statement rather than two.
294
+ if (rows[0] && link) {
295
+ await tx `
296
+ UPDATE review_links SET use_count = use_count + 1, last_used_at = now() WHERE id = ${link.id}
297
+ `;
298
+ }
299
+ else if (link) {
300
+ await tx `UPDATE review_links SET last_used_at = now() WHERE id = ${link.id}`;
301
+ }
302
+ return rows[0]?.id ?? null;
303
+ });
304
+ reviewerId = inserted;
305
+ if (!reviewerId) {
306
+ // The conflict fired: this holder already has a row on this link.
307
+ const [again] = await sql `
308
+ SELECT id FROM reviewers
309
+ WHERE project_id = ${project.id} AND review_link_id = ${link?.id ?? null}
310
+ AND holder_secret_hash = ${sha256(holderSecret)} AND kind = 'link'
311
+ `;
312
+ reviewerId = again?.id ?? null;
313
+ holderSecret = null;
314
+ }
315
+ if (!reviewerId)
316
+ return fail(c, 500, "internal_error", "Could not create a reviewer.");
317
+ }
318
+ const linkExpiresAt = link ? Math.floor(link.expires_at.getTime() / 1000) : undefined;
319
+ const minted = await mintSession(keys, {
320
+ projectUid: uid,
321
+ reviewerId,
322
+ environmentKey: environment.key,
323
+ kind,
324
+ reviewLinkId: link?.id ?? null,
325
+ permissions,
326
+ sessionEpoch: project.session_epoch,
327
+ ...(linkExpiresAt ? { linkExpiresAt } : {}),
328
+ }, ttls, now);
329
+ return c.json({
330
+ sessionToken: minted.token,
331
+ expiresAt: minted.expiresAt.toISOString(),
332
+ refreshableUntil: minted.refreshableUntil.toISOString(),
333
+ reviewer: { id: ID_PREFIX.reviewer + reviewerId, name: reviewerName, kind },
334
+ // Returned ONCE, on first creation of this person (§4.4.1). Absent on
335
+ // every later exchange, which is how the client knows to keep the one
336
+ // it has.
337
+ ...(holderSecret ? { holderSecret } : {}),
338
+ environment,
339
+ permissions,
340
+ // T8: `number` alongside `id` -- the widget stores the NUMBER beside
341
+ // the bearer and passes `?round=<number>` on `GET …/threads`; the
342
+ // internal `rounds.id` is never a query param anywhere in 05.
343
+ ...(link?.round_id ? { round: { id: link.round_id, number: link.round_number } } : {}),
344
+ serverTime: now().toISOString(),
345
+ }, 201);
346
+ });
347
+ return app;
348
+ }
349
+ //# sourceMappingURL=sessions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../src/routes/sessions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,192 @@
1
+ import { Hono } from "hono";
2
+ import { createProject, validateOrigins } from "../projects.js";
3
+ /**
4
+ * NO CACHE INVALIDATION HOOK, deliberately.
5
+ *
6
+ * The obvious next line here is "bust the project cache so the new site is live
7
+ * at once", and it would be dead code. A new project was never cached — the
8
+ * cache is a miss and loads it. And `cors.ts`'s origin cache is a self-expiring
9
+ * 10 s window whose own comment already made this call: *"a NEWLY allowed
10
+ * origin waits up to ten seconds to work, which reads as 'it took a moment'"*.
11
+ * A hook that fires and changes nothing is worse than none, because the next
12
+ * person reading it believes invalidation is handled.
13
+ */
14
+ function fail(c, status, code, message) {
15
+ return c.json({ error: { code, message, requestId: c.get("requestId") } }, status);
16
+ }
17
+ export function siteRoutes(deps) {
18
+ const app = new Hono();
19
+ const { sql } = deps;
20
+ /**
21
+ * `POST /api/v1/projects` — add a site.
22
+ *
23
+ * Unscoped, because there is no project to scope it to yet; the ORG comes
24
+ * from the owner cookie and never from the body. A body-supplied `orgId`
25
+ * would be the whole authorisation model, handed to the caller.
26
+ */
27
+ app.post("/api/v1/projects", async (c) => {
28
+ const owner = c.get("owner");
29
+ if (!owner)
30
+ return fail(c, 401, "unauthorized", "Sign in to the dashboard first.");
31
+ const body = (await c.req.json().catch(() => null));
32
+ const name = typeof body?.["name"] === "string" ? body["name"].trim() : "";
33
+ if (!name)
34
+ return fail(c, 400, "validation_failed", "A site needs a name.");
35
+ if (name.length > 120) {
36
+ return fail(c, 400, "validation_failed", "That name is too long — 120 characters at most.");
37
+ }
38
+ // Accepts an array or the comma-separated string a text input produces, so
39
+ // the dashboard does not have to split it and the CLI does not have to
40
+ // build an array. One shape on the wire is not worth a parsing rule in two
41
+ // clients.
42
+ const raw = body?.["origins"];
43
+ const origins = Array.isArray(raw)
44
+ ? raw.map(String)
45
+ : typeof raw === "string"
46
+ ? raw.split(",")
47
+ : [];
48
+ try {
49
+ validateOrigins(origins);
50
+ }
51
+ catch (e) {
52
+ return fail(c, 400, "validation_failed", e instanceof Error ? e.message : String(e));
53
+ }
54
+ // A site with no allowed origin cannot be talked to by anything — CORS
55
+ // refuses every request from the tag the operator is about to paste. It is
56
+ // ALLOWED (an origin can be added later, and `snagset allow-origin`
57
+ // exists) but the response says so, because the alternative is a tag that
58
+ // is installed correctly and silently does nothing.
59
+ const created = await sql.begin(async (tx) => {
60
+ await tx `SELECT set_config('app.org_id', ${owner.orgId}, true)`;
61
+ return createProject(tx, {
62
+ orgId: owner.orgId,
63
+ name,
64
+ createdBy: owner.userId,
65
+ origins,
66
+ graceHours: deps.graceHours,
67
+ });
68
+ });
69
+ return c.json({
70
+ uid: created.uid,
71
+ name: created.name,
72
+ origins: validateOrigins(origins),
73
+ ...(origins.length
74
+ ? {}
75
+ : {
76
+ warning: "No origins allowed yet, so no site can talk to this project. " +
77
+ "Add one before pasting the tag.",
78
+ }),
79
+ }, 201);
80
+ });
81
+ /**
82
+ * `POST /api/v1/projects/:uid/origins` — allow another origin.
83
+ *
84
+ * Here rather than in a route file of its own because it is the other half
85
+ * of adding a site: you get it wrong, or you add staging first and
86
+ * production later, and the alternative is the CLI on a machine the person
87
+ * who noticed does not have.
88
+ */
89
+ app.post("/api/v1/projects/:uid/origins", async (c) => {
90
+ const owner = c.get("owner");
91
+ if (!owner)
92
+ return fail(c, 401, "unauthorized", "Sign in to the dashboard first.");
93
+ const [project] = await sql `
94
+ SELECT id, org_id FROM projects WHERE uid = ${c.req.param("uid")} AND deleted_at IS NULL
95
+ `;
96
+ // A project in another org is reported as absent, not as forbidden — the
97
+ // same rule the comment route follows, so a uid's validity leaks nothing
98
+ // about somebody else's org.
99
+ if (!project || project.org_id !== owner.orgId) {
100
+ return fail(c, 404, "project_not_found", "No project with that uid.");
101
+ }
102
+ const body = (await c.req.json().catch(() => null));
103
+ const raw = body?.["origin"] ?? body?.["origins"];
104
+ let origins;
105
+ try {
106
+ origins = validateOrigins(Array.isArray(raw) ? raw.map(String) : typeof raw === "string" ? raw.split(",") : []);
107
+ }
108
+ catch (e) {
109
+ return fail(c, 400, "validation_failed", e instanceof Error ? e.message : String(e));
110
+ }
111
+ if (!origins.length)
112
+ return fail(c, 400, "validation_failed", "An origin is required.");
113
+ await sql.begin(async (tx) => {
114
+ await tx `SELECT set_config('app.org_id', ${owner.orgId}, true)`;
115
+ for (const origin of origins) {
116
+ // Adding an origin that is already allowed is a no-op, not an error:
117
+ // the operator's intent is satisfied either way, and a 409 here would
118
+ // make "make sure staging is allowed" a two-step operation.
119
+ await tx `
120
+ INSERT INTO project_origins (id, org_id, project_id, pattern, is_wildcard)
121
+ VALUES (gen_random_uuid(), ${owner.orgId}, ${project.id}, ${origin}, ${origin.includes("*.")})
122
+ ON CONFLICT (project_id, pattern) DO NOTHING
123
+ `;
124
+ }
125
+ });
126
+ const all = await sql `
127
+ SELECT pattern FROM project_origins WHERE project_id = ${project.id} ORDER BY pattern
128
+ `;
129
+ return c.json({ origins: all.map((r) => r.pattern) }, 201);
130
+ });
131
+ /**
132
+ * `PATCH /api/v1/projects/:uid` — who sees the toolbar. D66.
133
+ *
134
+ * ONE knob, and it is the knob that already existed: `write_mode`. Who can
135
+ * WRITE is who the toolbar is for — showing a comment bar to somebody whose
136
+ * comments would be refused is a form the product fills in and then tears
137
+ * up. `open` = everyone on the allow-listed addresses sees it and may
138
+ * comment; `link-required` = only somebody who opened a review link (and,
139
+ * thereafter, whenever they come back).
140
+ *
141
+ * Choosing `open` here CLEARS the D1 grace timer. The 24-hour downgrade
142
+ * exists so a site somebody set up and forgot does not stay a public comment
143
+ * box; an owner choosing "everyone" in the dashboard is the opposite of
144
+ * forgetting, and having the toolbar silently vanish a day later would be
145
+ * this product's most mysterious bug report.
146
+ */
147
+ app.patch("/api/v1/projects/:uid", async (c) => {
148
+ const owner = c.get("owner");
149
+ if (!owner)
150
+ return fail(c, 401, "unauthorized", "Sign in to the dashboard first.");
151
+ const [project] = await sql `
152
+ SELECT id, org_id FROM projects WHERE uid = ${c.req.param("uid")} AND deleted_at IS NULL
153
+ `;
154
+ if (!project || project.org_id !== owner.orgId) {
155
+ return fail(c, 404, "project_not_found", "No project with that uid.");
156
+ }
157
+ const body = (await c.req.json().catch(() => null));
158
+ const mode = body?.["writeMode"];
159
+ if (mode !== "open" && mode !== "link-required" && mode !== "verified-only") {
160
+ return fail(c, 400, "validation_failed", "writeMode must be open, link-required or verified-only.");
161
+ }
162
+ await sql.begin(async (tx) => {
163
+ await tx `SELECT set_config('app.org_id', ${owner.orgId}, true)`;
164
+ await tx `
165
+ UPDATE projects
166
+ SET write_mode = ${mode}::snag_write_mode,
167
+ write_mode_downgrade_at = ${mode === "open" ? null : tx `write_mode_downgrade_at`},
168
+ config_version = config_version + 1
169
+ WHERE id = ${project.id}::uuid
170
+ `;
171
+ });
172
+ return c.json({ uid: c.req.param("uid"), writeMode: mode });
173
+ });
174
+ /** The origins on a site — what the dashboard shows beside the script tag. */
175
+ app.get("/api/v1/projects/:uid/origins", async (c) => {
176
+ const owner = c.get("owner");
177
+ if (!owner)
178
+ return fail(c, 401, "unauthorized", "Sign in to the dashboard first.");
179
+ const [project] = await sql `
180
+ SELECT id, org_id FROM projects WHERE uid = ${c.req.param("uid")} AND deleted_at IS NULL
181
+ `;
182
+ if (!project || project.org_id !== owner.orgId) {
183
+ return fail(c, 404, "project_not_found", "No project with that uid.");
184
+ }
185
+ const all = await sql `
186
+ SELECT pattern FROM project_origins WHERE project_id = ${project.id} ORDER BY pattern
187
+ `;
188
+ return c.json({ origins: all.map((r) => r.pattern) });
189
+ });
190
+ return app;
191
+ }
192
+ //# sourceMappingURL=sites.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sites.js","sourceRoot":"","sources":["../../src/routes/sites.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAgB,MAAM,MAAM,CAAC;AAE1C,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAwChE;;;;;;;;;;GAUG;AAEH,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,MAAM,UAAU,UAAU,CAAC,IAAc;IACvC,MAAM,GAAG,GAAG,IAAI,IAAI,EAAO,CAAC;IAC5B,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAErB;;;;;;OAMG;IACH,GAAG,CAAC,IAAI,CAAC,kBAAkB,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACvC,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;QAEnF,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,OAAO,IAAI,EAAE,CAAC,MAAM,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3E,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,mBAAmB,EAAE,sBAAsB,CAAC,CAAC;QAC5E,IAAI,IAAI,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;YACtB,OAAO,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,mBAAmB,EAAE,iDAAiD,CAAC,CAAC;QAC9F,CAAC;QAED,2EAA2E;QAC3E,uEAAuE;QACvE,2EAA2E;QAC3E,WAAW;QACX,MAAM,GAAG,GAAG,IAAI,EAAE,CAAC,SAAS,CAAC,CAAC;QAC9B,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;YAChC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC;YACjB,CAAC,CAAC,OAAO,GAAG,KAAK,QAAQ;gBACvB,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC;gBAChB,CAAC,CAAC,EAAE,CAAC;QACT,IAAI,CAAC;YACH,eAAe,CAAC,OAAO,CAAC,CAAC;QAC3B,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,mBAAmB,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QACvF,CAAC;QAED,uEAAuE;QACvE,2EAA2E;QAC3E,oEAAoE;QACpE,0EAA0E;QAC1E,oDAAoD;QACpD,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;YAC3C,MAAM,EAAE,CAAA,mCAAmC,KAAK,CAAC,KAAK,SAAS,CAAC;YAChE,OAAO,aAAa,CAAC,EAAE,EAAE;gBACvB,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,IAAI;gBACJ,SAAS,EAAE,KAAK,CAAC,MAAM;gBACvB,OAAO;gBACP,UAAU,EAAE,IAAI,CAAC,UAAU;aAC5B,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,OAAO,CAAC,CAAC,IAAI,CACX;YACE,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,OAAO,EAAE,eAAe,CAAC,OAAO,CAAC;YACjC,GAAG,CAAC,OAAO,CAAC,MAAM;gBAChB,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC;oBACE,OAAO,EACL,+DAA+D;wBAC/D,iCAAiC;iBACpC,CAAC;SACP,EACD,GAAG,CACJ,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH;;;;;;;OAOG;IACH,GAAG,CAAC,IAAI,CAAC,+BAA+B,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACpD,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;QAEnF,MAAM,CAAC,OAAO,CAAC,GAAG,MAAM,GAAG,CAAkC;oDACb,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC;KACjE,CAAC;QACF,yEAAyE;QACzE,yEAAyE;QACzE,6BAA6B;QAC7B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,CAAC,KAAK,EAAE,CAAC;YAC/C,OAAO,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,mBAAmB,EAAE,2BAA2B,CAAC,CAAC;QACxE,CAAC;QAED,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAmC,CAAC;QACtF,MAAM,GAAG,GAAG,IAAI,EAAE,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC,SAAS,CAAC,CAAC;QAClD,IAAI,OAAiB,CAAC;QACtB,IAAI,CAAC;YACH,OAAO,GAAG,eAAe,CACvB,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CACrF,CAAC;QACJ,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,mBAAmB,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QACvF,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,mBAAmB,EAAE,wBAAwB,CAAC,CAAC;QAExF,MAAM,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;YAC3B,MAAM,EAAE,CAAA,mCAAmC,KAAK,CAAC,KAAK,SAAS,CAAC;YAChE,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC7B,qEAAqE;gBACrE,sEAAsE;gBACtE,4DAA4D;gBAC5D,MAAM,EAAE,CAAA;;uCAEuB,KAAK,CAAC,KAAK,KAAK,OAAO,CAAC,EAAE,KAAK,MAAM,KAAK,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;;SAE7F,CAAC;YACJ,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,GAAG,GAAG,MAAM,GAAG,CAAuB;+DACe,OAAO,CAAC,EAAE;KACpE,CAAC;QACF,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH;;;;;;;;;;;;;;;OAeG;IACH,GAAG,CAAC,KAAK,CAAC,uBAAuB,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QAC7C,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,MAAM,CAAC,OAAO,CAAC,GAAG,MAAM,GAAG,CAAkC;oDACb,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC;KACjE,CAAC;QACF,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,CAAC,KAAK,EAAE,CAAC;YAC/C,OAAO,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,mBAAmB,EAAE,2BAA2B,CAAC,CAAC;QACxE,CAAC;QACD,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,IAAI,EAAE,CAAC,WAAW,CAAC,CAAC;QACjC,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,eAAe,IAAI,IAAI,KAAK,eAAe,EAAE,CAAC;YAC5E,OAAO,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,mBAAmB,EAAE,yDAAyD,CAAC,CAAC;QACtG,CAAC;QACD,MAAM,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;YAC3B,MAAM,EAAE,CAAA,mCAAmC,KAAK,CAAC,KAAK,SAAS,CAAC;YAChE,MAAM,EAAE,CAAA;;8BAEgB,IAAI;2CACS,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAA,yBAAyB;;sBAEzE,OAAO,CAAC,EAAE;OACzB,CAAC;QACJ,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,8EAA8E;IAC9E,GAAG,CAAC,GAAG,CAAC,+BAA+B,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACnD,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,MAAM,CAAC,OAAO,CAAC,GAAG,MAAM,GAAG,CAAkC;oDACb,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC;KACjE,CAAC;QACF,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,CAAC,KAAK,EAAE,CAAC;YAC/C,OAAO,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,mBAAmB,EAAE,2BAA2B,CAAC,CAAC;QACxE,CAAC;QACD,MAAM,GAAG,GAAG,MAAM,GAAG,CAAuB;+DACe,OAAO,CAAC,EAAE;KACpE,CAAC;QACF,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,OAAO,GAAG,CAAC;AACb,CAAC"}