# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Commands ```bash # Format make reformat # uv run ruff format src/ tests/ # Lint (with auto-fix) make lint # uv run ruff check src/ tests/ --fix # Type checking make typecheck # uv run ty check src/ # Tests make test # uv run python -m pytest -v uv run pytest tests/test_foo.py::test_specific # single test # All checks make check # Dev server OIDC_OP_ISSUER=http://localhost:8000 OIDC_OP_DEBUG=true \ uv run uvicorn porchlight.app:create_app \ --factory --host 127.0.0.1 --port 8000 --reload --reload-dir src ``` ### End-to-end tests (Playwright/Node) ```bash # One-time setup cd tests/e2e && npm install && npm run setup && cd ../.. # Run e2e tests (starts/stops the server automatically) ./tests/e2e/run.sh # Run a specific spec ./tests/e2e/run.sh tests/e2e/login.spec.js # Run with visible browser E2E_HEADLESS=0 ./tests/e2e/run.sh ``` ## Architecture Porchlight is an OpenID Connect (OIDC) Provider + user management app built on **FastAPI** with **idpyoidc** for the OIDC protocol layer. UI is server-side **Jinja2** templates with **HTMX** for interactivity. Storage defaults to **SQLite** via `aiosqlite`. ### Request flow ``` Browser/RP → FastAPI routes → Service classes → Repository layer → SQLite ↕ idpyoidc (OIDC protocol) ``` ### Module layout - `src/porchlight/app.py` — FastAPI app factory (`create_app()`), lifespan, middleware stack - `src/porchlight/config.py` — Settings loaded from env vars (`OIDC_OP_*` prefix) and optional TOML file - `src/porchlight/models.py` — Pydantic domain models (`User`, `WebAuthnCredential`, `MagicLink`, `Consent`) - `src/porchlight/validation.py` — Form-validation models (`ProfileUpdate` with email/phone/URL validation) - `src/porchlight/dependencies.py` — FastAPI dependency injection helpers - `src/porchlight/csrf.py` — Synchronizer-token CSRF middleware (exempt: `/token`, `/userinfo`) - `src/porchlight/authn/` — Password (`argon2`) and WebAuthn (`fido2`) authentication services + routes - `src/porchlight/manage/` — Authenticated user credential & profile management routes - `src/porchlight/admin/` — Admin user management routes (user list, invite creation, profile editing) - `src/porchlight/invite/` — Magic-link invitation service (proquint tokens, time-limited) - `src/porchlight/oidc/` — OIDC endpoints (discovery, JWKS, `/authorization`, `/token`, `/userinfo`) - `src/porchlight/store/` — Repository pattern: `protocols.py` defines interfaces; `sqlite/` has the concrete implementation; `mongodb/` is a stub - `src/porchlight/cli.py` — Typer CLI (`create-invite`, `initial-admin`) ### Storage layer `store/protocols.py` defines `UserRepository`, `CredentialRepository`, `MagicLinkRepository`, and `ConsentRepository` as Protocol classes. The SQLite implementation lives in `store/sqlite/repositories.py`. Migrations run automatically on startup from `store/sqlite/migrations/*.sql`. ### Configuration All settings use the `OIDC_OP_` env-var prefix (see `config.py`). Key vars: | Variable | Notes | |---|---| | `OIDC_OP_ISSUER` | **Required.** Must match the public-facing URL. | | `OIDC_OP_SESSION_SECRET` | Session signing key | | `OIDC_OP_DEBUG` | Enables Swagger UI | | `OIDC_OP_SQLITE_PATH` | DB file path (default `data/oidc_op.db`) | | `OIDC_OP_CONFIG_FILE` | Optional TOML file for clients and advanced config | OIDC relying parties (clients) are defined in the TOML config file under `[clients.]`. ### Test fixtures `tests/conftest.py` provides: - `settings` — in-memory SQLite, test issuer - `client` — async `httpx.AsyncClient` with ASGI transport - CSRF token extraction helper Unit and integration tests use in-memory SQLite; e2e tests use a seeded file-based DB (`tests/e2e/setup_db.py`).