Add account-settings OpenSpec change
Settings page at /settings with profile editing, passkey management (list, add, delete), email change with verification, and account deletion. Includes comprehensive E2E test plan using virtual WebAuthn. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0230d93004
commit
388a7d4866
6 changed files with 314 additions and 0 deletions
2
openspec/changes/account-settings/.openspec.yaml
Normal file
2
openspec/changes/account-settings/.openspec.yaml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
schema: spec-driven
|
||||
created: 2026-03-29
|
||||
105
openspec/changes/account-settings/design.md
Normal file
105
openspec/changes/account-settings/design.md
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
## Context
|
||||
|
||||
Users register and log in via passkeys or magic links. After that, there's no
|
||||
way to manage their account. The user profile page (`/users/:username`) is
|
||||
public-facing and read-only. Account management needs a private settings page.
|
||||
|
||||
The `users` table already has `displayName` and `bio` columns. The
|
||||
`credentials` table stores `deviceType`, `transports`, and `createdAt` per
|
||||
passkey — enough to show a meaningful list.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Single settings page with sections for profile, security, and account
|
||||
- Passkey management: list, add, delete
|
||||
- Profile editing: display name, bio
|
||||
- Email change with verification
|
||||
- Account deletion with confirmation
|
||||
|
||||
**Non-Goals:**
|
||||
- Avatar/photo upload (requires S3 — see activity-photos spec)
|
||||
- Notification preferences (no notifications yet)
|
||||
- Privacy settings (route visibility is in route-sharing spec)
|
||||
- Two-factor authentication (passkeys already provide strong auth)
|
||||
- Session management / "log out everywhere"
|
||||
|
||||
## Decisions
|
||||
|
||||
### D1: Single settings page with sections
|
||||
|
||||
One route at `/settings` with anchor-linked sections rather than separate
|
||||
sub-pages. Keeps navigation simple and avoids route proliferation.
|
||||
|
||||
Sections:
|
||||
- **Profile** — display name, bio
|
||||
- **Security** — passkeys list, add passkey button
|
||||
- **Account** — email, delete account
|
||||
|
||||
### D2: Profile editing
|
||||
|
||||
Simple form with display name and bio fields. Submit via POST to
|
||||
`/api/settings/profile`. No real-time validation needed — just save on submit.
|
||||
|
||||
Bio is plain text, max 160 characters (like a social bio).
|
||||
|
||||
### D3: Passkey management
|
||||
|
||||
List all credentials for the current user, showing:
|
||||
- Device type (from `deviceType` column, e.g., "singleDevice" / "multiDevice")
|
||||
- Transport hints (from `transports`, e.g., "internal", "usb", "ble", "nfc")
|
||||
- Date registered (from `createdAt`)
|
||||
- Delete button (with confirmation)
|
||||
|
||||
Friendly labels derived from transports:
|
||||
- `internal` → "This device"
|
||||
- `usb` → "Security key"
|
||||
- `ble` → "Bluetooth"
|
||||
- `hybrid` → "Phone or tablet"
|
||||
- fallback → "Passkey"
|
||||
|
||||
Add passkey button reuses the existing `addPasskeyStart` / `addPasskeyFinish`
|
||||
flow from auth.server.ts.
|
||||
|
||||
Deleting the last passkey is allowed — user can still log in via magic link.
|
||||
Show a warning when deleting the last one.
|
||||
|
||||
### D4: Email change
|
||||
|
||||
Two-step flow:
|
||||
1. User enters new email → server sends magic link to the **new** email
|
||||
2. User clicks link → email is updated
|
||||
|
||||
This ensures ownership of the new address. The old email is not notified
|
||||
(simplicity; can add later if needed).
|
||||
|
||||
New server function: `initiateEmailChange(userId, newEmail)` creates a
|
||||
magic token with a `newEmail` field, sends verification to the new address.
|
||||
New verify handler recognizes email-change tokens and updates the user record.
|
||||
|
||||
### D5: Account deletion
|
||||
|
||||
- Button at bottom of settings page, styled as danger
|
||||
- Confirmation modal: "This will permanently delete your account and all your
|
||||
data. Type your username to confirm."
|
||||
- Server-side: cascading delete via DB foreign keys (credentials, magic tokens,
|
||||
routes, activities all cascade from users)
|
||||
- After deletion: destroy session, redirect to home page
|
||||
|
||||
### D6: Navigation
|
||||
|
||||
Add "Settings" link to the user dropdown/nav when logged in. Links to
|
||||
`/settings`. Must be registered in `routes.ts`.
|
||||
|
||||
### D7: Auth guard
|
||||
|
||||
Settings page requires authentication. Loader checks session and redirects to
|
||||
`/auth/login` if not logged in.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **Email change without old-email notification**: Simpler but less secure.
|
||||
If an attacker has session access, they could change email silently. Mitigate:
|
||||
magic link to new address still required. Acceptable for now.
|
||||
- **Deleting last passkey**: User relies entirely on magic links. Acceptable
|
||||
since magic links are a first-class auth method.
|
||||
32
openspec/changes/account-settings/proposal.md
Normal file
32
openspec/changes/account-settings/proposal.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
## Why
|
||||
|
||||
Users have no way to manage their account after registration. There's no
|
||||
settings page to edit their profile, manage passkeys, change email, or delete
|
||||
their account. Passkey count currently shows on the home page as a stopgap,
|
||||
but it belongs in a dedicated settings area.
|
||||
|
||||
## What Changes
|
||||
|
||||
- **Account settings page** (`/settings`): central place for account management
|
||||
- **Profile editing**: update display name and bio
|
||||
- **Email change**: update email with verification via magic link
|
||||
- **Passkey management**: view registered passkeys (device type, date added),
|
||||
add new passkeys, delete existing ones
|
||||
- **Account deletion**: delete account and all associated data with confirmation
|
||||
- Move passkey status display from home page to settings
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `account-settings`: Settings page with profile, security, and account sections
|
||||
|
||||
### Modified Capabilities
|
||||
- `journal-auth`: Passkey management moves from home page prompt to settings;
|
||||
add passkey delete functionality
|
||||
|
||||
## Impact
|
||||
|
||||
- **Files**: New route `apps/journal/app/routes/settings.tsx`, API routes for
|
||||
profile update, email change, passkey delete, account delete
|
||||
- **Database**: No schema changes — uses existing users and credentials tables
|
||||
- **Routes**: Must register new routes in `apps/journal/app/routes.ts`
|
||||
|
|
@ -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`
|
||||
29
openspec/changes/account-settings/specs/journal-auth/spec.md
Normal file
29
openspec/changes/account-settings/specs/journal-auth/spec.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Add passkey from new device
|
||||
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
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Delete passkey
|
||||
The Journal SHALL allow users to delete individual passkeys from account settings.
|
||||
|
||||
#### 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: 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
|
||||
63
openspec/changes/account-settings/tasks.md
Normal file
63
openspec/changes/account-settings/tasks.md
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
## 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue