clearview/docs/superpowers/specs/2026-05-28-authentication-design.md

9.2 KiB
Raw Blame History

Clearview — Authentication Design

Date: 2026-05-28 Status: Approved (brainstorming phase) Scope: Add an authentication layer to the existing Clearview FastAPI + static-frontend application.


1. Goal

Restrict access to the Clearview UI and API to a small group of named administrators. No self-service registration, no public exposure of endpoints, no multi-tenant user isolation.

2. Requirements

# Requirement
R1 All existing API routes (/api/tenants/*, /api/jobs/*, /api/onboarding/*) require an authenticated session.
R2 Two roles: admin and user. Only admin can manage users and view the audit log. Both can use the scanner UI.
R3 Server-side sessions using an opaque session ID stored in an HttpOnly cookie.
R4 First admin is created via a one-time initial-setup page that is reachable only while the users table is empty. No env-var fallback.
R5 Password hashing with Argon2id (default parameters via argon2-cffi).
R6 Password policy: minimum 12 characters, at least one letter and one digit. Validated server-side.
R7 Session TTL: 8 hours sliding by default; 30 days fixed when "remember me" is checked at login.
R8 Audit log persisted in DB and viewable in the UI by admins.
R9 Existing changelog convention applies: append entries to changelog-develop.md per change.

Explicitly out of scope for v1: rate limiting / login lockout, password reset via email, MFA, SSO, fine-grained permissions beyond admin / user.

3. Architecture

3.1 Backend module layout

A new package clearview_app/auth/ with focused modules:

File Purpose
auth/__init__.py Package marker.
auth/models.py SQLAlchemy models: User, Session, AuthAudit.
auth/security.py Argon2id hashing, password policy validation, session-ID generation.
auth/sessions.py Create / lookup / refresh / revoke session records; expiry handling.
auth/audit.py Single record(event, user_id, ip, detail) helper.
auth/dependencies.py FastAPI dependencies: current_session, require_user, require_admin.
auth/router.py POST /api/auth/login, POST /api/auth/logout, GET /api/auth/me, GET /api/auth/setup-required, POST /api/auth/setup.
auth/users_router.py Admin-only: GET/POST/PATCH/DELETE /api/users, POST /api/users/{id}/reset-password, GET /api/audit.

main.py wires the new routers before the static-files mount and applies Depends(require_user) to the three existing routers (tenants, jobs, onboarding).

3.2 Database

Three new tables, added via a new Alembic migration in clearview_app/migrations/versions/.

users
  id            int pk
  username      text unique not null
  password_hash text not null            -- Argon2id encoded string
  role          text not null            -- 'admin' | 'user'
  is_active     bool not null default true
  created_at    timestamptz not null default now()
  updated_at    timestamptz not null default now()

sessions
  id            uuid pk                  -- opaque session id, stored in cookie
  user_id       int not null fk -> users.id on delete cascade
  created_at    timestamptz not null default now()
  expires_at    timestamptz not null
  last_seen_at  timestamptz not null default now()
  ip            text
  user_agent    text
  remember      bool not null default false

auth_audit
  id            bigserial pk
  ts            timestamptz not null default now()
  user_id       int null fk -> users.id on delete set null
  event         text not null            -- see event list below
  ip            text
  detail        jsonb                    -- e.g. {"username": "alice"} on login_fail

Audit events: login_ok, login_fail, logout, user_create, user_update, user_delete, password_reset, setup.

3.3 Session handling

  • Cookie name: clearview_session. Flags: HttpOnly, SameSite=Lax, Secure (set when request is HTTPS; configurable for local-dev HTTP).
  • Cookie value: the sessions.id UUIDv4. No data is encoded in the cookie itself.
  • On every authenticated request:
    1. Read cookie → look up row in sessions.
    2. If missing or expires_at <= now() → 401, delete cookie.
    3. If remember = false → extend expires_at = now() + 8h (sliding).
    4. If remember = true → leave expires_at untouched (fixed 30 days from creation).
    5. Update last_seen_at.
  • Logout: delete the session row and clear the cookie.
  • Cleanup: a lightweight purge of expired sessions runs at login time and is also exposed as a periodic task in the existing worker (cheap query: DELETE FROM sessions WHERE expires_at < now()).

3.4 Initial-setup flow

  • GET /api/auth/setup-required returns {"setup_required": true} iff COUNT(*) FROM users = 0.
  • POST /api/auth/setup accepts {username, password}, succeeds only when the table is empty, creates the user with role='admin', immediately establishes a session (sets cookie), and writes a setup audit row.
  • Once any user exists, both endpoints return 409 / setup_required=false.

3.5 Password policy

  • Server-side check before hashing or updating: len(pw) >= 12 and any(c.isalpha() for c in pw) and any(c.isdigit() for c in pw).
  • Hashing: argon2.PasswordHasher() with library defaults; the encoded string includes salt + parameters, so future tuning is non-breaking.

4. Frontend

The frontend is the existing static site/ (index.html, app.js, styles.css). Changes:

  • New login.html — standalone page, no app-shell. Form with username, password, "remember me" checkbox. Posts to /api/auth/login, then redirects to /.
  • New setup.html — same shell as login, used when /api/auth/setup-required returns true. Posts to /api/auth/setup.
  • app.js:
    • On boot, call GET /api/auth/me. On 401, redirect to /login (or /setup if setup-required).
    • Wrap the fetch helper so any 401 response triggers a redirect to /login.
    • Header shows username (role) + a Logout button that calls POST /api/auth/logout then redirects to /login.
  • New "Users" sidebar tab (admin-only): list / add / edit (username, role, active) / delete users, and a Reset password action that opens a modal.
  • New "Audit" sub-view under Users (admin-only): paged table of auth_audit rows, newest first, with event-type filter.
  • Non-admin users do not see the Users tab; the backend also enforces this independently (defence in depth).

5. Data flow — successful login

  1. Browser POST /api/auth/login with {username, password, remember}.
  2. Server fetches user by username. If not found or is_active=false → 401 + audit login_fail with the supplied username.
  3. Verify password with Argon2id. On mismatch → 401 + audit login_fail.
  4. Insert sessions row (remember flag determines TTL: 8h or 30d).
  5. Set-Cookie: clearview_session=<uuid>; HttpOnly; SameSite=Lax; Secure?; Path=/; Max-Age=<ttl>.
  6. Audit login_ok. Return {username, role}.
  7. Browser then calls GET /api/auth/me and renders the app.

6. Error handling

Situation Response
Missing / invalid / expired session cookie on protected endpoint 401 Unauthorized, cookie cleared.
Authenticated user hitting an admin-only endpoint 403 Forbidden.
Setup endpoint called when users already exist 409 Conflict.
Password policy violation 400 Bad Request with a single human-readable message.
Argon2 verification raising any exception Treated as failed login (no info leak).

7. Testing

Pytest cases (added in the existing test layout, mirroring current patterns):

  • Password hashing round-trip; policy validator edge cases.
  • Login success, wrong password, unknown user, inactive user.
  • Session expiry: sliding 8h refresh vs fixed 30d remember.
  • Logout invalidates the session.
  • require_user and require_admin reject correctly.
  • Setup endpoint: succeeds when empty, 409 when not empty.
  • User CRUD endpoints: admin allowed, user role gets 403.
  • Audit rows are written for each event.

8. Migration & compatibility

  • Single forward Alembic migration adds the three tables. No changes to existing tables.
  • First deploy on an existing install: the users table is empty → users are redirected to /setup on first visit.
  • No env vars are introduced for credentials. (An optional CLEARVIEW_COOKIE_SECURE toggle may be added so local-dev HTTP still works; default true.)

9. Work breakdown (units of work)

  1. DB models + Alembic migration.
  2. Auth core: hashing, policy, session create/lookup/refresh/revoke, audit helper.
  3. Auth router: login / logout / me / setup-required / setup.
  4. Users router + audit endpoint.
  5. Apply require_user to existing routers; require_admin to user/audit routes.
  6. Frontend: login.html, setup.html, fetch-wrapper 401 handling, header user-badge + logout.
  7. Frontend: Users tab (CRUD + reset password) and Audit sub-view.
  8. Tests for items 25.
  9. Append entries to changelog-develop.md per change.

10. Open items deferred to v2

  • Rate limiting / brute-force lockout.
  • Email-based password reset.
  • MFA / SSO via Microsoft Entra (would reuse existing tenant app-registration plumbing).
  • Per-tenant data scoping for non-admin users.