MBO-Tech-IT-Webseite/modules/02-admin-auth/TEMPLATE.md

9.4 KiB
Raw Permalink Blame History

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.ts mit 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);

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.tsSESSION_DURATION
Action-Token Gültigkeit (7 Tage) lib/admin-auth.tsACTION_TOKEN_DURATION
Rate-Limit Schwellenwert (5 Versuche) lib/rate-limit.tsMAX_ATTEMPTS
Account-Lock Dauer (15 Min) lib/rate-limit.tsLOCK_DURATION_MS
Inaktivitäts-Timeout (2h) components/admin/SessionTimeoutProvider.tsxTIMEOUT_MS
Warn-Zeitpunkt (15 Min vor Ablauf) components/admin/SessionTimeoutProvider.tsxWARN_BEFORE_MS
Audit-Log Aufbewahrung (90 Tage) lib/audit-log.tsdeleteOldAuditLogs()

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.