Owner accounts: auth, password reset, CRM dashboard visibility #19

Closed
opened 2026-07-23 14:07:58 +02:00 by mivanchenko · 1 comment
Owner

Parent

#14

What to build

Business-owner identity: a real login scoped to a single client, password reset both self-service
and operator-triggered, and visibility into owner accounts from the existing CRM dashboard.

  • Login page/route: email + password, backed by users.password_hash (standard Flask session,
    werkzeug password hashing — no new auth framework). A logged-in session is scoped to exactly one
    client_id; every subsequent owner route (tickets 6/7/8) must resolve client_id from this
    session, never from a request parameter.
  • Self-service "forgot password": owner requests a reset link by email; link uses a
    password_reset_tokens row (single-use, expiring).
  • Operator-triggered reset: from the existing CRM operator dashboard, a button next to a client
    sends the same reset email on the operator's behalf.
  • Owner-accounts list: a new section/tab in the existing CRM dashboard (alongside Clients,
    Credentials, etc.) listing each client's owner user(s).

Acceptance criteria

  • An owner can log in with email+password and reach an authenticated session scoped to their
    client_id.
  • A logged-in owner cannot access or infer another client's data through any owner route (this
    is enforced by ticket 1's data-access layer, but this ticket is what actually resolves
    client_id from session rather than trusting a request param).
  • An owner can request a password reset themselves and complete it via the emailed link.
  • An operator can trigger a password-reset email for any client's owner account from the CRM
    dashboard.
  • The CRM dashboard shows a list of owner accounts per client.
  • Reset tokens are single-use and expire.

Blocked by

  • #15 (Booking schema + tenancy-safe data access layer)
## Parent #14 ## What to build Business-owner identity: a real login scoped to a single client, password reset both self-service and operator-triggered, and visibility into owner accounts from the existing CRM dashboard. - Login page/route: email + password, backed by `users.password_hash` (standard Flask session, werkzeug password hashing — no new auth framework). A logged-in session is scoped to exactly one `client_id`; every subsequent owner route (tickets 6/7/8) must resolve `client_id` from this session, never from a request parameter. - Self-service "forgot password": owner requests a reset link by email; link uses a `password_reset_tokens` row (single-use, expiring). - Operator-triggered reset: from the existing CRM operator dashboard, a button next to a client sends the same reset email on the operator's behalf. - Owner-accounts list: a new section/tab in the existing CRM dashboard (alongside Clients, Credentials, etc.) listing each client's owner user(s). ## Acceptance criteria - [ ] An owner can log in with email+password and reach an authenticated session scoped to their `client_id`. - [ ] A logged-in owner cannot access or infer another client's data through any owner route (this is enforced by ticket 1's data-access layer, but this ticket is what actually resolves `client_id` from session rather than trusting a request param). - [ ] An owner can request a password reset themselves and complete it via the emailed link. - [ ] An operator can trigger a password-reset email for any client's owner account from the CRM dashboard. - [ ] The CRM dashboard shows a list of owner accounts per client. - [ ] Reset tokens are single-use and expire. ## Blocked by - #15 (Booking schema + tenancy-safe data access layer)
mivanchenko added the bookingready-for-agentenhancement labels 2026-07-23 14:07:58 +02:00
mivanchenko added a new dependency 2026-07-23 14:09:16 +02:00
mivanchenko added a new dependency 2026-07-23 14:09:17 +02:00
mivanchenko added a new dependency 2026-07-23 14:09:18 +02:00
Author
Owner

Implemented in 59c35ce (pushed to main).

  • Owner login: /owner/login, Flask-session-backed, scoped to exactly one client_id (never a request param) via a new login_required decorator in owner_auth.py — the foundation tickets 6/7/8 will build the real dashboard on top of.
  • Self-service password reset: /owner/forgot-password + /owner/reset-password/<token>, using the existing single-use/expiring password_reset_tokens row from #15. Enumeration-safe (same response whether or not the email matched a user).
  • Operator-triggered reset: new POST /api/owner_users/<user_id>/reset-password, gated by the CRM token, sends the same reset email.
  • CRM dashboard: new "Eigentümer-Konten" tab (backed by GET /api/owner_users) listing owner accounts across all clients with a "Reset-Link senden" button per row.
  • New owner_mail.py reuses the #18 mailer.py/booking_mail.py sender-resolution pattern for the reset email.
  • Dedicated SESSION_SECRET_KEY env var for the session cookie, kept separate from CRM_API_TOKEN/BOOKING_TOKEN_SECRET (added to .env.example and docker-compose.yml).
  • 20 new tests (test_owner_auth.py + additions to test_booking_db.py), all passing against a real throwaway Postgres per #14's testing convention. Full suite: 97/97 passing.

All acceptance criteria met.

Implemented in 59c35ce (pushed to `main`). - Owner login: `/owner/login`, Flask-session-backed, scoped to exactly one `client_id` (never a request param) via a new `login_required` decorator in `owner_auth.py` — the foundation tickets 6/7/8 will build the real dashboard on top of. - Self-service password reset: `/owner/forgot-password` + `/owner/reset-password/<token>`, using the existing single-use/expiring `password_reset_tokens` row from #15. Enumeration-safe (same response whether or not the email matched a user). - Operator-triggered reset: new `POST /api/owner_users/<user_id>/reset-password`, gated by the CRM token, sends the same reset email. - CRM dashboard: new "Eigentümer-Konten" tab (backed by `GET /api/owner_users`) listing owner accounts across all clients with a "Reset-Link senden" button per row. - New `owner_mail.py` reuses the #18 `mailer.py`/`booking_mail.py` sender-resolution pattern for the reset email. - Dedicated `SESSION_SECRET_KEY` env var for the session cookie, kept separate from `CRM_API_TOKEN`/`BOOKING_TOKEN_SECRET` (added to `.env.example` and `docker-compose.yml`). - 20 new tests (`test_owner_auth.py` + additions to `test_booking_db.py`), all passing against a real throwaway Postgres per #14's testing convention. Full suite: 97/97 passing. All acceptance criteria met.
Sign in to join this conversation.