From 5b649f96f25f6be940c33aa8580ba4edf84bdbe8 Mon Sep 17 00:00:00 2001 From: Bjoern Runden Date: Wed, 17 Jun 2026 17:17:18 +0200 Subject: [PATCH] git init --- ,gitignore | 0 CLAUDE.md | 83 +++++++++++++++++++ _templates/_template.html | 14 ++++ _templates/_template.py | 10 +++ claude-context.md | 166 ++++++++++++++++++++++++++++++++++++++ strukturelle-vorgaben.md | 131 ++++++++++++++++++++++++++++++ 6 files changed, 404 insertions(+) create mode 100644 ,gitignore create mode 100644 CLAUDE.md create mode 100644 _templates/_template.html create mode 100644 _templates/_template.py create mode 100644 claude-context.md create mode 100644 strukturelle-vorgaben.md diff --git a/,gitignore b/,gitignore new file mode 100644 index 0000000..e69de29 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..2cfe8c8 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,83 @@ +# 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. diff --git a/_templates/_template.html b/_templates/_template.html new file mode 100644 index 0000000..66b6b2c --- /dev/null +++ b/_templates/_template.html @@ -0,0 +1,14 @@ +{# + Datei: templates/.html + Autor: + Datum: YYYY-MM-DD + Zweck: + +#} +{% extends "base.html" %} + +{% block title %}{% endblock %} + +{% block content %} + {# Inhalt hier #} +{% endblock %} diff --git a/_templates/_template.py b/_templates/_template.py new file mode 100644 index 0000000..7346e24 --- /dev/null +++ b/_templates/_template.py @@ -0,0 +1,10 @@ +""" + + + + +:author: +:date: YYYY-MM-DD +:version: 1.0 +""" diff --git a/claude-context.md b/claude-context.md new file mode 100644 index 0000000..1510ce7 --- /dev/null +++ b/claude-context.md @@ -0,0 +1,166 @@ + +# System Prompt: Flask OIDC Showcase App with Zitadel (Advanced Dynamic RBAC) + +## 1. Role & Objective +You are an expert Python Backend Engineer and Cloud-Native Infrastructure Specialist. Your objective is to build a complete, clean Proof of Concept (PoC) / Showcase Web Application using **Python Flask 2.x**. + +The primary goal of this application is to demonstrate and validate fine-grained, dynamic Role-Based Access Control (RBAC) and multi-tenancy project assignment using **Zitadel** as an External Identity Provider (IdP) via **OIDC (Authlib)**. + +The secondary goal is to show a "flat" architecture where user authentication and global identity management are completely offloaded to the IdP (Zitadel Self-Service), while complex relational authorization, project assignments, and data separation are handled locally via **SQLite**. + +--- + +## 2. Tech Stack & Architecture +* **Backend Framework:** Python Flask 2.x (running inside a virtual environment `venv`). +* **OIDC Library:** `Authlib` (configured to interact with Zitadel's OpenID Connect endpoints). +* **Database / ORM:** SQLite using SQLAlchemy (to manage local relational mappings). +* **Configuration:** 12-Factor-App style using environment variables via a `.env` file. +* **Deployment & Multi-Environment:** Dual-purpose Git repository structure supporting: + 1. **Local Development:** Native execution via `flask run` inside `venv`. + 2. **Production:** Containerized execution using a lean, production-ready Docker image (`python:3.11-slim` or equivalent) with `gunicorn`, orchestrated via `docker-compose.yml`. + +--- + +## 3. Directory Structure +Generate the repository using the following clean layout: + +```text +. +├── .gitignore +├── .env.example +├── README.md +├── requirements.txt +├── docker-compose.yml +├── Dockerfile +├── app/ +│ ├── __init__.py +│ ├── config.py +│ ├── database.py +│ ├── models.py +│ ├── auth.py +│ ├── routes.py +│ ├── templates/ +│ │ ├── base.html +│ │ ├── login_error.html +│ │ ├── index.html (Dashboard with test matrix) +│ │ ├── admin.html (Global export & management) +│ │ ├── mitarbeiter.html (Project selection overview) +│ │ └── projekt.html (Project cockpit with notes log) +│ └── static/ +│ └── css/ +│ └── style.css + +``` + +--- + +## 4. Database Schema & Zitadel Role Mapping + +The application relies on Zitadel for global identity authentication. Upon successful login, the app extracts the user's primary identity (`sub` / User ID) and their assigned global roles from the OIDC UserInfo / ID Token. + +### Zitadel Global Roles (Managed in IdP): + +1. **Admin:** Has global read/write privileges over all local data and management views. +2. **Mitarbeiter** (Internal Employee): Allowed to see and interact with internal projects they are assigned to. +3. **Kunde** (External Client User): Tied to a client identity; allowed to see specific project deliverables. + +### SQLite Relational Schema (SQLAlchemy Models): + +To support advanced multi-tenancy and dynamic project scoping, implement the following relational schema: + +* **Users Table:** Stores the `zitadel_user_id` (Primary Key from OIDC token), `email`, and cached `global_role`. +* **Clients Table (Kundenstamm):** Represents the top-level company organization (e.g., "Firma Müller"). +* **Projects Table (Kundenprojekte):** Represents specific project contexts belonging to a Client (e.g., "Akquise Landkreis", "Akquise Bundesstaat", "Spezielle Produktpalette"). Each project belongs to exactly **one** Client. +* **Project_Mitarbeiter_Mapping (N:M Table):** Links internal employees (`Mitarbeiter`) to multiple projects. A single project can also have multiple assigned employees. +* **Project_Kunden_Mapping (N:M Table):** Links external client users (`Kunde`) to specific projects within their parent client organization. +* **Notes Table (Korrespondenz/Notizen):** Text logs containing timestamps, an author reference (`user_id`), and a foreign key to a specific `Project`. These represent project-specific internal notes. + +--- + +## 5. Frontend & Routing Requirements (The Showcase Test Matrix) + +### Unauthenticated / Access Denied State + +* Any unauthorized access attempt, failed login, or role-check violation must redirect the user to `login_error.html`. +* **Error Page Text:** "Bitte besuch uns später wieder, du bist im Moment nicht autorisiert." +* **Crucial Feature:** This page must feature a **"Zurück" (Back) button** that programmatically returns the user to their previous browser/application context (simulating the browser's back-button history) to allow fluid end-to-end testing of authorization boundaries. + +### Authenticated State (The Dashboard Matrix) + +Upon successful OIDC login, the user lands on the index dashboard (`index.html`). This page must display up to **four functional testing paths** dynamically rendered based on the active user's permissions: + +1. **Path: Admin Cockpit** +* *Access Rule:* Restricted strictly to users with the global Zitadel role `Admin`. +* *Features:* A master panel allowing full visibility into Clients, Projects, and User-to-Project mappings. Includes a mockup button for a **"Gesamtexport"** (Global Data Export) spanning all database tables. + + +2. **Path: Mitarbeiter Cockpit** +* *Access Rule:* Restricted strictly to users with the global Zitadel role `Mitarbeiter`. +* *Features:* Displays an overview listing *only* those Projects the employee is actively mapped to via the local N:M table. Clicking on a project routes them to the Project View. + + +3. **Path: Kundenprojekt View (Authorized Context)** +* *Access Rule:* Accessible if the logged-in user is an internal `Mitarbeiter` mapped to this project **OR** an external `Kunde` mapped to this specific project. +* *Features:* Displays project details and the associated **Notes/Korrespondenz Log**. Users can view notes and click an action to extract/export notes *strictly limited* to this project's scope. + + +4. **Path: Kunde außerhalb des Projekts (Failure Test Case)** +* *Access Rule:* This button simulates an attack or a misconfiguration test where an authenticated `Kunde` or `Mitarbeiter` attempts to force-navigate to a project ID/URL they are **not** mapped to in the local SQLite database. +* *Expected Behavior:* The routing guard must intercept this request, deny access, and immediately redirect to the Login Error Page. + + + +--- + +## 6. Environment & Deployment Configuration + +### `.env` File Setup + +Provide an `.env.example` file formatted exactly as follows: + +```env +FLASK_APP=app +FLASK_ENV=development +SECRET_KEY=replace-with-a-secure-random-key-in-prod +ZITADEL_CLIENT_ID=your-zitadel-client-id +ZITADEL_CLIENT_SECRET=your-zitadel-client-secret +ZITADEL_DISCOVERY_URL=https://.zitadel.cloud/.well-known/openid-configuration +DATABASE_URL=sqlite:///app.db + +``` + +### Local Native Execution + +Include instructions in the `README.md` to initialize the virtual environment natively: + +```bash +python3 -m venv venv +source venv/bin/activate +pip install -r requirements.txt +flask run + +``` + +### Containerized Cloud-Native Execution + +* **Dockerfile:** Create an ultra-slim, secure multi-stage or standard Docker production build using a lightweight Linux layer (e.g., `python:3.11-slim`). It must inject environment variables, resolve dependencies via `requirements.txt`, and bind the runtime application to a `gunicorn` production server. +* **docker-compose.yml:** Configure on-the-fly image building. Do **not** use pre-built registry images. It must read the `.env` file, bind ports, mount the SQLite database directory as a persistent volume if needed, and run out-of-the-box via `docker compose up --build`. + +eine bedingung wäre, dass bei aktualisierung vom basis-container auch das Build erneut ausgeführt wird. + +Es soll sich operativ für die Prod-Umgebung auch nativ anfühlen wie eine Prod-Umgebung. +Handhabbar, belastbar, etc. + +--- + +## 7. Developer-Experience & Validation Guidelines + +1. **Real-time OIDC Logging:** Configure the Flask application to print incoming OIDC token payloads (specifically the unique user `sub`, token scopes, and claim-based roles) cleanly into standard output (`stdout`) during development. This allows real-time analysis of the Zitadel handshake. +2. **Database Seeding (`flask seed-db`):** Include a custom Flask CLI command to automate local testing. The script must initialize the SQLite schema and pre-populate mock data: +* At least 1 Client record ("Firma Müller"). +* At least 3 separate Project records under "Firma Müller" (e.g., "Akquise Landkreis", "Akquise Bundesstaat"). +* Mock rows in the N:M tables linking explicit test user IDs to specific projects. +* Sample records in the Notes table attached to those projects. + + +3. **Clean Code Focus:** Keep CSS and page layouts minimal, clean, and unbloated. The engineering focus must remain entirely on the strictness of the routing guards, database queries, and the security handshake. diff --git a/strukturelle-vorgaben.md b/strukturelle-vorgaben.md new file mode 100644 index 0000000..2fc1c0b --- /dev/null +++ b/strukturelle-vorgaben.md @@ -0,0 +1,131 @@ +# Strukturelle Vorgaben – Quellcode-Konventionen + +Dieses Dokument legt verbindliche Konventionen für alle Quelldateien im Projekt fest. + +--- + +## 1. Datei-Header + +Jede Quelldatei beginnt mit einem standardisierten Header. Der Header dient gleichzeitig als Modul-Docstring und wird von **Sphinx** zur automatischen Dokumentationserzeugung ausgewertet (Python-Äquivalent zu JavaDoc). + +### Python (`.py`) + +Der Header ist ein Modul-Docstring (triple-quoted) direkt am Dateianfang. Sphinx liest ihn automatisch aus: + +```python +""" + + + + +:author: Vorname Nachname +:date: YYYY-MM-DD +:version: 1.0 +""" +``` + +Pflichtfelder: `:author:`, `:date:` +Optionale Felder: `:version:`, `:see:`, `:note:` + +### Jinja2 / HTML (`.html`) + +Jinja2-Kommentare (`{# ... #}`) werden vom Browser nicht gerendert und sind deshalb die richtige Wahl für Template-Header: + +```html +{# + Datei: templates/beispiel.html + Autor: Vorname Nachname + Datum: YYYY-MM-DD + Zweck: Kurze Beschreibung, welche Seite/Komponente dieses Template rendert + und welche Jinja2-Variablen es erwartet. +#} +``` + +Pflichtfelder: `Datei:`, `Autor:`, `Datum:`, `Zweck:` + +--- + +## 2. Dokumentationserzeugung mit Sphinx + +Python bietet mit **Sphinx** ein mächtiges Werkzeug analog zu JavaDoc. Es liest Modul-Docstrings (inkl. `:author:`/`:date:`-Felder) und erzeugt daraus eine vollständige HTML-Dokumentation. + +### Einrichtung (einmalig) + +```bash +pip install sphinx +cd docs/ +sphinx-quickstart # Interaktiver Setup-Assistent +``` + +Im Setup-Assistenten: +- `autodoc` aktivieren (liest Docstrings automatisch) +- Projektname, Autor und Version eingeben + +Anschließend in `docs/conf.py` sicherstellen: + +```python +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.viewcode", # Links zum Quellcode +] +``` + +### Dokumentation aus dem `app/`-Verzeichnis einlesen + +```bash +sphinx-apidoc -o docs/source/ app/ +``` + +Dieser Befehl scannt alle `.py`-Dateien unter `app/` und erzeugt `.rst`-Stub-Dateien, die den Modul-Docstring und alle Klassen/Funktionen einbinden. + +### Dokumentation bauen + +```bash +cd docs/ +make html +# Ergebnis liegt unter: docs/_build/html/index.html +``` + +### Ablauf + +``` +Modul-Docstrings → sphinx-apidoc → .rst-Dateien → make html → HTML-Doku +``` + +> **Hinweis:** Für HTML-Templates gibt es kein Python-natives Doku-Tool. Die Header-Kommentare dienen dort als Inline-Dokumentation für Entwickler. + +--- + +## 3. Template-Dateien + +Im Verzeichnis [`_templates/`](_templates/) liegen Vorlagen, von denen sich der Entwickler **jederzeit eine frische Kopie ziehen** kann: + +| Template | Verwendung | +|---|---| +| [`_templates/_template.py`](_templates/_template.py) | Neue Python-Quelldatei | +| [`_templates/_template.html`](_templates/_template.html) | Neues Jinja2/HTML-Template | + +### Workflow: neue Datei aus Template erzeugen + +```bash +# Python-Datei +cp _templates/_template.py app/mein_modul.py + +# HTML-Template +cp _templates/_template.html app/templates/meine_seite.html +``` + +Anschließend im kopierten File die Platzhalter (`<...>`) ersetzen. + +--- + +## 4. Kurzreferenz – Pflichtfelder auf einen Blick + +| Feldname | Python (Docstring) | HTML (Kommentar) | Pflicht | +|---|---|---|---| +| Autor | `:author:` | `Autor:` | Ja | +| Datum | `:date:` | `Datum:` | Ja | +| Zweck/Titel | Erste Docstring-Zeile | `Zweck:` | Ja | +| Version | `:version:` | – | Nein | +| Dateiname | – | `Datei:` | Ja |