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
+46
View File
@@ -308,6 +308,52 @@ HTTPS reverse proxy to the web app on port 3000, set `NEXTAUTH_URL` to your
public HTTPS URL, and set `COOKIE_SECURE=1`. The database remains internal to
Docker.
### Rate-limit identity behind a proxy
Set `TRUSTED_PROXY=1` in `.env` when (and only when) the proxy in front of the
web container **overwrites** `X-Real-IP` with the browser's address — the
bundled nginx template does, and `scripts/server-deploy.sh` writes the flag
for server deployments. The Next server then signs that address and forwards
it to Go, so each browser gets its own login/signup/public-endpoint bucket.
Without it every client shares the web container's bucket (20 logins/minute
for the whole instance).
Do **not** set `TRUSTED_PROXY=1` while port 3000 is reachable directly:
clients could then choose their own rate-limit identity. The web process logs
a startup warning when `NEXTAUTH_URL` is HTTPS but the flag is missing.
### First-run administrator on an exposed server
The default empty `ADMIN_EMAILS` plus bootstrap lets the first signup become
administrator. That is fine locally, but on a public host it is a race with
whoever registers first after DNS goes live. For exposed deployments:
1. Put `ALLOW_BOOTSTRAP_SIGNUP=0` in `.env` (server-deploy.sh writes it).
2. List the operator in `ADMIN_EMAILS=you@example.com` and restart the app.
3. Provision the account with `SEED_EMAIL=you@example.com SEED_PASSWORD='...' make seed`.
`SIGNUP_ENABLED=0` additionally closes self-service registration. Public
signup can never grant `ADMIN` after bootstrap is disabled; recovery is
operator-driven.
### Large-install index maintenance
Migration `007` builds GIN trigram indexes on `Page(title)` and
`Page(content)`. The migration runner wraps each file in one transaction, so
`CREATE INDEX CONCURRENTLY` is unavailable and the build holds locks that
block writes for its duration. Fresh and small installations are fine; on a
large existing `Page` table, schedule a maintenance window, or pre-build the
indexes yourself before deploying (the migration uses `IF NOT EXISTS` and
becomes a no-op):
```sql
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX CONCURRENTLY IF NOT EXISTS "Page_title_trgm_idx" ON "Page" USING gin (title gin_trgm_ops);
CREATE INDEX CONCURRENTLY IF NOT EXISTS "Page_content_trgm_idx" ON "Page" USING gin (content gin_trgm_ops);
```
Run those through `make db-shell` (they cannot run inside a transaction).
### Bundled deployment script
The repository ships a target-agnostic deployment for exactly this setup —