# 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/`. ```text 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=; HttpOnly; SameSite=Lax; Secure?; Path=/; Max-Age=`. 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 2–5. 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.