- rate-limiting: correct BRouter limit to 300/hour (was 60); add Overpass rate-limit requirement (120/min per IP) - security-hardening: BROUTER_AUTH_TOKEN lives in secrets.app.env, not infra.env - account-management: email-change verification does not re-auth; existing session stays valid - planner-journal-handoff: full rewrite — documents the actual JWT callback architecture (edit-in-planner → POST /api/sessions → callback endpoint), token claims, notes round-trip via GPX <metadata><desc>, session lifecycle - route-drag-reshape: rewrite to describe permanent segment midpoint handles (not proximity hover ghost marker); click-to-insert + waypoint drag model - route-splitting: rewrite to match midpoint handle model; notes geometric midpoint placement (not cursor-snapped) - road-type-coloring: redirect to route-coloring (all requirements already covered there) - osm-tile-overlays: mark profile-aware auto-enable as not yet implemented (profileOverlayDefaults exported but not wired) - osm-poi-overlays: zoom threshold is 10 not 12; user override persistence marked as not yet implemented - shared-packages: add all 7 missing packages (map-core, fit, api, db, jobs, sentry-config, correct map description); document map vs map-core boundary Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4.2 KiB
account-management Specification
Purpose
Lifecycle operations on a user's account: changing the registered email address (with re-verification) and deleting the account. Authentication-method specifics (passkeys, magic links) live in authentication-methods; profile-editing UX lives in profile-settings. This spec covers the irreversible / verification-gated operations exposed from the Journal's settings page.
Requirements
Requirement: Email change with verification
The Account settings page (/settings/account) SHALL include an "Email" section where the signed-in user can request a new email address via POST /api/settings/email. The change SHALL NOT take effect until the user clicks a verification link delivered to the new email; the original address remains active until verification completes.
Scenario: Initiate email change
- WHEN a signed-in user submits a new email address that is not already in use by another account
- THEN the server creates a verification token (purpose =
email-change) tied to the user's id and the proposed address, sends it to the new address, and the page renders a "check your inbox" confirmation.users.emailis unchanged at this point.
Scenario: Reject duplicate email
- WHEN the submitted new email is already registered to another user
- THEN the server responds with a validation error and the verification email is not sent
Scenario: Verification link applies the change
- WHEN the user follows the verification link (
/auth/verify?email-change=1&token=...) - THEN the server validates the token (matching purpose, not expired, not used), updates
users.email, marks the token used, and redirects to/settings/account. The existing session remains valid; no re-authentication is performed.
Scenario: Expired verification link
- WHEN the verification link is more than 15 minutes old or has already been used
- THEN the page shows an expired/used message and the email is unchanged
Requirement: Account deletion is irreversible and owner-bound
The Account settings page (/settings/account) SHALL include a "Delete account" section behind a confirmation step. Deletion SHALL be irreversible and SHALL cascade to all rows owned by the user (per the existing FK ON DELETE CASCADE rules: routes, activities, follows, notifications, magic tokens, sync connections, oauth tokens). Pending follow requests targeting the deleted user SHALL also be cleared.
Scenario: Authenticated user deletes their account
- WHEN a signed-in user POSTs to
/api/settings/delete-accountwith the confirmation step satisfied - THEN the server deletes
usersrow for that user (cascading per schema), invalidates the user's session, and redirects to a logged-out goodbye page
Scenario: Anonymous request is rejected
- WHEN an unauthenticated request hits
/api/settings/delete-account - THEN the server responds with HTTP 401 and no rows are deleted
Scenario: A deleted user's authored notifications survive (with actor set null)
- WHEN a user is deleted who has previously emitted notifications (e.g. follows, activity_published)
- THEN the recipient's notification rows remain, with
actor_user_idset to NULL via ON DELETE SET NULL — so historical context is preserved as "someone followed you" rather than dropping the row
Requirement: Terms re-acceptance gate (cross-cutting)
Settings pages SHALL be reachable while the user has a stale terms_version so they can read the current Terms or sign out, but action endpoints behind settings SHALL NOT execute side effects until the user has re-accepted the current Terms version. The cross-cutting gate that enforces this lives in journal-auth's "Re-accept updated Terms on next visit" requirement; this spec only documents the dependency so settings UX is read in context.
Scenario: Stale-terms user is redirected before reaching settings
- WHEN a user with a stale
terms_versionnavigates to/settings(or any sub-page like/settings/account) - THEN the root loader's Terms gate redirects them to
/auth/accept-termsfirst (perjournal-auth); after re-acceptance they return to the settings page and side effects work normally