Effort 3: account management (Phase 6 multi-user) #67

Closed
opened 2026-10-04 04:17:24 +00:00 by agent-pi · 2 comments
Collaborator

Phase 6 made yunjin multi-user (auth in Effort 1, data ownership in Effort 2), but accounts themselves are frozen: the only account is the seeded default user, passwords can only be set or reset from the CLI, passkeys exist only as recorded design decisions, and there is no way for a user to leave — no deletion path and no cleanup of their feeds, aggregates, read state, settings, or credentials.

Effort 3 — the final effort of Phase 6 before the collecting branch lands on main — should close this out:

  • a way for accounts to come to exist (creation policy)
  • password change from the web UI
  • passkey enrollment and login (with passwords remaining the fallback/recovery path)
  • credential management
  • user deletion with data cleanup

Design decisions are recorded in the approved spec (local repo, .superpowers/specs/).

Phase 6 made yunjin multi-user (auth in Effort 1, data ownership in Effort 2), but accounts themselves are frozen: the only account is the seeded `default` user, passwords can only be set or reset from the CLI, passkeys exist only as recorded design decisions, and there is no way for a user to leave — no deletion path and no cleanup of their feeds, aggregates, read state, settings, or credentials. Effort 3 — the final effort of Phase 6 before the collecting branch lands on `main` — should close this out: - a way for accounts to come to exist (creation policy) - password change from the web UI - passkey enrollment and login (with passwords remaining the fallback/recovery path) - credential management - user deletion with data cleanup Design decisions are recorded in the approved spec (local repo, `.superpowers/specs/`).
Author
Collaborator

Findings from design exploration

  • Effort 1 already landed the session/CSRF layer with a per-login-method lifetime hook (passkey sessions target ~12 h); Effort 2 landed per-user data with an orphan-feed GC invariant — deletion can ride both.
  • webauthn 3.0.1 verified against PyPI as the current release; API inspected from installed source (options/verify functions, sign-count clone detection, JSON-in/JSON-out). HTTPS on a stable domain is a hard prerequisite (RP ID binds credentials) and will be documented.
  • Canned credential fixtures from the webauthn library sdist test suite let the whole WebAuthn surface be tested end-to-end through real Flask routes with no browser.

Plan of attack

Approved spec: .superpowers/specs/2026-10-04-effort3-account-management-design.md; implementation plan: .superpowers/plans/2026-10-04-effort3-account-management.md (both local to the box, git-excluded, as with Efforts 1–2).

Approach:

  • Accounts are created CLI-only (yunjin create-user); no admin role (family instance, 2–3 users).
  • New webauthn_credentials table + users.webauthn_user_handle; one Alembic migration on top of 20261003_data_ownership.
  • services/webauthn.py wraps the library; four JSON ceremony endpoints on the auth blueprint; username-less (discoverable-credential) login resolved by unique credential id.
  • YunjinSessionInterface subclass implements per-login lifetimes (passkey = 12 h) with a server-side absolute cap in the auth guard.
  • Account card on the settings page: password change (current-password gate, current session survives), passkey list/enroll/delete, password-confirmed self-deletion with a last-user guard (CLI delete-user bypasses it deliberately).

Key files: alembic/versions/20261004_webauthn_credentials.py, src/yunjin/db/webauthn_credentials.py, src/yunjin/services/webauthn.py, src/yunjin/services/accounts.py, src/yunjin/web/auth.py, src/yunjin/web/routes/auth.py, src/yunjin/web/routes/settings.py, src/yunjin/__main__.py, src/yunjin/models.py, templates login.html/settings.html, docs config.md/cli.md/passkeys.md.

Sequence (9 tasks, TDD, suite green at each task end): migration+db layer → webauthn dep+service → session lifetimes → passkey login → enrollment+credential management → password change → account deletion → CLI → docs+full nox.

Branch: feat/issue-67-account-management (cut from origin/feat/multiuser)

Commits (gitmoji subjects):

  1. 🗃️ Add webauthn credential schema and data access layer
  2. ➕ Add webauthn dependency
  3. ✨ Add webauthn ceremony service
  4. 🔒 Enforce per-login session lifetimes
  5. ✨ Add username-less passkey login
  6. ✨ Add passkey enrollment and credential management
  7. ✨ Add password change with session handling
  8. ✨ Add self-service account deletion with data cleanup
  9. ✨ Add create-user and delete-user CLI commands
  10. 📝 Document account management and passkeys

PR (after work completes, targeting the collecting branch, non-closing refs):

