mirror of
https://github.com/guyg1474/hcrb.git
synced 2026-10-15 23:13:28 +02:00
Rollen (Admin/Mitglied), Seite Kooperationen, Textanpassungen
- Gemeinsame Zugänge mit Rollen: Admin darf alles, Mitglied nutzt Kalender, Fahrtplaner, Treffpunkte und bearbeitet Mitglieder (kein Hinzufügen, Löschen, Import, keine Patches, keine Statistik-Korrektur) - Server prüft die Rechte (403), Konten ohne Rolle gelten als Admin - npm run user: add <name> [admin|mitglied], role <name> <rolle> - Neue öffentliche Seite Kooperationen mit Titelbild, Anführer, Social-Media-Links und Beschreibung, Pflege durch Admin - Kopfzeile bleibt bei mittlerer Breite einzeilig - Texte auf Wunsch gekürzt Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f46016487d
commit
819219fb2d
18 files changed
+661
-106
No files matched your search
@@ -2,6 +2,10 @@
|
||||
//
|
||||
// Passwörter werden nie im Klartext gespeichert, sondern mit scrypt (eigenes Salt pro Passwort) gehasht.
|
||||
// Sitzungs-Tokens liegen nur im Cookie des Browsers; auf dem Server wird lediglich ihr SHA-256-Hash gespeichert.
|
||||
//
|
||||
// Rollen: Die Konten sind gemeinsame Zugänge, keine persönlichen.
|
||||
// admin – alles, inkl. Mitglieder hinzufügen/löschen, Gruppe importieren, Patches, Statistik-Korrektur
|
||||
// mitglied – Kalender, Fahrtplaner, Treffpunkte, Mitglieder bearbeiten (nicht hinzufügen/löschen)
|
||||
|
||||
import { scrypt, randomBytes, timingSafeEqual, createHash } from 'node:crypto';
|
||||
import { readFileSync, writeFileSync, renameSync, mkdirSync, existsSync, statSync } from 'node:fs';
|
||||
@@ -17,6 +21,10 @@ const SESSION_DAYS = 30;
|
||||
const MAX_FAILS = 10; // Fehlversuche pro IP …
|
||||
const FAIL_WINDOW = 15 * 60 * 1000; // … innerhalb von 15 Minuten
|
||||
export const MIN_PASSWORD = 8;
|
||||
export const ROLES = ['admin', 'mitglied'];
|
||||
|
||||
// Konten aus der Zeit vor den Rollen wurden vom Betreiber angelegt → Admin, damit sich niemand aussperrt
|
||||
export const roleOf = (user) => (ROLES.includes(user?.role) ? user.role : 'admin');
|
||||
|
||||
// ---------- Passwort-Hashing (scrypt) ----------
|
||||
const SCRYPT = { N: 2 ** 15, r: 8, p: 1, keylen: 64 };
|
||||
@@ -105,7 +113,7 @@ function setCookie(req, res, value, maxAgeSec) {
|
||||
res.setHeader('Set-Cookie', attrs.join('; '));
|
||||
}
|
||||
|
||||
// Liefert den angemeldeten Benutzernamen oder null
|
||||
// Liefert den angemeldeten Benutzer ({ name, role }) oder null
|
||||
function sessionUser(req) {
|
||||
const token = readCookie(req, COOKIE);
|
||||
if (!token) return null;
|
||||
@@ -114,7 +122,7 @@ function sessionUser(req) {
|
||||
// Benutzer gelöscht oder Passwort seit der Anmeldung geändert → Sitzung ungültig
|
||||
const user = currentUsers().find((u) => u.name === s.user);
|
||||
if (!user || s.created < (user.changedAt || 0)) return null;
|
||||
return user.name;
|
||||
return { name: user.name, role: roleOf(user) };
|
||||
}
|
||||
|
||||
// ---------- Schutz vor Passwort-Raten ----------
|
||||
@@ -156,7 +164,7 @@ export function authRoutes(app) {
|
||||
});
|
||||
saveSessions();
|
||||
setCookie(req, res, token, SESSION_DAYS * 86400);
|
||||
res.json({ user: user.name });
|
||||
res.json({ user: user.name, role: roleOf(user) });
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
@@ -170,15 +178,24 @@ export function authRoutes(app) {
|
||||
});
|
||||
|
||||
app.get('/api/me', (req, res) => {
|
||||
res.json({ user: sessionUser(req) });
|
||||
const user = sessionUser(req);
|
||||
res.json({ user: user?.name ?? null, role: user?.role ?? null });
|
||||
});
|
||||
}
|
||||
|
||||
// Alles, was nach dieser Middleware kommt, ist nur angemeldet erreichbar
|
||||
export function requireAuth(req, res, next) {
|
||||
req.user = sessionUser(req);
|
||||
if (req.user) return next();
|
||||
const user = sessionUser(req);
|
||||
req.user = user?.name ?? null;
|
||||
req.role = user?.role ?? null;
|
||||
if (user) return next();
|
||||
if (req.path.startsWith('/api/')) return res.status(401).json({ error: 'Bitte anmelden.' });
|
||||
if (req.method === 'GET') return res.redirect(`/login?next=${encodeURIComponent(req.originalUrl)}`);
|
||||
res.status(401).end();
|
||||
}
|
||||
|
||||
// Nur für Admins (nach requireAuth verwenden)
|
||||
export function requireAdmin(req, res, next) {
|
||||
if (req.role === 'admin') return next();
|
||||
res.status(403).json({ error: 'Das darf nur der Admin-Zugang.' });
|
||||
}
|
||||
Reference in new issue
Block a user