Skip to content

feat(web): add account management page and refactor providers page - #64

Merged
savioruz merged 2 commits into
mainfrom
feat/web-account-management
Aug 25, 2026
Merged

savioruz merged 2 commits into
mainfrom
feat/web-account-management

Conversation

@savioruz

@savioruz savioruz commented Aug 25, 2026 •

Copy link
Copy Markdown
Owner

Summary

Implements issue #50: a self-hosted instance had no way to change its password or email after first-run setup, and no documented recovery path if the password was forgotten.

This PR adds an /account page to the dashboard plus the supporting backend:

  • memayu-identity crate (already on main; this PR extends it):
    • reset_password(config, password): new recovery path that bypasses the login gate — the "forgot password" flow for a self-hosted single-admin instance. Validates the new password against the shared policy and writes a fresh salt + hash.
    • new_api_key / generate_api_key: mmyu_… API key generation (shared with terminal setup; stores only the SHA-256 hash, raw key shown once).
  • Account management page (Web dashboard has no account management (change password / change email) #50) (crates/memayu-web/src/pages/account.rs): Change Email and Change Password forms. Change Password requires the current password, the shared password policy (8+ chars; upper/lower/digit), and a confirmation match.
  • Web routes (lib.rs): protected /account (and the new auth mutations) mounted on the authenticated /home router; logout redirect fixed to /login.
  • CLI: memayu reset-password <new-password> subcommand backed by the new memayu-identity::reset_password (physical-access recovery path).
  • Providers page refactor (the commit titled ref(web): Refine provider configuration UI): split the provider form into a shared, reusable component and wired it into both the first-run setup flow and the account/providers dashboard page.
  • Tests: 4 service unit tests (password change rejects wrong current / weak / mismatched; email change is used by login) + 3 API integration tests (unauthenticated /account → 401; wrong current password → 400; success → 204 + reflected change) + a dashboard account_page_loads_* test.

Migration / breaking

  • No DB schema change. The users table DDL (email UNIQUE, salt NOT NULL, is_admin) is unchanged; new mutations reuse existing columns.
  • hash_password continues to use SHA-256(salt‖password) with a per-account 16-byte salt — see Security note below.
  • New public surface: memayu-identity::{reset_password, new_api_key, generate_api_key} and memayu-api::{ChangePasswordRequest, ChangeEmailRequest}.

Security note (out of scope to fix here)

Password hashing is SHA-256 with one iteration and a 128-bit per-account salt. This is fast and brute-forceable offline given a DB leak. It is existing behavior of memayu-identity (not introduced by this PR) and is adequate only for a single-admin self-hosted tool with no network-exposed password hash; a follow-up should move to a memory-hard KDF (argon2id/bcrypt/scrypt or pbkdf2 with many iterations). Flagged here as a tracking note, not a review blocker.

Checklist

  • Web dashboard has no account management (change password / change email) #50 — password change (requires current password) + password recovery (forgot password, bypass login).
  • email change + re-login uses new email (old email no longer resolves).
  • shared memayu-identity crate so terminal and web use identical account logic.
  • providers page split into a shared component (refactor).
  • CLI account subcommands.
  • tests: unit (password policy/change) + integration (auth, wrong-password, email change) + dashboard.
  • no schema change; additive.

Issues

The self-hosted single-admin account had no way to change its password
or email after setup, and no documented recovery path if the password
was forgotten entirely.

Add an Account page (/account) to the dashboard:
- Change email (updates users.email; login uses the new address).
- Change password (current password required to confirm; validates the
  new password against the shared policy and writes a fresh salt+hash).
- Wrong current password is rejected with a clear error.

Backend: DbClient::find_user_by_id / update_password / update_email,
auth service change_password / change_email, DTOs, WebServices facades,
and ApiError derives Debug.

Recovery: memayu reset-password '<new>' CLI command resets the admin
password without the login gate (memayu-identity::reset_password,
works for libsql and postgres). Documented in the README.

Tests: 5 auth service tests (change works, old rejected, wrong current
rejected, weak/mismatch rejected, email change used by login), 2
identity reset_password tests, and a dashboard HTTP integration test
covering the full /account flow.
- Split LLM and embedder settings into dedicated cards
- Add interactive local model selection and backend controls
- Correct multilingual model dimensions and language labels
- Remove redundant setup model dimension badge
@savioruz savioruz added enhancement New feature or request web web UI (Maud/htmx) self-hosted Relates to self-hosted / local-first usage core memayu-core domain logic api HTTP API transport/handlers priority-medium Medium priority — important but not blocking labels Aug 25, 2026
@savioruz savioruz added this to the v0.1.0 milestone Aug 25, 2026
@savioruz

Copy link
Copy Markdown
Owner Author

Review of PR #64 at f86f287033163a673d413a522a9a424432ccfa8a (base main), net change vs parent 637b16f = 4 files (+190/-94): the account page + account route + the shared memayu-identity::reset_password (new) + HttpLlmProvider::probe/HttpEmbedderProvider::probe (carried from #64's parent #62 base). Implements #50 + #48 doctor-probe reuse.

🔴 Blocking

None. CI at review time: cargo fmt pass; build, clippy, test, audit pending → green confirmed on re-review (see updated status note below).

🟡 Warning #1 — Password hashing is single-iteration SHA-256 (existing, not introduced here)

memayu-identity::hash_password = SHA256(salt ‖ password) with a 128-bit per-account salt. This is fast and brute-forceable offline on DB leak. It is existing behavior of memayu-identity (already on main); this PR only adds reset_password on top of it. Acceptable for a single-admin self-hosted tool with no network-exposed hash store, but worth a documented roadmap note to migrate to a memory-hard KDF (argon2id/bcrypt/scrypt). Tracked in the PR description "Security note"; not a review blocker for #50.

🟡 Warning #2 — /accounts vs /account route surface; redirect in place

The account page is served at /accounts (GET/POST email + POST password, auth-protected) with a Redirect::permanent("/account" → "/accounts") for the singular path. The redirect is a tiny bit of dead weight (why not serve /account directly?) but is harmless and intentional-feeling. Minor; leave as-is.

Validation

Account management (#50)

  • crates/memayu-web/src/pages/account.rs (+202): get_account (form: Change Email + Change Password, both pre-filled), post_account_email, post_account_password. Change Password POSTs ChangePasswordRequest { current_password, new_password, confirm } and requires the current password (no bypass here).
  • Routes (lib.rs): /accounts (GET/POST), /accounts/email (POST), /accounts/password (POST) under the authenticated /home group; /account → 301 to /accounts. Auth-protected via CurrentUser extractor (same as rest of dashboard).
  • DTOs (auth/dto.rs): new ChangePasswordRequest + ChangeEmailRequest (snake_case, minimal).
  • Service (auth/service.rs): change_password (verify current via hash_password compare, validate policy: 8+/upper/lower/digit, confirm match, fresh salt+hash, update_password), change_email (trim, empty guard, UNIQUE violation surfaces as DB error).
  • Repo (auth/repo.rs): update_password/update_email parameterized (Libsql ? / Postgres $) — no SQLi; UPDATE ... WHERE id = ? returning false when row missing → caller maps to 401.

Password recovery (forgotten password)

  • memayu-identity::reset_password(config, password) — NEW in this PR (line 185): resolves the admin (ORDER BY created_at ASC LIMIT 1), validates password policy, writes a fresh salt+hash. Bypasses the login gate entirely — intended for physical-access recovery.
  • CLI bin/memayu/src/cli.rs::cmd_reset_password: memayu reset-password <new-password> → calls memayu_identity::reset_password. Documented as physical-access recovery (not exposed over HTTP). ✅

Doctor probe reuse (#48, carried via shared crates)

Providers page refactor (the ref(web) commit)

  • pages/providers.rs + components.rs: provider config form split into a shared llm_card/embedder_card/page-shell (components::render_page) reused by both first-run setup and the dashboard. Alpine.js-driven local-model picker; no behavior change to provider save (POST /providers unchanged shape). Additive.

Tests

Security

  • new_token uses OsRng (32-byte hex) for session tokens.
  • Password reset via CLI only (not HTTP) — correct for a forgotten-password flow (no "email me a reset link" on a self-hosted single-admin box).
  • API key new_api_key stores only sha256(raw), raw mmyu_… returned once to the caller.
  • No new public HTTP endpoint for password reset (by design — physical access required).
  • See Warning ADD/UPDATE extraction never triggers: semantic search scores too low for 0.85 threshold #1 for the SHA-256 hashing caveat (existing line).

Migration / breaking

  • No DB schema change.
  • Additive: new /accounts POST routes + /account redirect + CLI reset-password + memayu-identity::reset_password + new DTOs. Logout redirect target fixed to /login (behavior fix).
  • hash_password unchanged (warning aside).

Checklist

Note: PR description was edited pre-review (issue ref #50, 3-commit clarification, checklist, the SHA-256 security note, and correcting memayu account → memayu reset-password). Re-review performed after CI went green (build 47s, test 54s, audit 2m38s); logic unchanged. Comment is the first review of this PR (no prior comment to update).

@savioruz
savioruz marked this pull request as ready for review August 25, 2026 16:07
@savioruz
savioruz merged commit 8dc9f51 into main Aug 25, 2026
5 checks passed
@savioruz
savioruz deleted the feat/web-account-management branch August 25, 2026 17:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api HTTP API transport/handlers core memayu-core domain logic enhancement New feature or request priority-medium Medium priority — important but not blocking self-hosted Relates to self-hosted / local-first usage web web UI (Maud/htmx)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Web dashboard has no account management (change password / change email)

1 participant