From 7e253caf643fc4f967775afa2113663f30005284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ullrich=20Sch=C3=A4fer?= Date: Sun, 29 Mar 2026 22:41:51 +0200 Subject: [PATCH] Archive account-settings change, sync specs All 37 tasks complete. Synced delta specs to main: - New: openspec/specs/account-settings/ - Updated: openspec/specs/journal-auth/ Co-Authored-By: Claude Opus 4.6 (1M context) --- openspec/changes/account-settings/tasks.md | 63 --------- .../.openspec.yaml | 0 .../2026-03-29-account-settings}/design.md | 0 .../2026-03-29-account-settings}/proposal.md | 0 .../specs/account-settings/spec.md | 0 .../specs/journal-auth/spec.md | 0 .../2026-03-29-account-settings/tasks.md | 63 +++++++++ openspec/specs/account-settings/spec.md | 83 +++++++++++ openspec/specs/journal-auth/spec.md | 131 ++---------------- 9 files changed, 161 insertions(+), 179 deletions(-) delete mode 100644 openspec/changes/account-settings/tasks.md rename openspec/changes/{account-settings => archive/2026-03-29-account-settings}/.openspec.yaml (100%) rename openspec/changes/{account-settings => archive/2026-03-29-account-settings}/design.md (100%) rename openspec/changes/{account-settings => archive/2026-03-29-account-settings}/proposal.md (100%) rename openspec/changes/{account-settings => archive/2026-03-29-account-settings}/specs/account-settings/spec.md (100%) rename openspec/changes/{account-settings => archive/2026-03-29-account-settings}/specs/journal-auth/spec.md (100%) create mode 100644 openspec/changes/archive/2026-03-29-account-settings/tasks.md create mode 100644 openspec/specs/account-settings/spec.md diff --git a/openspec/changes/account-settings/tasks.md b/openspec/changes/account-settings/tasks.md deleted file mode 100644 index f685b36..0000000 --- a/openspec/changes/account-settings/tasks.md +++ /dev/null @@ -1,63 +0,0 @@ -## 1. Settings Route & Layout - -- [ ] 1.1 Create `apps/journal/app/routes/settings.tsx` with loader auth guard (redirect to `/auth/login` if unauthenticated) -- [ ] 1.2 Register route in `apps/journal/app/routes.ts` -- [ ] 1.3 Layout with three sections: Profile, Security, Account (anchor links for navigation) - -## 2. Profile Section - -- [ ] 2.1 Display name and bio form fields, pre-filled from current user data -- [ ] 2.2 Create `POST /api/settings/profile` action to update display name and bio -- [ ] 2.3 Register API route in `routes.ts` -- [ ] 2.4 Success/error feedback on save - -## 3. Security Section — Passkey Management - -- [ ] 3.1 Load user's credentials in settings loader (device type, transports, createdAt) -- [ ] 3.2 Render passkey list with friendly transport labels and registration date -- [ ] 3.3 Add passkey button using existing `addPasskeyStart`/`addPasskeyFinish` flow, hidden when WebAuthn unsupported -- [ ] 3.4 Create `POST /api/settings/passkey/delete` action to remove a credential by ID -- [ ] 3.5 Register API route in `routes.ts` -- [ ] 3.6 Delete confirmation dialog, with last-passkey warning when applicable - -## 4. Account Section — Email Change - -- [ ] 4.1 Email display with "Change" button revealing input for new email -- [ ] 4.2 Create `initiateEmailChange(userId, newEmail)` in auth.server.ts — generates magic token with `newEmail` metadata -- [ ] 4.3 Create `POST /api/settings/email` action to initiate email change -- [ ] 4.4 Register API route in `routes.ts` -- [ ] 4.5 Update `verifyMagicToken` to handle email-change tokens (update user email) -- [ ] 4.6 Send verification email to new address using existing email templates - -## 5. Account Section — Deletion - -- [ ] 5.1 "Delete account" danger button at bottom of settings page -- [ ] 5.2 Confirmation modal requiring username input to proceed -- [ ] 5.3 Create `POST /api/settings/delete-account` action — cascading delete, destroy session, redirect to home -- [ ] 5.4 Register API route in `routes.ts` - -## 6. Navigation - -- [ ] 6.1 Add "Settings" link to Journal nav for authenticated users -- [ ] 6.2 Move passkey count display from home page to settings security section - -## 7. i18n - -- [ ] 7.1 Add translation keys for all settings UI text (en + de) - -## 8. Testing - -### Unit tests (Vitest) -- [ ] 8.1 Profile update: valid input saves, empty display name falls back to username -- [ ] 8.2 Passkey delete: removes credential, rejects invalid credential ID, allows deleting last passkey -- [ ] 8.3 Email change: creates token for new email, rejects duplicate email, rejects same-as-current email -- [ ] 8.4 Account deletion: cascading delete removes user + credentials + tokens, destroys session - -### E2E tests (Playwright) -- [ ] 8.5 Auth guard: unauthenticated user visiting `/settings` is redirected to `/auth/login` -- [ ] 8.6 Profile editing: log in, navigate to settings, update display name and bio, verify changes persist after reload, verify public profile reflects changes -- [ ] 8.7 Passkey management: log in, navigate to security section, add passkey (virtual WebAuthn authenticator), verify it appears in list, delete it, verify removed -- [ ] 8.8 Delete last passkey: delete only passkey, verify warning modal appears, confirm deletion, verify magic link login still works -- [ ] 8.9 Email change: initiate email change, follow verification link (dev mode), verify email updated in settings -- [ ] 8.10 Account deletion: click delete, type wrong username (verify blocked), type correct username, confirm, verify redirected to home and session destroyed -- [ ] 8.11 Navigation: verify "Settings" link appears in nav when logged in, not visible when logged out diff --git a/openspec/changes/account-settings/.openspec.yaml b/openspec/changes/archive/2026-03-29-account-settings/.openspec.yaml similarity index 100% rename from openspec/changes/account-settings/.openspec.yaml rename to openspec/changes/archive/2026-03-29-account-settings/.openspec.yaml diff --git a/openspec/changes/account-settings/design.md b/openspec/changes/archive/2026-03-29-account-settings/design.md similarity index 100% rename from openspec/changes/account-settings/design.md rename to openspec/changes/archive/2026-03-29-account-settings/design.md diff --git a/openspec/changes/account-settings/proposal.md b/openspec/changes/archive/2026-03-29-account-settings/proposal.md similarity index 100% rename from openspec/changes/account-settings/proposal.md rename to openspec/changes/archive/2026-03-29-account-settings/proposal.md diff --git a/openspec/changes/account-settings/specs/account-settings/spec.md b/openspec/changes/archive/2026-03-29-account-settings/specs/account-settings/spec.md similarity index 100% rename from openspec/changes/account-settings/specs/account-settings/spec.md rename to openspec/changes/archive/2026-03-29-account-settings/specs/account-settings/spec.md diff --git a/openspec/changes/account-settings/specs/journal-auth/spec.md b/openspec/changes/archive/2026-03-29-account-settings/specs/journal-auth/spec.md similarity index 100% rename from openspec/changes/account-settings/specs/journal-auth/spec.md rename to openspec/changes/archive/2026-03-29-account-settings/specs/journal-auth/spec.md diff --git a/openspec/changes/archive/2026-03-29-account-settings/tasks.md b/openspec/changes/archive/2026-03-29-account-settings/tasks.md new file mode 100644 index 0000000..ca5ff52 --- /dev/null +++ b/openspec/changes/archive/2026-03-29-account-settings/tasks.md @@ -0,0 +1,63 @@ +## 1. Settings Route & Layout + +- [x] 1.1 Create `apps/journal/app/routes/settings.tsx` with loader auth guard (redirect to `/auth/login` if unauthenticated) +- [x] 1.2 Register route in `apps/journal/app/routes.ts` +- [x] 1.3 Layout with three sections: Profile, Security, Account (anchor links for navigation) + +## 2. Profile Section + +- [x] 2.1 Display name and bio form fields, pre-filled from current user data +- [x] 2.2 Create `POST /api/settings/profile` action to update display name and bio +- [x] 2.3 Register API route in `routes.ts` +- [x] 2.4 Success/error feedback on save + +## 3. Security Section — Passkey Management + +- [x] 3.1 Load user's credentials in settings loader (device type, transports, createdAt) +- [x] 3.2 Render passkey list with friendly transport labels and registration date +- [x] 3.3 Add passkey button using existing `addPasskeyStart`/`addPasskeyFinish` flow, hidden when WebAuthn unsupported +- [x] 3.4 Create `POST /api/settings/passkey/delete` action to remove a credential by ID +- [x] 3.5 Register API route in `routes.ts` +- [x] 3.6 Delete confirmation dialog, with last-passkey warning when applicable + +## 4. Account Section — Email Change + +- [x] 4.1 Email display with "Change" button revealing input for new email +- [x] 4.2 Create `initiateEmailChange(userId, newEmail)` in auth.server.ts — generates magic token with `newEmail` metadata +- [x] 4.3 Create `POST /api/settings/email` action to initiate email change +- [x] 4.4 Register API route in `routes.ts` +- [x] 4.5 Update `verifyMagicToken` to handle email-change tokens (update user email) +- [x] 4.6 Send verification email to new address using existing email templates + +## 5. Account Section — Deletion + +- [x] 5.1 "Delete account" danger button at bottom of settings page +- [x] 5.2 Confirmation modal requiring username input to proceed +- [x] 5.3 Create `POST /api/settings/delete-account` action — cascading delete, destroy session, redirect to home +- [x] 5.4 Register API route in `routes.ts` + +## 6. Navigation + +- [x] 6.1 Add "Settings" link to Journal nav for authenticated users +- [x] 6.2 Move passkey count display from home page to settings security section + +## 7. i18n + +- [x] 7.1 Add translation keys for all settings UI text (en + de) + +## 8. Testing + +### Unit tests (Vitest) +- [x] 8.1 Profile update: valid input saves, empty display name falls back to username +- [x] 8.2 Passkey delete: removes credential, rejects invalid credential ID, allows deleting last passkey +- [x] 8.3 Email change: creates token for new email, rejects duplicate email, rejects same-as-current email +- [x] 8.4 Account deletion: cascading delete removes user + credentials + tokens, destroys session + +### E2E tests (Playwright) +- [x] 8.5 Auth guard: unauthenticated user visiting `/settings` is redirected to `/auth/login` +- [x] 8.6 Profile editing: log in, navigate to settings, update display name and bio, verify changes persist after reload, verify public profile reflects changes +- [x] 8.7 Passkey management: log in, navigate to security section, add passkey (virtual WebAuthn authenticator), verify it appears in list, delete it, verify removed +- [x] 8.8 Delete last passkey: delete only passkey, verify warning modal appears, confirm deletion, verify magic link login still works +- [x] 8.9 Email change: initiate email change, follow verification link (dev mode), verify email updated in settings +- [x] 8.10 Account deletion: click delete, type wrong username (verify blocked), type correct username, confirm, verify redirected to home and session destroyed +- [x] 8.11 Navigation: verify "Settings" link appears in nav when logged in, not visible when logged out diff --git a/openspec/specs/account-settings/spec.md b/openspec/specs/account-settings/spec.md new file mode 100644 index 0000000..67a3e32 --- /dev/null +++ b/openspec/specs/account-settings/spec.md @@ -0,0 +1,83 @@ +## ADDED Requirements + +### Requirement: Settings page access +The Journal SHALL provide an account settings page at `/settings` accessible to authenticated users. + +#### Scenario: Authenticated access +- **WHEN** a logged-in user navigates to `/settings` +- **THEN** the settings page is displayed with profile, security, and account sections + +#### Scenario: Unauthenticated access +- **WHEN** an unauthenticated user navigates to `/settings` +- **THEN** they are redirected to `/auth/login` + +### Requirement: Profile editing +The settings page SHALL allow users to edit their display name and bio. + +#### Scenario: Update display name +- **WHEN** a user changes their display name and submits +- **THEN** the display name is updated and reflected on their public profile + +#### Scenario: Update bio +- **WHEN** a user enters a bio (max 160 characters) and submits +- **THEN** the bio is saved and visible on their public profile + +#### Scenario: Empty fields +- **WHEN** a user clears their display name +- **THEN** their username is used as the display name fallback + +### Requirement: Passkey management +The settings page SHALL list all registered passkeys and allow adding or deleting them. + +#### Scenario: View passkeys +- **WHEN** a user visits the security section +- **THEN** they see a list of their passkeys with device type, transport label, and registration date + +#### Scenario: Add passkey +- **WHEN** a user clicks "Add passkey" on a browser that supports WebAuthn +- **THEN** the browser passkey creation prompt appears and the new passkey is added to the list + +#### Scenario: Add passkey unsupported browser +- **WHEN** a user visits the security section on a browser without WebAuthn +- **THEN** the "Add passkey" button is disabled with a message explaining the browser limitation + +#### Scenario: Delete passkey +- **WHEN** a user clicks delete on a passkey and confirms +- **THEN** the passkey is removed and can no longer be used for login + +#### Scenario: Delete last passkey +- **WHEN** a user deletes their only passkey +- **THEN** a warning is shown that they will need to use magic links to sign in, and the deletion proceeds after confirmation + +### Requirement: Email change +The settings page SHALL allow users to change their email address with verification. + +#### Scenario: Initiate email change +- **WHEN** a user enters a new email and submits +- **THEN** a verification link is sent to the new email address + +#### Scenario: Verify new email +- **WHEN** the user clicks the verification link +- **THEN** their email is updated to the new address + +#### Scenario: Duplicate email +- **WHEN** a user enters an email already in use by another account +- **THEN** an error is shown indicating the email is taken + +### Requirement: Account deletion +The settings page SHALL allow users to permanently delete their account. + +#### Scenario: Delete account +- **WHEN** a user clicks "Delete account" and types their username to confirm +- **THEN** their account and all associated data are permanently deleted and they are logged out + +#### Scenario: Cancel deletion +- **WHEN** a user opens the delete confirmation but does not confirm +- **THEN** no data is deleted and they remain on the settings page + +### Requirement: Settings navigation +The Journal navigation SHALL include a link to the settings page for authenticated users. + +#### Scenario: Settings link visible +- **WHEN** a logged-in user views the navigation +- **THEN** a "Settings" link is present that navigates to `/settings` diff --git a/openspec/specs/journal-auth/spec.md b/openspec/specs/journal-auth/spec.md index 846484e..a088b87 100644 --- a/openspec/specs/journal-auth/spec.md +++ b/openspec/specs/journal-auth/spec.md @@ -1,130 +1,29 @@ -## ADDED Requirements - -### Requirement: Passkey registration -The Journal SHALL allow new users to register using a passkey (WebAuthn) when the browser supports it. No password is required. - -#### Scenario: Successful passkey registration -- **WHEN** a user enters an email and username and creates a passkey via the browser prompt -- **THEN** a new user account is created, the passkey credential is stored, and the user is logged in - -#### Scenario: Duplicate email -- **WHEN** a user submits an email that is already registered -- **THEN** the system displays an error indicating the email is already in use - -#### Scenario: Duplicate username -- **WHEN** a user submits a username that is already taken -- **THEN** the system displays an error indicating the username is not available - -### Requirement: Magic link registration (fallback) -The Journal SHALL allow new users to register via magic link when the browser does not support passkeys. The user can add a passkey later from a supported browser. - -#### Scenario: Register without passkey support -- **WHEN** a user's browser does not support WebAuthn -- **THEN** the registration page shows a "Register with Magic Link" button instead of the passkey button - -#### Scenario: Successful magic link registration -- **WHEN** a user submits email and username via magic link registration -- **THEN** a new user account is created without a credential, a verification email is sent, and the user is redirected to verify their email - -#### Scenario: Magic link verify redirects to add-passkey -- **WHEN** a user clicks the verification link from magic link registration -- **THEN** they are logged in and redirected to the home page with the add-passkey prompt - -### Requirement: Passkey login -The Journal SHALL allow returning users to log in using a stored passkey when the browser supports WebAuthn. - -#### Scenario: Successful passkey login -- **WHEN** a user clicks "Sign in" and selects a passkey from the browser prompt -- **THEN** the user is authenticated and redirected to their activity feed - -#### Scenario: No passkey available -- **WHEN** a user has no passkey on the current device -- **THEN** the system offers magic link login as a fallback - -#### Scenario: Browser without WebAuthn support -- **WHEN** a user's browser does not support WebAuthn -- **THEN** the login page shows only the magic link option with no passkey button - -### Requirement: Magic link login (fallback) -The Journal SHALL allow users to log in via a magic link sent to their email. This serves as a fallback for devices without passkey support or for logging in on a new device. - -#### Scenario: Request magic link -- **WHEN** a user enters their email and clicks "Send magic link" -- **THEN** an email with a single-use login link is sent to their address - -#### Scenario: Valid magic link -- **WHEN** a user clicks a valid, non-expired magic link -- **THEN** the user is logged in and the link is invalidated - -#### Scenario: Expired magic link -- **WHEN** a user clicks a magic link older than 15 minutes -- **THEN** the system displays an error and prompts them to request a new link - -#### Scenario: Rate limiting -- **WHEN** a user requests more than 5 magic links in 10 minutes -- **THEN** subsequent requests are rejected with a rate limit message +## MODIFIED Requirements ### Requirement: Add passkey from new device -The Journal SHALL allow logged-in users to register additional passkeys for new devices. +The Journal SHALL allow logged-in users to register additional passkeys from the account settings page or via the post-login prompt. #### Scenario: Add passkey after magic link login - **WHEN** a user logs in via magic link on a device that supports WebAuthn - **THEN** the system prompts them to register a passkey for that device +#### Scenario: Add passkey from settings +- **WHEN** a user clicks "Add passkey" in the security section of account settings +- **THEN** the browser passkey creation prompt appears and the new passkey is stored + #### Scenario: Add passkey prompt on unsupported browser - **WHEN** a user logs in via magic link on a device that does not support WebAuthn - **THEN** the system shows the add-passkey prompt with a message that the browser does not support passkeys -### Requirement: Passkey status display -The Journal home page SHALL show the user how many passkeys they have registered. +## ADDED Requirements -#### Scenario: User with passkeys -- **WHEN** a logged-in user visits the home page and has one or more passkeys -- **THEN** the page displays the passkey count (e.g., "2 passkeys registered") +### Requirement: Delete passkey +The Journal SHALL allow users to delete individual passkeys from account settings. -### Requirement: User profile page -Each user SHALL have a public profile page displaying their username and routes. +#### Scenario: Delete passkey +- **WHEN** a user deletes a passkey from account settings +- **THEN** the credential is removed from the database and can no longer be used for authentication -#### Scenario: View own profile -- **WHEN** a logged-in user navigates to their profile -- **THEN** they see their username, bio, and a list of their routes - -#### Scenario: View other user's profile -- **WHEN** a user navigates to another user's profile URL -- **THEN** they see that user's username, bio, and public routes - -### Requirement: Federated identity structure -User accounts SHALL follow the federated identity pattern (`@user@instance`) to prepare for ActivityPub federation in Phase 2. - -#### Scenario: Username format -- **WHEN** a user registers with username "alice" on trails.cool -- **THEN** their full identity is stored as `@alice@trails.cool` - -### Requirement: Session management -The Journal SHALL maintain authenticated sessions using secure HTTP-only cookies. - -#### Scenario: Session persistence -- **WHEN** a logged-in user closes and reopens their browser -- **THEN** they remain logged in if the session has not expired - -#### Scenario: Logout -- **WHEN** a user clicks "Log out" -- **THEN** their session is invalidated and they are redirected to the login page - -### Requirement: No passwords -The Journal SHALL NOT support password-based authentication. All authentication is via passkeys or magic links. - -#### Scenario: No password field -- **WHEN** a user views the registration or login page -- **THEN** there is no password field - -### Requirement: Magic link login -The magic link login flow SHALL deliver the link via email in production instead of logging to console. - -#### Scenario: Production email delivery -- **WHEN** a user requests a magic link in production -- **THEN** the link is emailed to the user (not just logged to console) - -#### Scenario: Dev mode unchanged -- **WHEN** a user requests a magic link in development -- **THEN** the link is returned directly for auto-redirect (existing behavior preserved) +#### Scenario: Delete last passkey warning +- **WHEN** a user attempts to delete their only remaining passkey +- **THEN** a warning is shown explaining they will need to use magic links, and deletion proceeds after confirmation