trails/openspec/specs/account-management/spec.md
Ullrich Schäfer 37073eafd7 Spec catchup: drift fixes, account-settings split, notifications archive
Drift (specs aligned to shipped code):
- social-follows: locked-account access rule for /users/:u/followers and
  /users/:u/following (owner + accepted-follower see; non-followers of
  private get 404). Adds the follow→notification lifecycle requirement.
  Fills the placeholder Purpose.
- public-profiles: counts degrade to plain text (not anchors) for viewers
  who can't see the lists. Cross-references social-follows. Fills the
  placeholder Purpose.
- journal-auth slimmed to cookie session + Terms gate. Auth methods moved
  out (see authentication-methods).

Splits:
- account-settings (14-line stub) deleted, content split into:
  - profile-settings (display name, bio, profile_visibility)
  - account-management (email change with verification, account deletion)
  - connected-services (Wahoo + future external integrations)
- authentication-methods split out of journal-auth: passkeys
  (register/login/add/delete), magic links, 6-digit codes
  (login + register), method toggle on register/login forms,
  dev-console fallback.

New specs:
- sse-broker: /api/events, in-process broker, useUnreadNotifications
  hook, Caddy passthrough, multi-process forward-compat contract.

Archived: notifications change → openspec/changes/archive/2026-04-26-notifications.
Promoted the four delta spec files into top-level specs:
- specs/notifications/ (new capability)
- specs/activity-feed/ (added: public activity fan-out)
- specs/journal-landing/ (added: Notifications navbar entry)
- specs/social-follows/ (added: follow→notification lifecycle)

Added openspec/CAPABILITIES.md grouped index covering all 40 specs with
a Conventions section explaining cross-references, naming, and the
catch-up-vs-change rule.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 02:02:43 +02:00

4 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 settings page 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.email is 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
  • WHEN the user follows the verification link (/auth/verify-email-change?token=...)
  • THEN the server validates the token (matching purpose, not expired, not used), updates users.email, marks the token used, and signs the user back in if necessary
  • 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 settings page 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-account with the confirmation step satisfied
  • THEN the server deletes users row 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_id set 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 can read settings but is blocked from saving

  • WHEN a user with a stale terms_version navigates to /settings
  • THEN the loader-level Terms gate redirects them to /auth/accept-terms first (per journal-auth); after re-acceptance they return to the settings page and side effects work normally