9.4 KiB
9.4 KiB
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
npm install bcryptjs
npm install -D @types/bcryptjs
Benötigt außerdem:
lib/supabase.tsmit Service Client- Supabase-Tabelle
benutzer(Admin-User-Tabelle, siehe unten)
Umgebungsvariablen (.env.local)
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)
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:
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)
import { SessionTimeoutProvider } from "@/components/admin/SessionTimeoutProvider";
export default function AdminLayout({ children }) {
return (
<SessionTimeoutProvider>
{children}
</SessionTimeoutProvider>
);
}
3. Admin-API-Routes schützen
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
// Redirect nach Login zu /admin (oder ?from=/admin/anfragen)
<Link href="/admin/login">Admin Login</Link>
5. Ersten Admin-User anlegen
-- bcrypt-Hash generieren (cost factor 12) und direkt eintragen
INSERT INTO benutzer (email, password_hash)
VALUES ('admin@example.com', '$2b$12$...');
Oder via Script:
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.tsverarbeitetGET ?token=...- Token erzeugen mit
createActionToken(id, "bestaetigt")auslib/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 <SessionTimeoutProvider>:
import { SessionTimeoutProvider } from "@/components/admin/SessionTimeoutProvider";
export default function AdminLayout({ children }) {
return <SessionTimeoutProvider>{children}</SessionTimeoutProvider>;
}
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.