Phase 9: migrations, account controls, frontend tests, SES bounces #24

Merged
vmanne-poc merged 6 commits from phase-9 into main 2026-10-15 01:03:23 -04:00
Owner

Summary

Phase 9 (v0.9.0). Each issue is its own commit.

  • #16 Versioned migrations. prisma db push --accept-data-loss is gone. 0_init is the v0.8.0 baseline, and containers run scripts/migrate.sh. On first start it adopts an existing pre-v0.9.0 database: syncs it to the frozen v0.8.0 schema without allowing data loss, then marks 0_init as applied and runs prisma migrate deploy. The prisma CLI is now a real runtime dependency.
  • #23 Account controls. The administrator can disable/enable an account (its sessions end at once), set a temporary password (the user must change it), or delete an account; projects where that user was the only owner go to the administrator. Forgot-password by email uses system mail (POSTAK_SMTP_URL, POSTAK_MAIL_FROM, POSTAK_APP_URL). Reset links are single-use, last 1 hour, and are throttled, and the response never reveals whether an account exists.
  • #18 Frontend tests. Vitest + Testing Library (jsdom, API mocked) cover the segment rule helpers, Login, ResetPassword, Users, and Layout, run by a new frontend-test compose service. Getting test tooling without known advisories meant upgrading the frontend toolchain: Vite 8, plugin-react 6, React Router 7.18 (fixes an open-redirect advisory), building on Node 22.
  • #17 SES bounces and complaints. New endpoint POST /api/webhooks/ses for SNS. It verifies SNS signatures (v1/v2) with certificates fetched only from SNS hosts, accepts only topics in the POSTAK_SES_SNS_TOPIC_ARNS allowlist, and is off when that list is empty. Hard bounces mark the contact BOUNCED and complaints mark it COMPLAINED; neither is mailed again. Soft bounces are only logged as events, and redeliveries are deduplicated. SES senders now store SES's own message ID.
  • The backend also moves to Node 22, since Node 20 is end-of-life.

Migrations added: user_account_controls and contact_bounce_statuses. Both are additive.

Upgrading: back up the database first.

Testing

  • Backend: 53 unit and 35 e2e tests, plus both migration paths (adopt a db push database, build fresh) and a schema-drift check (docker compose run --rm --build -T backend-test).
  • Frontend: 28 tests (docker compose run --rm --build -T frontend-test).
  • npm audit: backend (--omit=dev) 0, frontend 0.
  • Live stack:
    • The real v0.8.0 database was backed up, then adopted and migrated with its data intact.
    • Through nginx with Mailpit: forgot password → email → reset (reusing the link gets 400); temporary password (mustChangePassword, old sessions get 401); disable (session 401, login 403); delete with project hand-over.
    • The webhook returns 404 when no topic is configured.
    • The production bundle renders in headless Edge.

Not verified

  • No real AWS SNS/SES subscription yet. That path is covered by tests that use locally signed SNS messages.
  • The new Users-page actions were not tested by hand in a browser; component tests cover them.

Closes #16
Closes #23
Closes #18
Closes #17

## Summary Phase 9 (v0.9.0). Each issue is its own commit. - **#16 Versioned migrations.** `prisma db push --accept-data-loss` is gone. `0_init` is the v0.8.0 baseline, and containers run `scripts/migrate.sh`. On first start it adopts an existing pre-v0.9.0 database: syncs it to the frozen v0.8.0 schema without allowing data loss, then marks `0_init` as applied and runs `prisma migrate deploy`. The `prisma` CLI is now a real runtime dependency. - **#23 Account controls.** The administrator can disable/enable an account (its sessions end at once), set a temporary password (the user must change it), or delete an account; projects where that user was the only owner go to the administrator. Forgot-password by email uses system mail (`POSTAK_SMTP_URL`, `POSTAK_MAIL_FROM`, `POSTAK_APP_URL`). Reset links are single-use, last 1 hour, and are throttled, and the response never reveals whether an account exists. - **#18 Frontend tests.** Vitest + Testing Library (jsdom, API mocked) cover the segment rule helpers, Login, ResetPassword, Users, and Layout, run by a new `frontend-test` compose service. Getting test tooling without known advisories meant upgrading the frontend toolchain: Vite 8, plugin-react 6, React Router 7.18 (fixes an open-redirect advisory), building on Node 22. - **#17 SES bounces and complaints.** New endpoint `POST /api/webhooks/ses` for SNS. It verifies SNS signatures (v1/v2) with certificates fetched only from SNS hosts, accepts only topics in the `POSTAK_SES_SNS_TOPIC_ARNS` allowlist, and is off when that list is empty. Hard bounces mark the contact BOUNCED and complaints mark it COMPLAINED; neither is mailed again. Soft bounces are only logged as events, and redeliveries are deduplicated. SES senders now store SES's own message ID. - The backend also moves to Node 22, since Node 20 is end-of-life. Migrations added: `user_account_controls` and `contact_bounce_statuses`. Both are additive. **Upgrading:** back up the database first. ## Testing - Backend: 53 unit and 35 e2e tests, plus both migration paths (adopt a `db push` database, build fresh) and a schema-drift check (`docker compose run --rm --build -T backend-test`). - Frontend: 28 tests (`docker compose run --rm --build -T frontend-test`). - `npm audit`: backend (`--omit=dev`) 0, frontend 0. - Live stack: - The real v0.8.0 database was backed up, then adopted and migrated with its data intact. - Through nginx with Mailpit: forgot password → email → reset (reusing the link gets 400); temporary password (`mustChangePassword`, old sessions get 401); disable (session 401, login 403); delete with project hand-over. - The webhook returns 404 when no topic is configured. - The production bundle renders in headless Edge. ## Not verified - No real AWS SNS/SES subscription yet. That path is covered by tests that use locally signed SNS messages. - The new Users-page actions were not tested by hand in a browser; component tests cover them. Closes #16 Closes #23 Closes #18 Closes #17
Baseline migration 0_init (v0.8.0 schema). Containers run scripts/migrate.sh, which adopts pre-v0.9.0 db-push databases (sync to frozen baseline without data loss, mark 0_init applied) then runs prisma migrate deploy. Tests cover both migration paths and fail on schema drift. prisma CLI is now a runtime dependency.

Refs #16
Admin can disable/enable accounts (ends sessions), set a temporary password that must be changed, and delete accounts (sole-owned projects go to the admin). Self-service reset by email via system mail (POSTAK_SMTP_URL / POSTAK_MAIL_FROM), single-use one-hour links, throttled, no account enumeration. Migration user_account_controls.

Closes #23
Tests for segment rule helpers, Login, ResetPassword, Users, and Layout (28 tests, API mocked via fetch stub). frontend-test compose service. Vite 8, plugin-react 6, React Router 7.18, Node 22 build; npm audit 0.

Closes #18
POST /api/webhooks/ses verifies SNS signatures (v1/v2, certs only from SNS hosts) and an allowlist of topic ARNs (POSTAK_SES_SNS_TOPIC_ARNS), confirms subscriptions, and matches SES message IDs. Hard bounces -> BOUNCED, complaints -> COMPLAINED (never mailed again); soft bounces recorded as events; redeliveries deduplicated. SES senders store the SES message ID from the SMTP reply. Contact counts, project page, and daily snapshots updated. Migration contact_bounce_statuses.

Closes #17
vmanne-poc deleted branch phase-9 2026-10-15 01:03:23 -04:00
Sign in to join this conversation.
No reviewers
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
vmanne-poc/Postak!24
No description provided.