# Modul: Admin-Auth & Security
> Vollständige Admin-Authentifizierung: HMAC-SHA256 Session-Tokens (2h), Brute-Force-Schutz (Rate-Limit + exponentieller Backoff), Token-Blacklist (Logout-Revocation), One-Time-Use Email-Action-Links, Session-Timeout-Provider (15-Min-Warnung), Audit-Logging aller Login-Versuche.
---
## Enthaltene Dateien
| Ziel im neuen Projekt | Inhalt |
|---|---|
| `lib/admin-auth.ts` | Token-Erzeugung/Verifikation (HMAC-SHA256), `requireAdmin()` Middleware |
| `lib/rate-limit.ts` | In-Memory Rate-Limiting mit Backoff (5 Versuche/15 Min, Lock nach 10) |
| `lib/token-blacklist.ts` | Session-Token Revocation + Action-Token One-Time-Use (Supabase) |
| `lib/audit-log.ts` | Login-Audit-Logging + Brute-Force-Erkennung |
| `app/api/admin/login/route.ts` | POST: Login, DELETE: Logout |
| `app/api/admin/anfragen-action/route.ts` | GET: Email-Action-Link verarbeiten (HMAC-Token) |
| `app/admin/login/page.tsx` | Login-Seite (Open-Redirect-Schutz) |
| `app/admin/audit-logs/page.tsx` | Admin-Dashboard: Login-Überwachung |
| `components/admin/SessionTimeoutProvider.tsx` | Client-seitiger Inaktivitäts-Tracker (Warnung + Auto-Logout) |
| `migrations/MIGRATIONS_TOKEN_BLACKLIST.sql` | Tabellen: `admin_session_blacklist`, `action_token_blacklist` |
| `migrations/MIGRATIONS_AUDIT_LOGS.sql` | Tabelle: `admin_audit_logs` |
---
## Voraussetzungen
```bash
npm install bcryptjs
npm install -D @types/bcryptjs
```
Benötigt außerdem:
- `lib/supabase.ts` mit Service Client
- Supabase-Tabelle `benutzer` (Admin-User-Tabelle, siehe unten)
---
## Umgebungsvariablen (`.env.local`)
```env
NEXTAUTH_SECRET=min-32-zeichen-zufaelliger-string # HMAC-Schlüssel für Tokens
```
---
## Datenbank-Migrationen (Supabase)
### 1. Migrations aus `migrations/` ausführen
```
MIGRATIONS_AUDIT_LOGS.sql → Tabelle admin_audit_logs
MIGRATIONS_TOKEN_BLACKLIST.sql → Tabellen admin_session_blacklist, action_token_blacklist
```
### 2. Admin-User-Tabelle (muss im Projekt existieren)
```sql
CREATE TABLE benutzer (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
email text UNIQUE NOT NULL,
password_hash text NOT NULL, -- bcrypt-Hash
name text,
active boolean DEFAULT true,
created_at timestamptz DEFAULT now()
);
```
### 3. Supabase-Typen ergänzen (`lib/supabase.ts`)
In `Database.public.Tables` ergänzen:
```ts
benutzer: { Row: { id: string; email: string; password_hash: string; active: boolean } }
admin_audit_logs: { Row: { id: string; email: string; ip_addr: string; user_agent: string; success: boolean; reason: string | null; timestamp: string } }
admin_session_blacklist: { Row: { id: string; admin_id: string; token_signature: string; revoked_at: string; reason: string } }
action_token_blacklist: { Row: { id: string; anfrage_id: string; token_signature: string; action_type: string; used_at: string } }
```
---
## Einbindung Schritt für Schritt
### 1. Dateien kopieren
Alle `files/` in entsprechende Projektpfade.
### 2. Admin-Layout absichern (`app/admin/layout.tsx`)
```tsx
import { SessionTimeoutProvider } from "@/components/admin/SessionTimeoutProvider";
export default function AdminLayout({ children }) {
return (
{children}
);
}
```
### 3. Admin-API-Routes schützen
```ts
import { requireAdmin } from "@/lib/admin-auth";
export async function GET(req: Request) {
const admin = await requireAdmin(req);
if (!admin.ok) return admin.response;
// ... Route-Logik
}
```
### 4. Login-Route in Nav verlinken
```tsx
// Redirect nach Login zu /admin (oder ?from=/admin/anfragen)
Admin Login
```
### 5. Ersten Admin-User anlegen
```sql
-- bcrypt-Hash generieren (cost factor 12) und direkt eintragen
INSERT INTO benutzer (email, password_hash)
VALUES ('admin@example.com', '$2b$12$...');
```
Oder via Script:
```ts
import bcrypt from "bcryptjs";
const hash = await bcrypt.hash("password", 12);
```
### 6. Action-Links (für Email-Buttons)
Wenn Action-Links aus Emails verarbeitet werden sollen (z.B. Anfrage bestätigen per Klick):
- `app/api/admin/anfragen-action/route.ts` verarbeitet `GET ?token=...`
- Token erzeugen mit `createActionToken(id, "bestaetigt")` aus `lib/admin-auth.ts`
- Route anpassen: Was passiert nach erfolgreicher Aktion (DB-Update + Redirect)
---
## Anpassungspunkte
| Was | Wo |
|---|---|
| Session-Dauer (aktuell 2h) | `lib/admin-auth.ts` → `SESSION_DURATION` |
| Action-Token Gültigkeit (7 Tage) | `lib/admin-auth.ts` → `ACTION_TOKEN_DURATION` |
| Rate-Limit Schwellenwert (5 Versuche) | `lib/rate-limit.ts` → `MAX_ATTEMPTS` |
| Account-Lock Dauer (15 Min) | `lib/rate-limit.ts` → `LOCK_DURATION_MS` |
| Inaktivitäts-Timeout (2h) | `components/admin/SessionTimeoutProvider.tsx` → `TIMEOUT_MS` |
| Warn-Zeitpunkt (15 Min vor Ablauf) | `components/admin/SessionTimeoutProvider.tsx` → `WARN_BEFORE_MS` |
| Audit-Log Aufbewahrung (90 Tage) | `lib/audit-log.ts` → `deleteOldAuditLogs()` |
---
## Integrations-Prompt
Kopiere diesen Prompt in eine neue KI-Konversation, nachdem du die `files/` in dein Projekt kopiert hast. Ersetze alle `[PLATZHALTER]`.
```
Ich integriere das Admin-Auth-Modul (HMAC-Session, Rate-Limit, Token-Blacklist, Audit-Log) in mein Next.js/Supabase-Projekt.
PROJEKT-KONTEXT:
- Erster Admin-User: Email [ADMIN_EMAIL], Passwort [ADMIN_PASSWORT]
- Admin-Bereich URL-Prefix: /admin
- Supabase: bereits eingerichtet, lib/supabase.ts vorhanden
BEREITS KOPIERTE DATEIEN (aus modules/02-admin-auth/files/):
- lib/admin-auth.ts, lib/rate-limit.ts, lib/token-blacklist.ts, lib/audit-log.ts
- app/api/admin/login/route.ts
- app/api/admin/anfragen-action/route.ts
- app/admin/login/page.tsx
- app/admin/audit-logs/page.tsx
- components/admin/SessionTimeoutProvider.tsx
AUFGABEN – führe sie der Reihe nach aus:
1. SUPABASE-MIGRATIONEN: Führe diese SQLs nacheinander im Supabase SQL-Editor aus:
-- Aus migrations/MIGRATIONS_AUDIT_LOGS.sql:
CREATE TABLE IF NOT EXISTS admin_audit_logs (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
email text NOT NULL, ip_addr text NOT NULL, user_agent text NOT NULL,
success boolean NOT NULL, reason text, timestamp timestamptz DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_audit_logs_email ON admin_audit_logs(email);
CREATE INDEX IF NOT EXISTS idx_audit_logs_timestamp ON admin_audit_logs(timestamp DESC);
ALTER TABLE admin_audit_logs ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Admin Logs lesen" ON admin_audit_logs FOR SELECT USING (true);
-- Aus migrations/MIGRATIONS_TOKEN_BLACKLIST.sql:
CREATE TABLE admin_session_blacklist (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
admin_id uuid NOT NULL, token_signature text NOT NULL UNIQUE,
revoked_at timestamptz DEFAULT now(), reason text NOT NULL, notes text
);
CREATE INDEX idx_admin_session_blacklist_sig ON admin_session_blacklist(token_signature);
CREATE TABLE action_token_blacklist (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
anfrage_id uuid NOT NULL, token_signature text NOT NULL UNIQUE,
action_type text NOT NULL, used_at timestamptz DEFAULT now(),
used_by_ip text, notes text
);
CREATE INDEX idx_action_token_blacklist_sig ON action_token_blacklist(token_signature);
ALTER TABLE admin_session_blacklist ENABLE ROW LEVEL SECURITY;
ALTER TABLE action_token_blacklist ENABLE ROW LEVEL SECURITY;
2. ADMIN-USER-TABELLE: Prüfe ob eine Tabelle für Admin-Benutzer existiert. Falls nicht, erstelle:
CREATE TABLE benutzer (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
email text UNIQUE NOT NULL, password_hash text NOT NULL,
name text, active boolean DEFAULT true, created_at timestamptz DEFAULT now()
);
3. ERSTEN ADMIN-USER ANLEGEN: Generiere einen bcrypt-Hash für [ADMIN_PASSWORT] (cost 12)
und füge diesen direkt per SQL ein:
INSERT INTO benutzer (email, password_hash, name) VALUES ('[ADMIN_EMAIL]', '[HASH]', 'Admin');
4. SUPABASE-TYPEN: Lies lib/supabase.ts und ergänze in Database.public.Tables:
benutzer: { Row: { id: string; email: string; password_hash: string; active: boolean; name: string | null } }
admin_audit_logs: { Row: { id: string; email: string; ip_addr: string; user_agent: string; success: boolean; reason: string | null; timestamp: string } }
admin_session_blacklist: { Row: { id: string; admin_id: string; token_signature: string; revoked_at: string; reason: string } }
action_token_blacklist: { Row: { id: string; anfrage_id: string; token_signature: string; action_type: string; used_at: string } }
5. ENV-VARIABLEN: Ergänze .env.local um:
NEXTAUTH_SECRET=[MIN_32_ZEICHEN_ZUFAELLIGER_STRING]
6. ADMIN-LAYOUT absichern: Lies app/admin/layout.tsx (oder erstelle es).
Importiere und wrappe mit :
import { SessionTimeoutProvider } from "@/components/admin/SessionTimeoutProvider";
export default function AdminLayout({ children }) {
return {children};
}
7. API-ROUTES schützen: Zeige mir alle Dateien unter app/api/admin/ (außer login/).
Füge in jede Route am Anfang der Handler-Funktion ein:
import { requireAdmin } from "@/lib/admin-auth";
const admin = await requireAdmin(req);
if (!admin.ok) return admin.response;
8. TEST: Starte Dev-Server, öffne /admin/login, logge dich mit [ADMIN_EMAIL] ein.
Prüfe: Weiterleitung zu /admin nach Login, /admin/audit-logs zeigt Login-Eintrag.
Lies jede Datei vor dem Bearbeiten. Melde wenn alle Schritte abgeschlossen sind.
```