chahinebrini d3dfa74cf8 feat(admin): Admin App initial commit + Deploy-Infrastructure
apps/admin/:
- Nuxt 4.1.3 + @nuxt/ui 4 + @nuxtjs/supabase, port 3017 staging
- 7 pages: index (59 LOC dashboard), login (72 LOC), auth/confirm, plus stubs
  für domains/users/stats/moderation (14-17 LOC each, content für separate
  Phase 2 Session)
- composables/useAdminAuth.ts: Supabase login + verifyAdminRole hook
- middleware/admin-auth.ts: route guard (Phase 3 backend-check ready)
- layouts/default.vue, app.vue, README.md
- nuxt.config.ts: SSR=true, port 3017, dark-mode preference, Supabase
  pkce-flow, runtimeConfig.adminSecret für Phase 3 backend-binding

Deploy-Infrastructure:
- .github/workflows/deploy-admin-staging.yml: build admin auf push to main mit
  path-filter apps/admin/**, scp tar zu Server, atomic-mv + pm2 restart
- scripts/deploy-admin-from-artifact.sh: Server-side deploy (extract, atomic mv,
  pm2 reload). Kein prisma-migrate (admin hat kein eigenes DB-Schema).
- apps/admin/start-admin-staging.sh: pm2 start-script mit Infisical-wrapper,
  port 3017, mappt Infisical SUPABASE_URL/KEY auf NUXT_PUBLIC_*
- ecosystem.config.js: rebreak-admin-staging Eintrag (port 3017,
  max_memory_restart 400M)
- ops/nginx/admin-staging.rebreak.org.conf: HTTP→HTTPS redirect, SSL paths,
  proxy auf 127.0.0.1:3017, noindex header

Pending User-Actions für go-live:
1. DNS-A-Record admin.staging.rebreak.org → 49.13.55.22
2. SSL-cert via certbot (oder bestehender wildcard *.staging.rebreak.org)
3. nginx-config auf Server aktivieren (sudo cp + ln + reload)
4. pm2 initial start: pm2 start ecosystem.config.js --only rebreak-admin-staging
5. Infisical-secret ADMIN_SECRET (server-only, Phase 3 binding)

GH-Actions: keine neuen Secrets (nutzt bestehende HETZNER_SSH_KEY/HOST/USER)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 22:17:20 +02:00

8.0 KiB

rebreak Admin

Internes Verwaltungspanel fuer rebreak.org.

Status: Phase 1 -- Skeleton (Auth-Wiring, Layout, Stub-Pages). Keine echten API-Calls.


Stack

Schicht Technologie
Framework Nuxt 4.1.3 (SSR)
UI @nuxt/ui 4.x (Tailwind 4, Nuxt UI Komponenten)
Auth @nuxtjs/supabase 2.x (PKCE-Flow)
Backend-Komm. $fetch gegen /api/admin/* (Phase 3)
Laufzeit Node 24.11.1 / pm2 auf Hetzner CX23

Wo die Admin-App lebt

Environment URL Port (intern) pm2-Service
Staging admin.staging.rebreak.org 3017 rebreak-admin-staging
Prod admin.rebreak.org 3018 rebreak-admin

Nginx-Routing-Config: wird in Phase 2 Deploy angelegt (analog zu staging.rebreak.org-Config).


Lokale Entwicklung

# Vom Monorepo-Root:
pnpm dev:admin

# Oder direkt:
cd apps/admin && pnpm dev

Laeuft auf http://localhost:3017.

Infisical-Secrets werden fuer lokales Dev nicht gebraucht -- die Supabase-URL/Key kommen als process.env.NUXT_PUBLIC_SUPABASE_URL/KEY oder fallen auf Staging-Defaults zurueck.


Auth-Architektur

Admin-Browser
    |
    | 1. POST /api/auth/login (Supabase Email/Password)
    v
Supabase Auth (db-staging.rebreak.org oder db.rebreak.org)
    |
    | 2. JWT zurueck (access_token)
    v
Admin-Browser haelt Session (PKCE-Flow, persistSession=true)
    |
    | 3. GET /api/admin/* (Authorization: Bearer <token>)
    v
Backend (staging.rebreak.org)
    |
    | 4. requireAdmin-Middleware:
    |    - JWT verifizieren (Supabase public key)
    |    - user_id in admin_users-Tabelle pruefen
    |    - Bei Misserfolg: 403
    v
Admin-Endpoint-Response

Aktueller Status (Phase 1): Supabase-Login funktioniert. Schritt 4 (requireAdmin) ist NICHT implementiert -- jeder eingeloggte Supabase-User koennte theoretisch rein, wenn er die URL kennt. Das ist akzeptabel weil die Admin-URL nicht public ist und Staging-Daten keine hochsensiblen Produktionsdaten enthalten.

Phase 3 schaltet requireAdmin ein -- dann ist der Zugang haerter gesperrt.


DSGVO-Considerations fuer Admin-Zugriff

Admins haben Zugriff auf User-Daten. Das bedingt:

  1. Audit-Log (TODO Phase 4 -- hans-mueller): Jede Admin-Aktion (User ansehen, Domain genehmigen, Content moderieren) muss in einer admin_audit_log-Tabelle geloggt werden:

    • Wer (admin_user_id)
    • Was (action: "view_user" | "approve_domain" | "reject_domain" | "ban_user")
    • Welcher Datensatz (target_id)
    • Wann (timestamp)
  2. Daten-Minimierung im Admin-UI: User-Liste zeigt NIEMALS echten Namen oder E-Mail. Nur Nickname (analog zur App-Anzeige). E-Mail ist nur fuer Kontaktaufnahme via Support-Ticket, nicht fuer Browse-UI.

  3. Admin-Zugangs-Liste: admin_users-Tabelle in Supabase darf nur von User (Chahine) befuellt werden. Kein Self-Signup, kein automatisches Promoten.

  4. Data-Processing-Agreement: Wenn externe Personen Admin-Zugang bekommen (z.B. Moderatoren), braucht es einen AVV. Aktuell: nur interner Zugang (Chahine).

  5. Datenschutzfolgeabschaetzung (DSFA): Admin-Zugang auf Nutzerdaten von Suchtkranken faellt unter Art. 35 DSGVO (besondere Kategorien). Hans-Mueller-Task.


Deploy-Plan

Variante A: SSR auf Hetzner (Empfehlung)

Analog zu rebreak-staging -- separater pm2-Service, separater Port, nginx-Subdomain.

Pros: Kein zusaetzlicher Hosting-Service, Server-Side-Auth-Checks funktionieren native, Infisical-Secrets per Infisical-run-wrapper wie gewohnt.

Cons: Belastet CX23 zusaetzlich (RAM). Admin-App hat aber wenig Traffic -- kein Risiko.

Setup:

# /srv/rebreak/ecosystem.config.js (ergaenzen):
{
  name: 'rebreak-admin-staging',
  script: '/srv/rebreak/apps/admin/.output-staging/server/index.mjs',
  env: { PORT: 3017, NODE_ENV: 'production' }
}
# nginx: admin.staging.rebreak.org
server {
  listen 443 ssl;
  server_name admin.staging.rebreak.org;
  location / {
    proxy_pass http://127.0.0.1:3017;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
  }
}

Variante B: Static via Cloudflare Pages

nuxt generate + Deploy zu Cloudflare Pages.

Pros: Kostenlos, globales CDN, keine Hetzner-Last.

Cons: Kein echter SSR (Auth-Checks nur client-side), API-Calls gehen trotzdem zu Hetzner. Fuer eine Admin-App die Server-Side-Checks braucht suboptimal.

Entscheidung: Variante A (SSR auf Hetzner).


GitHub-Actions-Pipeline -- Plan fuer Phase 2 Deploy

Die bestehende .github/workflows/deploy-staging.yml baut nur backend/. Admin-App braucht einen separaten Job ODER einen eigenen Workflow.

Option 1: Separater Workflow deploy-admin-staging.yml (empfohlen)

name: Deploy Admin Staging
on:
  push:
    branches: [main]
    paths:
      - "apps/admin/**"
      - ".github/workflows/deploy-admin-staging.yml"
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 24.11.1
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - name: Build admin
        working-directory: apps/admin
        run: pnpm build
      - run: tar czf admin-output.tar.gz -C apps/admin/.output .
      - uses: actions/upload-artifact@v4
        with:
          name: admin-output
          path: admin-output.tar.gz

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment: staging
    steps:
      # ... analog zu deploy-staging.yml:
      # scp admin-output.tar.gz -> /srv/rebreak/apps/admin/.output-incoming.tar.gz
      # ssh -> scripts/deploy-admin-from-artifact.sh

Warum separater Workflow: paths-Filter verhindert dass ein Backend-Push auch die Admin-App rebuildet (und vice versa). Weniger GH-Actions-Minutes-Verbrauch.

Option 2: Zusaetzlicher Job in deploy-staging.yml

Parallel-Job neben dem bestehenden build-Job. Einfacher aber kein paths-Filter moeglich ohne komplizierte Logik -- jeder Push rebuildet alles.

Entscheidung: Option 1 (eigener Workflow mit paths-Filter).

Der Workflow-File wird in Phase 2 angelegt -- NICHT jetzt (Pipeline-Scope-Creep verhindern).


Server-Script fuer Admin-Deploy

Analog zu scripts/deploy-from-artifact.sh -- ein scripts/deploy-admin-from-artifact.sh das:

  1. Admin-Artifact extrahiert nach /srv/rebreak/apps/admin/.output-staging-new/
  2. Atomisches mv nach .output-staging
  3. pm2 restart rebreak-admin-staging

KEIN Migration-Step (Admin-App hat keine eigene DB -- nutzt Backend-API).


TODOs nach Phase 1

Backend (rebreak-backend-Agent / Phase 3)

  • requireAdmin-Middleware: JWT verifizieren + admin_users-Tabelle-Check
  • Supabase-Migration: admin_users-Tabelle (id uuid references auth.users, created_at)
  • GET /api/admin/verify-admin (prueft ob eingeloggter User Admin ist)
  • GET /api/admin/users (paginierte User-Liste, NUR nickname + plan + created_at + last_seen)
  • GET /api/admin/domains (Blocker-Domain-Approval-Queue)
  • POST /api/admin/domains/:id/approve
  • POST /api/admin/domains/:id/reject
  • GET /api/admin/stats (aggregierte anonyme Metriken)

Hans-Mueller / DSGVO (Phase 4)

  • DSFA fuer Admin-Zugriff auf Nutzerdaten gemaess Art. 35 DSGVO
  • Audit-Log-Design: admin_audit_log-Tabelle + Retention-Policy
  • AVV-Template fuer externe Moderatoren (falls noetig)
  • TOM (Technische+Organisatorische Massnahmen) fuer Admin-Zugang dokumentieren
  • Loeschkonzept fuer Audit-Log-Eintraege (wie lange aufbewahren?)

Backyard (Phase 2 Deploy)

  • nginx-Config: admin.staging.rebreak.org -> Port 3017
  • nginx-Config: admin.rebreak.org -> Port 3018
  • Let's Encrypt-Cert fuer admin.staging.rebreak.org + admin.rebreak.org
  • ecosystem.config.js: rebreak-admin-staging + rebreak-admin Service
  • GH-Actions-Workflow: deploy-admin-staging.yml (paths-filtered)
  • Server-Script: scripts/deploy-admin-from-artifact.sh
  • GitHub-Environment: staging muss HETZNER_SSH_KEY/HOST/USER schon haben (von backend-deploy geerbt)