# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview Flask 2.x Proof-of-Concept to demonstrate fine-grained, dynamic RBAC and multi-tenancy using **Zitadel** as OIDC IdP. Authentication is fully delegated to Zitadel; authorization and project assignments are handled locally via SQLite. The full specification is in [claude-context.md](claude-context.md). ## Commands ### Local Development (venv) ```bash python3 -m venv venv source venv/bin/activate pip install -r requirements.txt cp .env.example .env # then fill in Zitadel credentials flask db-init # initialize SQLite schema flask seed-db # seed mock data (Firma Müller, 3 projects, sample notes) flask run ``` ### Docker / Production ```bash docker compose up --build # builds image from Dockerfile, reads .env docker compose down ``` ### Database CLI commands ```bash flask seed-db # wipes and re-seeds the SQLite database with test fixtures ``` ## Architecture **Flat backend, rich local authorization:** - **Zitadel (external)** — handles login, issues OIDC tokens carrying the user's global role claim (`Admin`, `Mitarbeiter`, or `Kunde`). - **`app/auth.py`** — Authlib OIDC client; extracts `sub`, role claims, and token scopes from the IdP response; logs token payload to stdout in development. - **`app/models.py`** — SQLAlchemy models for the local relational layer: - `User` (keyed on Zitadel `sub`) - `Client` (top-level company, e.g. "Firma Müller") - `Project` (belongs to one Client) - `ProjectMitarbeiterMapping` (N:M: employees ↔ projects) - `ProjectKundenMapping` (N:M: external users ↔ projects) - `Note` (timestamped text entry tied to a Project) - **`app/routes.py`** — all routing guards live here; unauthorized access always redirects to `login_error.html` (never raises HTTP errors). - **`app/config.py`** — reads `.env`; follows 12-Factor style. - **`app/database.py`** — SQLAlchemy engine/session setup. ## Role-Based Access (RBAC) Logic | Role | Access | |---|---| | `Admin` | Full visibility; all clients, projects, users; global export | | `Mitarbeiter` | Only projects linked via `ProjectMitarbeiterMapping` | | `Kunde` | Only projects linked via `ProjectKundenMapping` for their client | **Routing guard pattern:** Every protected route checks (1) Zitadel role from session, (2) local DB mapping for the requested resource. Any mismatch → redirect to `/error` (renders `login_error.html`). The error page has a JS back-button to support test flows. ## Environment Variables See `.env.example`: ``` FLASK_APP=app FLASK_ENV=development SECRET_KEY=... ZITADEL_CLIENT_ID=... ZITADEL_CLIENT_SECRET=... ZITADEL_DISCOVERY_URL=https://.zitadel.cloud/.well-known/openid-configuration DATABASE_URL=sqlite:///app.db ``` ## Docker Notes - `Dockerfile` uses `python:3.11-slim`, runs app via `gunicorn`. - `docker-compose.yml` builds from source (no pre-built images), mounts a volume for the SQLite file, and reads `.env` directly. - Setting `pull_policy: always` (or equivalent) in the compose file ensures a base-image update triggers a rebuild.