curl -X POST \
  -H "Authorization: token $FORGE_AGENT_PI_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\n    \"title\": \"**COLLECTING BRANCH** Account management: passkeys, password change, deletion\",\n    \"body\": \"Effort 3 of Phase 6. Refs #25\\nRefs #67\\n\\nBase: `feat/multiuser` (collecting branch).\",\n    \"head\": \"feat/issue-67-account-management\",\n    \"base\": \"feat/multiuser\"\n  }" \
  "https://forge.marvin8.zone/api/v1/repos/marvin8/yunjin/pulls"
## Findings from design exploration - Effort 1 already landed the session/CSRF layer with a per-login-method lifetime hook (passkey sessions target ~12 h); Effort 2 landed per-user data with an orphan-feed GC invariant — deletion can ride both. - `webauthn` 3.0.1 verified against PyPI as the current release; API inspected from installed source (options/verify functions, sign-count clone detection, JSON-in/JSON-out). HTTPS on a stable domain is a hard prerequisite (RP ID binds credentials) and will be documented. - Canned credential fixtures from the webauthn library sdist test suite let the whole WebAuthn surface be tested end-to-end through real Flask routes with no browser. ## Plan of attack Approved spec: `.superpowers/specs/2026-10-04-effort3-account-management-design.md`; implementation plan: `.superpowers/plans/2026-10-04-effort3-account-management.md` (both local to the box, git-excluded, as with Efforts 1–2). **Approach:** - Accounts are created CLI-only (`yunjin create-user`); no admin role (family instance, 2–3 users). - New `webauthn_credentials` table + `users.webauthn_user_handle`; one Alembic migration on top of `20261003_data_ownership`. - `services/webauthn.py` wraps the library; four JSON ceremony endpoints on the auth blueprint; username-less (discoverable-credential) login resolved by unique credential id. - `YunjinSessionInterface` subclass implements per-login lifetimes (passkey = 12 h) with a server-side absolute cap in the auth guard. - Account card on the settings page: password change (current-password gate, current session survives), passkey list/enroll/delete, password-confirmed self-deletion with a last-user guard (CLI `delete-user` bypasses it deliberately). **Key files:** `alembic/versions/20261004_webauthn_credentials.py`, `src/yunjin/db/webauthn_credentials.py`, `src/yunjin/services/webauthn.py`, `src/yunjin/services/accounts.py`, `src/yunjin/web/auth.py`, `src/yunjin/web/routes/auth.py`, `src/yunjin/web/routes/settings.py`, `src/yunjin/__main__.py`, `src/yunjin/models.py`, templates `login.html`/`settings.html`, docs `config.md`/`cli.md`/`passkeys.md`. **Sequence (9 tasks, TDD, suite green at each task end):** migration+db layer → webauthn dep+service → session lifetimes → passkey login → enrollment+credential management → password change → account deletion → CLI → docs+full nox. **Branch:** `feat/issue-67-account-management` (cut from `origin/feat/multiuser`) **Commits (gitmoji subjects):** 1. :card_file_box: Add webauthn credential schema and data access layer 2. :heavy_plus_sign: Add webauthn dependency 3. :sparkles: Add webauthn ceremony service 4. :lock: Enforce per-login session lifetimes 5. :sparkles: Add username-less passkey login 6. :sparkles: Add passkey enrollment and credential management 7. :sparkles: Add password change with session handling 8. :sparkles: Add self-service account deletion with data cleanup 9. :sparkles: Add create-user and delete-user CLI commands 10. :memo: Document account management and passkeys **PR (after work completes, targeting the collecting branch, non-closing refs):** ```bash curl -X POST \ -H "Authorization: token $FORGE_AGENT_PI_TOKEN" \ -H "Content-Type: application/json" \ -d "{\n \"title\": \"**COLLECTING BRANCH** Account management: passkeys, password change, deletion\",\n \"body\": \"Effort 3 of Phase 6. Refs #25\\nRefs #67\\n\\nBase: `feat/multiuser` (collecting branch).\",\n \"head\": \"feat/issue-67-account-management\",\n \"base\": \"feat/multiuser\"\n }" \ "https://forge.marvin8.zone/api/v1/repos/marvin8/yunjin/pulls" ```
Author
Collaborator

Landed on main in release 2026.10.6.1 (PR #69, merge 89f26f7). Closing.

Landed on `main` in release **2026.10.6.1** (PR #69, merge `89f26f7`). Closing.
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
marvin8/yunjin#67
No description provided.