Neues "users"-Table (bcrypt-Hash, nie Klartext), POST /api/auth/login gibt ein selbst-signiertes JWT (HS256) aus. requireAuth unterscheidet MSAL- (RS256) und lokale Tokens (HS256) anhand des alg-Headers. Server legt beim Start optional ein erstes lokales Konto an, wenn SEED_ADMIN_USERNAME/SEED_ADMIN_PASSWORD als Stack-Env gesetzt sind (idempotent, Klartext-Passwort landet nie im Repo). Frontend: Login-Screen hat jetzt einen Alternativ-Link zu Benutzername/Passwort, App.tsx kombiniert MSAL- und lokalen Auth-Status. Temporärer test-build-only.yml Workflow zur Docker-Build-Verifikation ohne Registry-Push (wird nach dem Test wieder entfernt). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
120 lines
5.7 KiB
Markdown
120 lines
5.7 KiB
Markdown
# BergVOLT Materialschein
|
||
|
||
Materialschein & Kabelrechner für BergVOLT Elektrotechnik. React + Vite + TypeScript
|
||
(PWA) im Client, Express + TypeScript + Prisma im Server, Postgres als Datenbank.
|
||
Anmeldung über Microsoft Entra ID (gleiche App-Registrierung wie das Zeiterfassungs-Tool).
|
||
|
||
## Lokale Entwicklung
|
||
|
||
```bash
|
||
# 1. Postgres lokal starten
|
||
docker compose -f docker-compose.dev.yml up -d
|
||
|
||
# 2. Server
|
||
cd server
|
||
npm install
|
||
echo 'DATABASE_URL="postgresql://materialschein:dev@localhost:5433/materialschein"' > .env
|
||
echo 'MSAL_CLIENT_ID="b1cd974c-77ce-4d93-b6e8-00e41a31adc8"' >> .env
|
||
echo 'MSAL_TENANT_ID="d9adeaaf-853b-4c4e-a822-8f1bedbb84f6"' >> .env
|
||
echo 'PORT=8787' >> .env
|
||
npx prisma migrate deploy
|
||
npm run dev # läuft auf :8787
|
||
|
||
# 3. Client (neues Terminal)
|
||
cd client
|
||
npm install
|
||
npm run dev # läuft auf :5173, proxied /api auf :8787
|
||
```
|
||
|
||
## Architektur
|
||
|
||
- `client/` – React 18 + Vite + TypeScript, `vite-plugin-pwa` für Manifest/Service Worker.
|
||
MSAL.js (`@azure/msal-browser` + `@azure/msal-react`) für den Microsoft-Login.
|
||
- `server/` – Express + TypeScript, validiert das MSAL-ID-Token serverseitig (JWKS von
|
||
`login.microsoftonline.com`) und liefert zusätzlich die gebaute Client-App aus
|
||
(`public/` = `client/dist`, SPA-Fallback für React-Router).
|
||
- `server/prisma/schema.prisma` – zwei Tabellen: `projekte` (Baustellen) und
|
||
`material` (Positionen je Projekt, per `projektId` verknüpft).
|
||
- Ein Docker-Image (`Dockerfile`, Multi-Stage) enthält Client-Build + Server, Port 80.
|
||
|
||
## Deploy (git.prox.ws → Portainer)
|
||
|
||
Folgt [deploy-standard](https://git.prox.ws/PROX/deploy-standard) 1:1 – `gitops/stack.yml`
|
||
und `.gitea/workflows/build.yml` sind bereits fertig im Repo. Zum Scharfschalten:
|
||
|
||
### 1. Azure AD – zweite Redirect-URI eintragen
|
||
Bestehende App-Registrierung "BergVolt Zeiterfassung" im Azure Portal öffnen →
|
||
Authentifizierung → bei "Single-Page-Anwendung" ergänzen:
|
||
`https://materialschein.bergvolt.at` (oder die tatsächlich vorgesehene Domain/Pfad).
|
||
**Redirect-URI in `client/src/lib/authConfig.ts` steht auf `window.location.origin` –
|
||
d.h. die exakte Domain, unter der die App später erreichbar ist, muss hier eingetragen werden.**
|
||
|
||
### 2. Gitea – Repo-Secrets setzen
|
||
Repo → Settings → Actions → Secrets:
|
||
|
||
| Secret | Wert |
|
||
|---|---|
|
||
| `GITEAUSER` | Gitea-Username des Package-Namespace-Owners (siehe deploy-standard: Package-Push geht nur als Namespace-Owner) |
|
||
| `GITEAPASSWORD` | PAT dieses Users, Scope **nur `write:package`** (Settings → Applications → Generate New Token) |
|
||
| `PORTAINER_WEBHOOK_URL` | kommt aus Schritt 4 |
|
||
|
||
Repo → Settings → Actions: Default-Permission auf **Read and Write**, damit der
|
||
Commit-back (`gitops/stack.yml` auf `:<version>` pinnen) funktioniert.
|
||
|
||
### 3. Erster Push
|
||
```bash
|
||
git push origin main
|
||
```
|
||
`build` wird grün, `deploy` schlägt fehl (Webhook-Secret fehlt noch) – erwartet.
|
||
|
||
### 4. Portainer – Stack anlegen
|
||
Auf `https://10.1.10.111:9443` (Zugang laut deploy-standard bei "dominic"):
|
||
|
||
- Stacks → Add stack → **Repository**
|
||
- Repository URL: `https://git.prox.ws/BERGVOLT/Materialschein`
|
||
- Reference: `refs/heads/main`
|
||
- Compose path: `gitops/stack.yml`
|
||
- Authentication: `portainer-deploy` + dessen PAT (Org-Repos sind über das
|
||
`deploy-read`-Team bereits abgedeckt, nichts zusätzlich einzutragen)
|
||
- Environment-Variablen setzen:
|
||
- `POSTGRES_PASSWORD` = ein zufälliges, sicheres Passwort
|
||
- `JWT_SECRET` = ein zufälliger, langer String (signiert die lokalen Login-Tokens –
|
||
niemals ins Repo, nur hier als Stack-Env). Beispiel zum Generieren: `openssl rand -base64 48`
|
||
- `SEED_ADMIN_USERNAME` / `SEED_ADMIN_PASSWORD` = legt beim ersten Start automatisch
|
||
ein lokales Fallback-Konto an (siehe "Lokales Konto" unten)
|
||
- **Webhook aktivieren**, **ForceUpdate an**, `ForcePullImage` bleibt aus
|
||
- Die angezeigte Webhook-URL (`.../api/stacks/webhooks/<uuid>`) als Gitea-Secret
|
||
`PORTAINER_WEBHOOK_URL` eintragen (Schritt 2).
|
||
- nginx-proxy-manager: Proxy Host auf Container `materialschein`, Port `80`.
|
||
|
||
### 5. Verifizieren
|
||
- Webhook von Hand feuern (`curl -sk -X POST <url>`) → `204`, Container laufen
|
||
(`db` + `web`).
|
||
- Push oder „Run workflow" auslösen → beide Jobs grün, `gitops/stack.yml` zeigt danach
|
||
`image: …:1.0.<N>` (kein `:latest` mehr), Commit `deploy: v1.0.<N> [skip ci]` im Log.
|
||
- Cleanup-Rule für den Package-Owner einmalig setzen (Org/User → Settings → Packages →
|
||
Cleanup Rules): keep most recent 10 + keep matching `^latest$`.
|
||
|
||
## Lokales Konto (Fallback ohne Microsoft-Login)
|
||
|
||
Auf dem Login-Screen gibt es unter dem Microsoft-Button den Link "Alternativ mit
|
||
Benutzername anmelden". Damit das funktioniert, muss im Portainer-Stack `JWT_SECRET`
|
||
gesetzt sein (Schritt 4).
|
||
|
||
Ein erstes Konto legt der Server **automatisch beim Start** an, wenn
|
||
`SEED_ADMIN_USERNAME` und `SEED_ADMIN_PASSWORD` als Stack-Env gesetzt sind (idempotent –
|
||
läuft bei jedem Neustart mit, legt aber nur an, was noch nicht existiert). Das
|
||
Klartext-Passwort steht dabei nur in der Portainer-Stack-Konfiguration, nie im Git-Repo –
|
||
der Server speichert ausschließlich einen bcrypt-Hash in der Datenbank.
|
||
|
||
Optional danach `SEED_ADMIN_PASSWORD` aus dem Stack wieder entfernen (Härtung), sobald
|
||
das Konto einmal erfolgreich angelegt wurde – `SEED_ADMIN_USERNAME` kann stehen bleiben,
|
||
ohne Passwort passiert dann nichts mehr. Weitere lokale Konten aktuell nur direkt in der
|
||
Datenbank anlegbar (`INSERT INTO users …` mit einem bcrypt-Hash) – ein Verwaltungs-UI
|
||
dafür gibt es noch nicht.
|
||
|
||
### Danach: zum Homescreen hinzufügen
|
||
Seite im Handy-Browser öffnen, anmelden, "Zum Home-Bildschirm hinzufügen" (iOS Safari)
|
||
bzw. "App installieren" (Android Chrome) – die App ist als PWA installierbar
|
||
(`vite-plugin-pwa` generiert Manifest + Service Worker automatisch beim Build).
|