Files
Vorrania/README.md
T
ScarriffleandClaude Opus 5 254737086e Sitzungsdauer einstellbar, bis hin zu unbegrenzt
Die Anmeldung galt fest 7 Tage - und die Frist lief ab dem *Anmelden*, nicht
ab der letzten Nutzung. Wer die App taeglich benutzte, flog trotzdem
punktgenau nach einer Woche hinaus.

Beides ist jetzt geregelt:

- Einstellungen -> Anmeldung: 7 Tage bis 1 Jahr oder unbegrenzt. Gilt fuer
  Website und App gemeinsam, weil beide dasselbe Token bekommen - auch nach
  Single Sign-on.
- POST /auth/refresh schiebt die Frist beim Start nach vorn. Damit zaehlt die
  eingestellte Dauer ab dem letzten Besuch, und eine geaenderte Einstellung
  greift ohne Neuanmeldung.

"Unbegrenzt" heisst ein Token ganz ohne exp. Das ist bewusst folgenreich: Es
gibt keine Sperrliste, eine erteilte Anmeldung laesst sich nicht einzeln
zurueckziehen. Die Oberflaeche sagt das dazu und nennt den einzigen Ausweg
(JWT_SECRET aendern).

/auth/refresh nimmt bewusst nur JWTs an, keine langlebigen API-Token: aus
einem jederzeit widerrufbaren Zugang (Home Assistant) waere sonst ein
unwiderruflicher geworden.

Der Rueckfallwert aus der Umgebung steigt von 7 auf 30 Tage - er gilt nur noch,
solange nichts eingestellt ist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-24 23:15:46 +02:00

