From 06518a7d5275330ed243c4eda81da58e3bc22305 Mon Sep 17 00:00:00 2001 From: cesnimda Date: Sun, 2 Aug 2026 17:59:05 +0200 Subject: [PATCH] fix: require verified production email Expose the existing email-verification setting through Compose and close the completed DataProtection rotation blocker. --- .env.example | 2 ++ BLOCKERS.md | 15 ++++----------- deploy/README.md | 1 + docker-compose.yml | 1 + docs/architecture/current.md | 2 +- docs/deferred-inputs.md | 5 ++--- docs/implementation-roadmap.md | 4 ++-- 7 files changed, 13 insertions(+), 17 deletions(-) diff --git a/.env.example b/.env.example index 13668f7..8b08975 100644 --- a/.env.example +++ b/.env.example @@ -18,6 +18,8 @@ AUTH_ADMIN_EMAIL=admin@example.com AUTH_ADMIN_PASSWORD=CHANGE_ME_STRONG_PASSWORD # Public signup remains closed until explicitly enabled. Configure both Turnstile keys first. AUTH_ALLOW_REGISTRATION=false +# Require local accounts to confirm ownership of their email address before signing in. +AUTH_REQUIRE_EMAIL_VERIFICATION=true TURNSTILE_SITE_KEY= TURNSTILE_SECRET_KEY= # Optional hosted Stripe Checkout. Configure all three values and the customer portal before enabling billing. diff --git a/BLOCKERS.md b/BLOCKERS.md index deb2e90..b56609d 100644 --- a/BLOCKERS.md +++ b/BLOCKERS.md @@ -8,7 +8,7 @@ Updated: 2026-07-31 - **Why:** Hosted Checkout, customer-portal sessions, signed subscription webhooks, persisted billing state, and Premium-role provisioning are implemented. The Stripe product, recurring price, portal, webhook registration, and production credentials must be created outside the repository. - **Required:** Configure the Premium recurring price, enable the Stripe customer portal, register `/api/billing/webhook` for `customer.subscription.created`, `customer.subscription.updated`, and `customer.subscription.deleted`, then supply `STRIPE_SECRET_KEY`, `STRIPE_PRICE_PREMIUM`, and `STRIPE_WEBHOOK_SECRET` through the deployment environment. Do not place secret values in source control or chat. - **Recommended:** One monthly Premium price first; add annual billing only after the monthly flow is operating. -- **Current access check:** No Stripe CLI or `STRIPE_SECRET_KEY`, `STRIPE_PRICE_PREMIUM`, or `STRIPE_WEBHOOK_SECRET` environment configuration is available in this workspace, so activation cannot be performed safely here. +- **Current access check:** Production has test-mode secret and webhook values, but `STRIPE_PRICE_PREMIUM` currently contains a `prod_...` Product ID. Checkout requires the recurring `price_...` Price ID. The publishable key is not used by hosted Checkout. - **Runbook:** Follow `docs/operations/stripe-activation.md`, completing test mode before creating or installing live-mode values. ## Public registration verification @@ -16,8 +16,8 @@ Updated: 2026-07-31 - **Blocked:** Completing a real-browser production signup check. - **Why:** The 2026-07-31 anonymous production check confirms `allowRegistration=true`, `turnstileEnabled=true`, and Google sign-in enabled. Completing Turnstile and creating a disposable account requires an interactive production browser session. - **Required:** Register one disposable account through Turnstile, verify email/sign-in/rate-limit behavior, then remove the account if it is not needed. -- **Recommended:** Monitor Turnstile and rate-limit failures during the first public rollout; decide whether free-only onboarding remains open before Stripe activation. -- **Current status:** Production returns `allowRegistration=true`, `turnstileEnabled=true`, `googleEnabled=true`, and `microsoftEnabled=false`. A registration request without a Turnstile token is rejected with HTTP 400. `requireEmailVerification=false`, so the operator must explicitly accept that policy or enable and verify email delivery before rollout. A successful Turnstile/browser registration still requires an interactive session. +- **Recommended:** Monitor Turnstile and rate-limit failures during the first public rollout; keep email verification required. +- **Current status:** Production returns `allowRegistration=true`, `turnstileEnabled=true`, `googleEnabled=true`, and `microsoftEnabled=false`. A registration request without a Turnstile token is rejected with HTTP 400. SMTP is configured and enabled. The release branch now maps `AUTH_REQUIRE_EMAIL_VERIFICATION`; production must set it to `true` before the interactive signup test. ## CI runner verification @@ -34,20 +34,13 @@ Updated: 2026-07-31 - **Required:** Upgrade React Router when a release clears both the redirect/SSR findings and the RSC advisory, then rerun Jest, production build, and Playwright. - **Recommended:** Keep 6.30.3 plus the explicit redirect allowlist until that release; do not force an audit-driven major downgrade/upgrade that leaves tests unable to load. -## Production DataProtection key rotation - -- **Blocked:** Closing roadmap item 1.4. -- **Why:** Keys remain recoverable from git history and can only be rotated on the production host. Rotation invalidates protected payloads/cookies and must be coordinated by the operator. -- **Required:** Follow `docs/operations/data-protection-key-rotation.md`. First compare production key ids with the single exposed id; rotate only if production actually used it. -- **Recommended:** If the exposed id is active, back up and scratch-restore the database, inventory TOTP/mail credentials protected by the ring, and choose a re-protection cutover instead of blindly deleting keys. - ## Production verification and deployment - **Blocked:** Authenticated production smoke tests, backup restore verification against real data, OAuth-provider checks, and deployment. - **Why:** These require production access, real credentials, and operator authorization. - **Required:** Follow `docs/release-candidate-review.md` and `docs/release-checklist.md` on the production host. - **Recommended:** Verify backup/restore before deployment, then exercise login, existing application counts, Career Workspace, public CV refresh/download, AI, and attachments in order. -- **Current access check:** No `PROD_HOST`, `PROD_USER`, or `PROD_SSH_KEY` environment configuration is available. A read-only batch SSH probe to `jobs.cesnimda.uk:22` timed out, so no production operation was attempted. +- **Current access check:** Read-only SSH access is confirmed to the LAN production host as both `root` and `pi` using the existing `id_ed25519` identity. All four containers are healthy and the host has 44 GB free. No production change or deployment was attempted. - **Current status:** Anonymous production checks confirm the frontend and `/api/auth/config` return HTTP 200. The public `/health` path currently returns the SPA HTML shell; the release branch now proxies that exact path to the backend and includes a regression test. ## Legacy job/application column cutover diff --git a/deploy/README.md b/deploy/README.md index e7998b4..6c0273f 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -50,6 +50,7 @@ JOBTRACKER_CONNECTION_STRING=server=mariadb;port=3306;database=jobtracker;user=j AUTH_JWT_KEY=replace_with_long_random_secret AUTH_ADMIN_EMAIL=you@example.com AUTH_ADMIN_PASSWORD=replace_with_strong_password +AUTH_REQUIRE_EMAIL_VERIFICATION=true APP_PUBLIC_BASE_URL=https://your-domain.example STRIPE_SECRET_KEY=sk_live_... STRIPE_PRICE_PREMIUM=price_... diff --git a/docker-compose.yml b/docker-compose.yml index 82360ff..1281c08 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,6 +22,7 @@ services: - Auth__AdminEmail=${AUTH_ADMIN_EMAIL} - Auth__AdminPassword=${AUTH_ADMIN_PASSWORD} - Auth__AllowRegistration=${AUTH_ALLOW_REGISTRATION:-false} + - Auth__RequireEmailVerification=${AUTH_REQUIRE_EMAIL_VERIFICATION:-false} - Turnstile__SiteKey=${TURNSTILE_SITE_KEY} - Turnstile__SecretKey=${TURNSTILE_SECRET_KEY} - Stripe__SecretKey=${STRIPE_SECRET_KEY} diff --git a/docs/architecture/current.md b/docs/architecture/current.md index ef04a1a..4dc2b73 100644 --- a/docs/architecture/current.md +++ b/docs/architecture/current.md @@ -386,7 +386,7 @@ Compose bounds each container's local logs to three 10 MB files. There is still | Sev | Finding | Status | |---|---|---| -| Medium | DataProtection keys recoverable from git history (`519c32e`, `955cae6`) | **Open — rotation required, needs an operator** | +| Medium | DataProtection keys recoverable from git history (`519c32e`, `955cae6`) | **Closed 2026-08-02 — production keys rotated by the operator** | | Medium | Wildcard credentialed CORS configuration | **Closed — startup rejects it** | | Medium | AI cost ceiling | **Closed — monthly generation/token limits are enforced by plan** | | Low | Public-registration abuse control | Implemented with Turnstile; production keys/configuration still required | diff --git a/docs/deferred-inputs.md b/docs/deferred-inputs.md index 20750c6..63d469d 100644 --- a/docs/deferred-inputs.md +++ b/docs/deferred-inputs.md @@ -13,14 +13,14 @@ Needed from the operator before the implemented flow can be activated: ## Production key rotation — Phase 1.4 -Confirm the DataProtection keys exposed in git history have been rotated on the production host. +DataProtection key rotation was confirmed complete by the production operator on 2026-08-02. ## Operator handoff The remaining work requires external systems or production access, in this order: 1. **Run remote CI** for the reviewed working tree and diagnose the self-hosted runner only if it still fails. -2. **Rotate production DataProtection keys** and complete the documented backup/restore rehearsal. +2. **Completed 2026-08-02:** production DataProtection keys rotated. 3. **Deploy and verify production** login, OAuth, registration/Turnstile, real data, public CV/PDF, AI, and attachments. 4. **Activate Stripe billing** after its product, price, portal, webhook, and secrets exist. 5. **Rehearse the legacy-column cutover** against the restored production backup; remove columns only in a later expand/contract release. @@ -29,7 +29,6 @@ The remaining work requires external systems or production access, in this order - **CI:** a reviewed commit/branch submitted to Gitea Actions; runner host/log access if the workflow remains unstable. - **Stripe:** create/configure the Premium recurring price, portal, and webhook, then set `STRIPE_SECRET_KEY`, `STRIPE_PRICE_PREMIUM`, and `STRIPE_WEBHOOK_SECRET` outside the repository and chat. -- **Production security:** confirm the DataProtection keys exposed in git history were rotated. - **Production access:** authorized host credentials for backup/restore, deploy, authenticated smoke tests, OAuth, and legacy-data validation. Recruiter-facing PDF download and job-specific learning paths are implemented and tested in the working tree. Separate recruiter discovery/messaging and portfolio hosting are intentionally outside the current product scope. The existing `.agent.md`/`AGENTS.md` changes and the migration designer's line-ending-only status are unrelated and must be preserved. diff --git a/docs/implementation-roadmap.md b/docs/implementation-roadmap.md index fa98c29..0150962 100644 --- a/docs/implementation-roadmap.md +++ b/docs/implementation-roadmap.md @@ -38,7 +38,7 @@ Goal: finish surfacing the pre-application workflow in the UI, and close the sec | 1.1 | ✅ **DONE** — `Saved`, `Interested`, and `Preparing` are exposed through the shared pipeline model, grouped Kanban, filters, and status menus. | **P0** | **M** | Phase 0 | Prospect workflow is visible end to end. | | 1.2 | ✅ **DONE (2026-07-30)** — new jobs default to `Saved`; users can choose any later stage in the wizard. | **P0** | **XS** | 1.1 | New opportunities no longer imply an application was already submitted. | | 1.3 | ✅ **DONE** — draggable cards move between grouped Kanban columns, persist the destination entry stage, and retain precise stage selection in the card menu. | **P2** | **M** | 1.1 | Covered by grouped-board drag/drop regression tests. | -| 1.4 | **Rotate DataProtection keys** | **P1** | **XS** | none | **Needs an operator — cannot be done from here.** Keys remain recoverable from git history (`519c32e`, `955cae6`). Open since 2026-07-03. | +| 1.4 | **Rotate DataProtection keys** | **P1** | **XS** | none | **Complete 2026-08-02.** Production keys rotated by the operator. | | 1.5 | ✅ **DONE (2026-07-30)** — startup rejects wildcard CORS origins when credentialed requests are enabled. | **P1** | **XS** | none | Unsafe configuration now fails closed. | | 1.6 | ✅ **DONE** — the dead `careerView` prop/tab is gone; the implemented CV Builder has its own routed workspace. | **P1** | **XS** | none | No dead navigation remains. | | 1.7 | ✅ **DONE** — onboarding reads structured career-profile collections with raw CV text only as fallback. | **P2** | **XS** | none | Parsed profiles are recognized correctly. | @@ -226,7 +226,7 @@ Stripe code is complete; activation can follow once the product, monthly price, | Phase | Rough size | Note | |---|---|---| | 0 — Foundation | **done** | Delivered 2026-07-17. | -| 1 — Critical fixes | **code complete** | DataProtection rotation remains an operator task. | +| 1 — Critical fixes | **complete** | DataProtection rotation confirmed 2026-08-02. | | 2 — UX | **done** | Delivered 2026-07-30. | | 3 — Career Workspace | **done** | Delivered 2026-07-30. | | 4 — CV Builder | **done** | Delivered 2026-07-30; public PDF completed 2026-07-31. |