Files
OPENBUREAU/cms/api/src/auth.js
T
karim bd85570259 dialog: Diskussionsplattform mit Foren, Rollen & Moderation + RLS-Fix
Auth/RLS-Fix (Schreiben gab 400):
- supabase.js: eigener supabaseAuth-Client für Login/Token-Check, damit
  signInWithPassword den Service-Daten-Client nicht prozessweit aufs
  User-Token umstellt (sonst lief insert als role=authenticated → RLS-Block).

Rollen (admin > editor > user):
- auth.js: roleOf() aus app_metadata.role + ADMIN_EMAILS, requireModerator.
- users.js: Rolle anzeigen/setzen über GoTrue app_metadata; .env-Admins fix.

Datenmodell (schema.sql):
- forums (Kategorien) + threads; Seed Allgemein/Projekte/Technik/Off-Topic
  und Sonder-Kategorie Beiträge. Library-Beiträge werden als Threads
  gespiegelt (dialog-store.syncLibrary).

API (routes/dialog.js, dialog-store.js):
- öffentlich: /api/forums, /api/forums/:slug, /api/recent, /api/thread
- eingeloggt: POST /api/threads (Thread starten, nur in Foren)
- Moderation: /api/mod/* (sperren/ausblenden), Admin: /api/admin/forums CRUD
- comments: Lock-Prüfung beim Schreiben, Moderation darf jede löschen.

Frontend:
- static/dialog.js: Router (Übersicht-Split-View | Forum | Thread),
  neuer Thread, Mod-Leiste, subtiles Login (dezente Zeile statt Formular).
- Admin-UI: Tabs Foren + Moderation, Rollen-Dropdown bei Autor:innen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 16:09:19 +02:00

51 lines
2.0 KiB
JavaScript

import { supabaseAuth } from './supabase.js';
// Rollen-Hierarchie: admin > editor (Redakteur) > user.
// - admin: alles (Foren verwalten, moderieren, Nutzer/Rollen, Inhalte)
// - editor: moderieren (Wortmeldungen ausblenden/löschen, Threads sperren)
// - user: im Forum mitschreiben
// Admins aus der .env (ADMIN_EMAILS=a@x,b@y) sind immer Admin (Bootstrap, damit
// man sich nicht aussperrt). Zusätzlich kann eine Rolle in app_metadata.role
// liegen (im Admin-UI vergeben).
const ADMINS = (process.env.ADMIN_EMAILS || '')
.split(',').map((s) => s.trim().toLowerCase()).filter(Boolean);
export function roleOf(user) {
const email = (user?.email || '').toLowerCase();
const meta = (user?.app_metadata?.role || '').toLowerCase();
if (ADMINS.includes(email) || meta === 'admin') return 'admin';
if (meta === 'editor') return 'editor';
return 'user';
}
// Verifiziert den Supabase-Access-Token und legt user/email/role im Kontext ab.
export async function requireAuth(c, next) {
const header = c.req.header('Authorization') || '';
const token = header.startsWith('Bearer ') ? header.slice(7) : null;
if (!token) return c.json({ error: 'Nicht eingeloggt' }, 401);
const { data, error } = await supabaseAuth.auth.getUser(token);
if (error || !data?.user) return c.json({ error: 'Ungültiges Token' }, 401);
const email = (data.user.email || '').toLowerCase();
const role = roleOf(data.user);
c.set('user', data.user);
c.set('email', email);
c.set('role', role);
c.set('isAdmin', role === 'admin');
c.set('canModerate', role === 'admin' || role === 'editor');
await next();
}
// Nur Admins (nach requireAuth einsetzen).
export async function requireAdmin(c, next) {
if (!c.get('isAdmin')) return c.json({ error: 'Nur für Admins' }, 403);
await next();
}
// Admins + Redakteure — fürs Moderieren (nach requireAuth einsetzen).
export async function requireModerator(c, next) {
if (!c.get('canModerate')) return c.json({ error: 'Nur für Moderation' }, 403);
await next();
}