260 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Vorrania – Selfhostbare Vorrats- und Gegenstandsverwaltung
Ein selbst gehosteter Dienst, um sowohl deinen **Lebensmittelvorrat** als auch
allgemeine **Gegenstände** (Haushalt, Elektronik, Kleidung, Verbrauchsmaterial …)
zu verwalten: Ein-/Auslagern per Barcode, Chargen mit MHD, Einzelstücke mit
eigener UID/QR, Mindestbestände, automatische Einkaufsliste, verschachtelte
Lagerorte, Gruppen, Kategorien und Dashboards – mit **Web-UI** und **nativer
iOS-App**, die beide dieselbe REST-API nutzen.
## Was Vorrania verwaltet
**Lebensmittel** laufen als **Chargen** mit Mindesthaltbarkeitsdatum. Beim
Auslagern wird automatisch die zuerst ablaufende Charge entnommen (**FEFO**).
**Gegenstände** kennen drei Arten:
- **Menge je Lagerort** – reines Zählen (z. B. Unterhosen, Batterien).
- **Einzelstücke** – jedes Exemplar mit eigener **UID/QR**, Kaufdatum, Garantie,
Bezugsquelle und Beleg (z. B. Powerbank, Kamera).
- **Verbrauchsgegenstand** – wie ein Lebensmittel als Charge mit Menge und
Einheit geführt (z. B. Sonnencreme in ml), nur ohne eindeutigen Code.
## Features
- **Barcode-Lookup** über [Open Food Facts](https://world.openfoodfacts.org) und
Open Products Facts mit lokalem Fallback; unbekannte Produkte selbst anlegen,
Bild wird automatisch geholt.
- **Chargen mit MHD & FEFO**, Mengen in Packungen **oder** Teilmengen
(Basiseinheit Stück / Gramm / Milliliter plus Packungsgröße/Gebinde).
- **Lagerorte** beliebig verschachtelt (Keller → Regal 2 → Fach A), mit
**QR-Etiketten**, **Inhaltsansicht** („was liegt hier?“), Umlagern und
Entfernen mit Grund. Chargen und Einzelstücke sind einem Lagerort zugeordnet.
- **Einzelstücke verleihen** mit Name und Grund als Freitext und optionalem
Rückgabedatum – auch direkt nach dem Scannen des QR/NFC-Tags. Der Lagerort
bleibt stehen (dorthin gehört das Stück zurück), als verliehen zählt es aber
nicht mehr zum Bestand. Zurückgegebene Verleihe bleiben als Verlauf erhalten;
überfällige Rückgaben sind in Liste und Dashboard rot.
- **Kategorien** verschachtelt und pro Typ (Lebensmittel/Gegenstand); für
Gegenstände mit **eigenen Feldern** (z. B. Kapazität, Größe, Farbe).
- **Gruppen** rechnen Bestände mehrerer Marken/Artikel zusammen (z. B. „5 kg
Mehl“) mit eigenem Mindestbestand und Einheit.
- **Mindestbestände** je Produkt und Gruppe – gesamt **und je Lagerort** – mit
zentraler Übersichtsseite, daraus die automatische **Einkaufsliste** und
**Ablaufwarnung** für bald ablaufende Chargen.
- **Übergreifende Listen** für Produkte (getrennt Lebensmittel/Gegenstände),
Einzelstücke und Chargen – filter-, sortier- und spaltenkonfigurierbar; bei
Chargen lässt sich der Lagerort mehrerer auf einmal setzen.
- **Dashboards** mit konfigurierbaren Karten und Diagrammen, **Verlauf** (wer hat
wann was ein-/ausgelagert), **Stammdaten** (Einheiten, Gebinde, Shops) und
**Import/Export**.
- **Mehrbenutzer mit Rollen:** *Admin* verwaltet Stammdaten und Benutzer,
*Nutzer* lagert nur ein/aus und sieht Bestände; alle Bewegungen werden
protokolliert.
- **Single Sign-on (optional)** über OpenID Connect, z. B. gegen ein selbst
gehostetes [Authentik](https://goauthentik.io). Der Passwort-Login bleibt
daneben bestehen – siehe [Single Sign-on](#single-sign-on-optional).
- **Sitzungsdauer einstellbar** (Einstellungen → Anmeldung): 7 Tage bis 1 Jahr
oder unbegrenzt, für Website und App gemeinsam. Die Frist zählt ab dem letzten
Besuch, nicht ab dem Anmelden.
### In der iOS-App zusätzlich
- **Scannen** von Barcode, **MHD/Datum per Texterkennung** (auch nur Tag+Monat
ohne Jahr bei Frischware), **Lagerort-QR** und **Name/Marke per Text-Scan**.
- **Verleihen direkt nach dem Scannen** – QR, eingenähtes NFC-Tag oder
angetipptes Tag landen alle im Einzelstück, wo Herausgeben und Zurücknehmen
gleich oben stehen.
- **Foto direkt beim Anlegen** (Kamera/Galerie), **Push-Benachrichtigungen** bei
ablaufenden Produkten, lokale Dashboards und Home-Screen-Schnellaktionen.
Details zur App: [ios/README.md](ios/README.md).
## Architektur
| Teil | Technik |
|---------|----------------------------------------|
| Backend | Python · FastAPI · SQLAlchemy |
| Anmeldung | Passwort (JWT) und optional OpenID Connect |
| DB | PostgreSQL (SQLite für Tests/lokal) |
| Web-UI | React · Vite (via nginx ausgeliefert) |
| iOS | Native SwiftUI-App (VisionKit-Scanner) |
| Deploy | Docker Compose · `install.sh` |
```
backend/ FastAPI-App + Tests
web/ React/Vite SPA
ios/ Native SwiftUI-App (XcodeGen-Projekt)
deploy/ docker-compose.yml, install.sh, update.sh, .env.example
docs/ Roadmap
```
Das Schema wird beim Start per idempotenter `ALTER TABLE`-Migration in
`backend/app/main.py` nachgezogen (kein Alembic).
## Installation (selfhosted, Linux)
Voraussetzung: eine Linux-Maschine (Server, NAS, Raspberry Pi …). Docker wird bei
Bedarf automatisch installiert.
```bash
git clone https://git.scarriffle.com/Scarriffle/vorrania.git
cd vorrania/deploy
chmod +x install.sh
./install.sh # interaktiv – fragt Port & Admin-Passwort
# oder vollautomatisch mit generiertem Admin-Passwort:
# ./install.sh --yes
```
> Ist das Gitea-Repo privat, fragt `git clone` nach Benutzername + Access-Token.
> Falls `git` fehlt: `apt update && apt install -y git`.
Nach dem Start:
- Web-UI: `http://<server-ip>:8080` (Port konfigurierbar)
- Anmeldung mit dem im Installer angezeigten Admin-Benutzer/Passwort.
Die iOS-App wird mit derselben Adresse verbunden (`http://<server-ip>:8080`).
## Single Sign-on (optional)
Neben dem Passwort-Login kann sich Vorrania an einen OpenID-Connect-Dienst
hängen (getestet mit Authentik). Ohne Konfiguration ändert sich nichts: Es gibt
keinen SSO-Knopf, und der Passwort-Login bleibt der einzige Weg.
**1. In Authentik zwei Anwendungen anlegen** – Web-UI und App brauchen
unterschiedliche Client-Typen, und ein Provider kann nur einer davon sein:
| Für | Client-Typ | Redirect-URI |
|-----|-----------|--------------|
| Web-UI | *Confidential* (mit Secret) | `https://<deine-adresse>/api/auth/oidc/authentik/callback` |
| iOS-App | *Public* (ohne Secret, PKCE) | `vorrania://oauth/callback` |
Drei Punkte, an denen es sonst klemmt:
- Das `/api` in der Web-Adresse ist kein Versehen: Es entsteht im mitgelieferten
nginx und gehört deshalb in die bei Authentik hinterlegte Adresse.
- **Signing Key setzen** (Provider ▸ *Advanced protocol settings*). Ohne ihn
signiert Authentik mit HS256, und Vorrania prüft gegen den öffentlichen
Schlüsselsatz – das Token würde abgelehnt.
- Jede Anwendung hat in Authentik **ihren eigenen Issuer** (aus dem
Application-Slug). Deshalb bekommt der App-Client eine eigene
Discovery-Adresse: `OIDC_AUTHENTIK_NATIVE_DISCOVERY_URL`.
Damit die App ihre Anmeldung im Hintergrund erneuern kann, muss der öffentliche
Client `offline_access` ausliefern dürfen (in Authentik seit 2024.2 ein eigenes
Scope-Mapping) – sonst kommt kein Refresh-Token zurück.
**2. `deploy/.env` ergänzen** (vollständige Vorlage in `deploy/.env.example`):
```bash
PUBLIC_BASE_URL=https://vorrania.example.com
OIDC_PROVIDERS=authentik
OIDC_AUTHENTIK_DISPLAY_NAME=Authentik
OIDC_AUTHENTIK_DISCOVERY_URL=https://authentik.example.com/application/o/vorrania/.well-known/openid-configuration
OIDC_AUTHENTIK_CLIENT_ID=...
OIDC_AUTHENTIK_CLIENT_SECRET=...
OIDC_AUTHENTIK_NATIVE_CLIENT_IDS=vorrania-ios
OIDC_AUTHENTIK_NATIVE_DISCOVERY_URL=https://authentik.example.com/application/o/vorrania-ios/.well-known/openid-configuration
```
Danach `./update.sh` (oder `docker compose --env-file .env up -d`).
Ob es geklappt hat, verrät `curl http://<server>:8080/api/auth/oidc/providers`.
**3. Bestehende Konten verknüpfen.** Beim ersten SSO-Login zu einer noch
unbekannten Kennung fragt Vorrania nach, statt still ein zweites Konto
anzulegen:
> Die Anmeldung über Authentik hat geklappt – zu diesem Zugang gibt es hier aber
> noch kein Konto.
> **[ Bestehendes Konto verknüpfen ]** oder **[ Neues Konto anlegen ]**
Das Verknüpfen verlangt einmalig Benutzername und Passwort des bestehenden
Kontos. Dieser Nachweis ist der Grund, warum der Anmeldeweg selbst nie fragt
„welches Konto?" – ohne ihn könnte sich jeder mit einem beliebigen Konto beim
Anmeldedienst an ein fremdes Vorrania-Konto hängen. Rolle und Verlauf bleiben
dabei erhalten.
Zwei Wege führen daran vorbei, weil sie das Konto schon vorher finden:
- **E-Mail-Adresse** unter *Benutzer* eintragen – dieselbe wie in Authentik.
Verknüpft wird nur bei **bestätigter** Adresse.
- **Vorab verknüpfen**: angemeldet bleiben, unten in der Seitenleiste auf den
eigenen Namen → **Mein Konto** → *Verknüpfen*. Dort lässt sich eine
Verknüpfung auch wieder lösen – außer es wäre die letzte Anmeldemöglichkeit.
Mit `OIDC_AUTHENTIK_AUTO_CREATE=false` entfällt der Knopf „Neues Konto anlegen";
dann bleibt nur das Verknüpfen mit einem Konto, das der Admin angelegt hat.
Die App zeigt diesen Dialog nicht – dort gilt weiterhin: bekannte Kennung oder
passende Adresse, sonst je nach `AUTO_CREATE` ein neues Konto oder eine Absage.
Einmal im Browser verknüpft, funktioniert die App danach ohne Weiteres.
Weitere Anbieter sind reine Konfiguration: Kürzel in `OIDC_PROVIDERS` ergänzen
und einen zweiten Block mit dessen Präfix anlegen (`OIDC_<KÜRZEL>_…`) – am Code
ändert sich dafür nichts.
### Was dabei bewusst so gebaut ist
- Der Anmeldedienst ist **nur für die Anmeldung** zuständig. Danach stellt
Vorrania sein eigenes Token aus, wie beim Passwort-Login – Rollen, API-Tokens
und alle Endpunkte bleiben unverändert.
- **Rollen kommen nie aus dem Anbieter.** Neu angelegte SSO-Konten sind immer
*Benutzer*; Admin vergibt ein Admin von Hand.
- Der **Benutzername** wird einmal beim Anlegen übernommen und gehört danach
Vorrania – ein späterer Namenswechsel beim Anmeldedienst schlägt nicht durch.
Ändern lässt er sich jederzeit unter *Benutzer*: Sitzungen, Verlauf und
API-Tokens hängen an der Benutzer-ID, nicht am Namen.
- **Automatisch** verknüpft wird nur bei bestätigter E-Mail-Adresse. Sonst
könnte ein Konto im Anmeldedienst mit frei gewählter Adresse ein fremdes
Vorrania-Konto übernehmen. Beim Verknüpfen über *Mein Konto* entfällt das,
weil dort die eigene Sitzung bürgt – eine bereits anderweitig vergebene
Kennung wird aber auch dort abgelehnt.
- Die App meldet sich ausschließlich im **System-Browser** an (AppAuth mit
PKCE), nie in einem eingebetteten WebView, und sendet nie ein Client-Secret.
## Aktualisieren
```bash
cd vorrania/deploy
./update.sh # holt den neuesten Stand aus Git und baut neu
./update.sh --prune # zusätzlich alte Images aufräumen
./update.sh --no-pull # nur neu bauen, ohne git pull
```
Das Skript ist von überall aufrufbar, nutzt die vorhandene `deploy/.env` und
prüft am Ende, ob das Backend wieder erreichbar ist.
Verwaltung:
```bash
cd deploy
docker compose logs -f # Logs ansehen
docker compose down # stoppen
docker compose up -d # starten
```
Die Konfiguration liegt in `deploy/.env` (Passwörter, Port, JWT-Secret). Für den
Produktivbetrieb bitte hinter einen HTTPS-Reverse-Proxy (z. B. Caddy/Traefik) stellen.
## Entwicklung (ohne Docker)
**Backend:**
```bash
cd backend
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# lokale SQLite-DB nutzen:
export DATABASE_URL="sqlite:///./vorrania.db"
uvicorn app.main:app --reload
```
API-Doku dann unter `http://localhost:8000/docs`.
**Web:**
```bash
cd web
npm install
npm run dev # http://localhost:5173, proxyt /api → localhost:8000
```
**iOS:** siehe [ios/README.md](ios/README.md) (XcodeGen-Projekt, `xcodegen generate`).
**Tests:**
```bash
cd backend
pip install -r requirements.txt
DATABASE_URL="sqlite://" pytest
```
## Weiteres
Fahrplan und Ideen: [docs/ROADMAP.md](docs/ROADMAP.md).