Phase 8: dependency security upgrades, user management, unsubscribe compliance #22

Merged
vmanne-poc merged 5 commits from phase-8 into main 2026-10-14 23:56:03 -04:00
Owner

Phase 8 (milestone). First phase delivered through a PR; see CONTRIBUTING.md.

Summary

1. Dependency security (2bbf466)

  • NestJS 10 → 11.2.7 (Express 4 → 5), @nestjs/config 4, @nestjs/schedule 6, @nestjs/jwt / @nestjs/passport 11, nodemailer 6 → 10, Jest 29 → 30.
  • npm audit --omit=dev: 12 vulnerabilities (4 high) → 0.
  • Not NestJS 12: it's ESM-only and would mean converting the backend's module system.

2. Unsubscribe compliance fix (70f4d1b), found while smoke-testing the upgrade

  • Emails without a template had no unsubscribe link. A footer is now added when the content lacks one.
  • Adds List-Unsubscribe + RFC 8058 List-Unsubscribe-Post (one-click button in Gmail/Outlook).
  • Unsubscribes no longer count as clicks.
  • GET on the link shows a confirmation page; POST unsubscribes, so link scanners can't unsubscribe people.

3. User management (2f3458d)

  • POST /auth/change-password + Account page. Ends other sessions (token version re-checked per request).
  • Root-only GET/POST /users + Users page: how accounts are created when registration is disabled.
  • Project members with owner / member roles + Members page. Only owners delete projects or manage members; a project always keeps one owner.
  • Emails stored lowercase; sign-in case-insensitive. Web app returns to sign-in when a session ends.

4. Docs (3a5339e): CHANGELOG 0.8.0 and CONTRIBUTING.md.

5. Documentation moved out of the code repo (8240d91)

  • docs/internal/, docs/user/, and ROADMAP.md are removed from the repository and kept by the owners outside it.
  • Public places: user guide on the wiki; plan/status in milestones and issues; README, CHANGELOG, CONTRIBUTING, LICENSE in the repo.
  • Note: the removed files remain in git history of commits before this PR.

Breaking / behavior changes

  • GET /track/unsubscribe/:id no longer unsubscribes; POST does. Links in already-sent emails still work (they open the confirmation page).
  • Project members can no longer delete a project (owners only). All existing memberships are owner, so nobody loses access today.
  • Schema: adds User.tokenVersion (default 0). Applied automatically by db push; existing sessions stay valid.

How it was tested

  • docker compose run --rm --build -T backend-test: 45 unit + 29 e2e passing (was 43 + 22). New e2e: admin accounts (incl. 403 for non-root, case-insensitive duplicates), members add/role/remove/leave, owner-only actions, last-owner protection, password change ending old sessions, unsubscribe footer/header URL, GET-safe vs POST unsubscribe.
  • Frontend production build (strict tsc) passes.
  • Live smoke test on the running stack with existing data: existing admin login and sessions still valid; campaign without a template delivered to Mailpit with footer link, List-Unsubscribe, and List-Unsubscribe-Post; GET didn't unsubscribe, one-click POST did; click count stayed 0; Account/Users/Members pages served.
  • Commit 5 changes only Markdown and a Dockerfile comment.

