Address code review findings across API, web and deployment
release gate / check (push) Failing after 6m43s

High severity:
- wire TRUSTED_PROXY through Compose/deploy/docs and warn at web startup
- bucket public rate limits on effective client IP, add per-bookmark budget,
  and forward the signed browser identity from the share page
- disable first-run bootstrap in server deployments and document seeding
- serialize last-admin demotion in one advisory-locked transaction

Medium/low:
- cap and batch share recipient inserts
- add a shared outbound budget for preview/CardDAV/IMAP/signal work
- pin CardDAV dials to 443 and bound IMAP logout
- fix rightward kanban column drags and normalize cards on column delete
- classify backend outages in auth guards, add request timeouts and an
  error boundary, remove the dead login route, surface throttling
- enforce pinned/unpinned page reorder grouping server-side
- avoid preview work for stale bookmark versions; drop duplicate dialog fetch
- sequence-guard collection/tag/calendar/quick-find loads
- validate property config and fix MULTI_SELECT/DATE filter inputs
- document index maintenance; apply same-origin check to sign-out
- trim signup names, unique shared dates and kanban keys (migration 010),
  reset reference cache on identity changes, hide OIDC issuer, return 413
  for oversized direct bodies, escape Markdown link destinations
- remove dead template files and duplicate test helper

Verified with go build/vet, full PostgreSQL race suite, db convergence,
tsc, both ESLint configs and a Next production build.
This commit is contained in:
Andrew S Erwin
2026-10-20 00:13:30 -05:00
parent 5eab58f99c
commit 4e421f6ea2
58 changed files with 1419 additions and 333 deletions
+18 -12
View File
@@ -7,6 +7,10 @@ Successful responses are JSON unless marked Markdown. Errors are
`{"error":"message"}`. Data endpoints require authentication: **401** without
a valid session, **404** for another user's private record, **403** for
non-admin administration, **400** for invalid input, **409** for duplicates.
Request bodies over 1 MiB answer **413**. Rate-limited endpoints answer **429**
with `Retry-After`; public bookmark lookups are limited per effective client
and per bookmark id, and previews per client. Share recipient lists are
capped (100 per bookmark/tag/collection share, 200 per shared date).
## Authentication
@@ -17,19 +21,19 @@ non-admin administration, **400** for invalid input, **409** for duplicates.
| POST | `/api/signup` | 201 `{ok:true}` — the same uniform body for fresh, duplicate and operator-reserved addresses (review CR-03: no existence oracle); body `{email,password,name?}` |
| POST | `/api/auth/login` | `{ok:true,user:{id,name,email,image,role}}` and signed HttpOnly cookie |
| GET | `/api/auth/session` | `{user}` or `null` for no valid cookie |
| POST | `/api/auth/signout` | `{ok:true}` and expired cookie |
| POST | `/api/auth/signout` | `{ok:true}` and expired cookie; same-origin `Origin` required when present |
| GET | `/api/me` | `{user,effectiveUser,isAdmin,viewingAs}`; real and effective identities |
| GET | `/api/auth/oidc/status` | `{enabled,issuerSet,clientIdSet,clientSecretSet,issuer}`; never includes secrets |
| GET | `/api/auth/oidc/status` | `{enabled,issuerSet,clientIdSet,clientSecretSet}`; never includes secrets or the issuer URL |
| GET | `/api/auth/oidc/login` | **501**, client-owned Authelia integration stub |
| GET, POST | `/api/auth/callback/authelia` | **501**, client-owned callback stub |
The browser's NextAuth `/api/auth/*` endpoints remain on the Next server.
Direct Go API consumers use the `computer_session` cookie from login.
Go login accepts JSON and rejects foreign browser origins. The custom Next.js
login endpoint also requires the configured same-origin `Origin` header.
Login and signup are throttled per client IP (429 with `Retry-After`), plus a
consecutive-failure lockout per IP+email on login; loopback clients are
exempt. Browser traffic arrives through the trusted Next proxy, which
Go login accepts JSON and rejects foreign browser origins. Login and signup
are throttled per client IP (429 with `Retry-After`), plus a
consecutive-failure lockout per IP+email on login. Login applies to every
peer, loopback included; signup exempts only a direct loopback peer. Browser
traffic arrives through the trusted Next proxy, which
forwards the observed client IP in `X-Computer-Client-Ip` signed with
HMAC-SHA256 under the shared secret (`X-Computer-Client-Sig`); Go buckets on
that identity only when the signature verifies and falls back to the direct
@@ -50,10 +54,12 @@ Bookmark responses are permission-specific. Owners receive camelCase scalar
fields plus `tags:[{tag}]`, `contact`, `sharedWith:[{userId,user}]` and
`owner` (with email). Authenticated shared readers receive a narrowed
allowlist — URL, title/description, notes, preview metadata, `dueDate`,
`isPublic`, `kanbanStatus`, timestamps, `tags` and `owner:{id,name}` — never
the owner's contact record, the recipient list, alerts, mailbox watches or
board placement. Detail responses also include `pageLinks:[{pageId,page}]`,
limited to the effective user's pages.
`isPublic`, timestamps, `tags` and `owner:{id,name}` — never the owner's
contact record, the recipient list, alerts, mailbox watches or board
placement (`kanbanColumnId`/`kanbanPosition`). The legacy `kanbanStatus`
badge IS included and is status, not placement; it is documented here so the
projection and UI agree. Detail responses also include
`pageLinks:[{pageId,page}]`, limited to the effective user's pages.
| Method | Path | Returns / behavior |
|---|---|---|
@@ -231,7 +237,7 @@ already see.
| POST | `/api/imap/test` | `{ok:true}` after real connection/login; same mailbox fields and port rules |
| GET | `/api/imap/check` | `{matches:[{bookmarkId,title,from,externalId,occurredAt}]}` for legacy watchers; if a search fails after finding matches, the partial `matches` are returned with an `error` field |
| GET | `/api/shared-dates` | Created/received dates with `creator` and `recipient` |
| POST | `/api/shared-dates` | 201 array of created shares for `{date,note?,recipientIds:[...]}` |
| POST | `/api/shared-dates` | 201 array of created shares for `{date,note?,recipientIds:[...]}`. Sharing a date also grants read-only access to the creator's bookmarks due that day (including notes); the UI discloses this. Re-sharing the same date updates its note. |
| DELETE | `/api/shared-dates/{id}` | `{ok:true}`, creator only |
| GET | `/api/users/search?q=...` | Safe user projections; minimum two characters, maximum ten results |