Not in this PR

  • Password reset, account disable/delete (#23).
  • Dev-only tooling (@nestjs/cli, Jest) still has moderate audit findings; production is clean.

Closes #14
Closes #15
Closes #21

Phase 8 ([milestone](https://git.vmanne.com/vmanne-poc/Postak/milestone/8)). First phase delivered through a PR; see `CONTRIBUTING.md`. ## Summary **1. Dependency security** (`2bbf466`) - NestJS 10 → 11.2.7 (Express 4 → 5), `@nestjs/config` 4, `@nestjs/schedule` 6, `@nestjs/jwt` / `@nestjs/passport` 11, nodemailer 6 → 10, Jest 29 → 30. - `npm audit --omit=dev`: **12 vulnerabilities (4 high) → 0**. - Not NestJS 12: it's ESM-only and would mean converting the backend's module system. **2. Unsubscribe compliance fix** (`70f4d1b`), found while smoke-testing the upgrade - Emails without a template had **no unsubscribe link**. A footer is now added when the content lacks one. - Adds `List-Unsubscribe` + RFC 8058 `List-Unsubscribe-Post` (one-click button in Gmail/Outlook). - Unsubscribes no longer count as clicks. - `GET` on the link shows a confirmation page; `POST` unsubscribes, so link scanners can't unsubscribe people. **3. User management** (`2f3458d`) - `POST /auth/change-password` + **Account** page. Ends other sessions (token version re-checked per request). - Root-only `GET/POST /users` + **Users** page: how accounts are created when registration is disabled. - Project members with `owner` / `member` roles + **Members** page. Only owners delete projects or manage members; a project always keeps one owner. - Emails stored lowercase; sign-in case-insensitive. Web app returns to sign-in when a session ends. **4. Docs** (`3a5339e`): CHANGELOG 0.8.0 and `CONTRIBUTING.md`. **5. Documentation moved out of the code repo** (`8240d91`) - `docs/internal/`, `docs/user/`, and `ROADMAP.md` are removed from the repository and kept by the owners outside it. - Public places: user guide on the wiki; plan/status in milestones and issues; README, CHANGELOG, CONTRIBUTING, LICENSE in the repo. - Note: the removed files remain in git history of commits before this PR. ## Breaking / behavior changes - `GET /track/unsubscribe/:id` no longer unsubscribes; `POST` does. Links in already-sent emails still work (they open the confirmation page). - Project members can no longer delete a project (owners only). All existing memberships are `owner`, so nobody loses access today. - Schema: adds `User.tokenVersion` (default 0). Applied automatically by `db push`; existing sessions stay valid. ## How it was tested - `docker compose run --rm --build -T backend-test`: **45 unit + 29 e2e passing** (was 43 + 22). New e2e: admin accounts (incl. 403 for non-root, case-insensitive duplicates), members add/role/remove/leave, owner-only actions, last-owner protection, password change ending old sessions, unsubscribe footer/header URL, GET-safe vs POST unsubscribe. - Frontend production build (strict `tsc`) passes. - Live smoke test on the running stack with existing data: existing admin login and sessions still valid; campaign without a template delivered to Mailpit with footer link, `List-Unsubscribe`, and `List-Unsubscribe-Post`; GET didn't unsubscribe, one-click POST did; click count stayed 0; Account/Users/Members pages served. - Commit 5 changes only Markdown and a Dockerfile comment. ## Not in this PR - Password reset, account disable/delete (#23). - Dev-only tooling (`@nestjs/cli`, Jest) still has moderate audit findings; production is clean. Closes #14 Closes #15 Closes #21
Clears all 12 production npm audit findings (4 high). Express 4 -> 5 comes with platform-express 11. nodemailer 10 ships its own types, so @types/nodemailer is dropped. Stayed on NestJS 11: 12 is ESM-only. Verified: build, 43 unit + 22 e2e tests, live send via Mailpit with tracking. Refs #14
Emails without {{ unsubscribe_url }} had no way to opt out. Adds a footer when missing, RFC 2369/8058 List-Unsubscribe + one-click headers, stops counting unsubscribes as clicks, and makes GET a confirmation page so link scanners cannot unsubscribe people. Closes #21
POST /auth/change-password ends other sessions via a token version re-checked on every request. Root-only GET/POST /users. Owner/member roles with /projects/:id/members; only owners delete projects; a project always keeps one owner. Emails normalized to lowercase. UI: Account, Users, and Members pages; 401 returns to sign-in. Tests: 45 unit + 29 e2e. Refs #15
docs/internal, docs/user, and ROADMAP.md are now kept by the owners outside this repo. The user guide is published on the wiki; plan and status are in milestones and issues. README, CONTRIBUTING, and a Dockerfile comment no longer point at the removed files.
vmanne-poc deleted branch phase-8 2026-10-14 23:56:04 -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!22
No description provided.