Vollständiger 1:1-Port des cloudflare-worker/ (dogfather-universe-postfach) auf server-internal/: Bewerbungs-Postfach mit Rollen/Rechten, Team- Zugangsverwaltung (verschlüsselte Owner-only-Codes), DogiCrew-Supporter-Abo (PayPal Subscriptions, Google-Login, Resend-E-Mail, 30-Monats-Prämienzyklus) und automatische TikTok-Live-Erkennung. D1 -> better-sqlite3, Cloudflare- Cron -> node-cron. Läuft ohne gesetzte Secrets weiter (PayPal/Google/Resend/ Ticketanizer/Discord bleiben "nicht konfiguriert" statt kaputt), bis die echten Zugangsdaten eingetragen werden.
105 lines
4.1 KiB
JavaScript
105 lines
4.1 KiB
JavaScript
/* =====================================================================
|
|
routes/auth.js — Login / Logout (interner Team-Bereich).
|
|
1:1 portiert aus cloudflare-worker/src/routes/auth.js.
|
|
===================================================================== */
|
|
import { db } from "../db.js";
|
|
import { verifyCode, createSession, endSession, isLockedOut, recordFailedAttempt, clearAttempts, clientKey } from "../lib/auth.js";
|
|
import { logAction } from "../lib/audit.js";
|
|
import { nowIso, sha256Hex } from "../lib/crypto.js";
|
|
import { json } from "../lib/http.js";
|
|
|
|
/**
|
|
* Der Hauptadministrator ändert seinen eigenen Zugangscode.
|
|
* Nur mit gültiger Owner-Session UND erneuter Eingabe des aktuellen Codes.
|
|
* Gespeichert wird ausschließlich der SHA-256-Hash — nie der Klartext.
|
|
*/
|
|
export async function setOwnerCode(req, res, session) {
|
|
if (!session || !session.isOwner) {
|
|
return json(res, { ok: false, error: "Das darf nur der Hauptadministrator." }, 403);
|
|
}
|
|
|
|
const body = req.body || {};
|
|
const currentCode = String(body.currentCode || "");
|
|
const newCode = String(body.newCode || "");
|
|
|
|
const check = await verifyCode(currentCode);
|
|
if (!check.ok || !check.isOwner) {
|
|
logAction("owner", "owner.code_change_failed", null, null);
|
|
return json(res, { ok: false, error: "Der aktuelle Code stimmt nicht." }, 403);
|
|
}
|
|
|
|
if (newCode.length < 10) {
|
|
return json(res, { ok: false, error: "Der neue Code muss mindestens 10 Zeichen haben." }, 400);
|
|
}
|
|
if (newCode === currentCode) {
|
|
return json(res, { ok: false, error: "Der neue Code muss sich vom alten unterscheiden." }, 400);
|
|
}
|
|
|
|
const hash = await sha256Hex(newCode);
|
|
db.prepare(
|
|
`INSERT INTO app_settings (key, value, updated_at) VALUES ('owner_code_hash', ?, ?)
|
|
ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`
|
|
).run(hash, nowIso());
|
|
|
|
logAction("owner", "owner.code_changed", null, null);
|
|
return json(res, { ok: true });
|
|
}
|
|
|
|
export async function login(req, res) {
|
|
const body = req.body || {};
|
|
const code = String(body.code || "");
|
|
const key = `login:${clientKey(req)}`;
|
|
|
|
if (isLockedOut(key)) {
|
|
return json(res, { ok: false, error: "Zu viele Fehlversuche. Bitte in ein paar Minuten erneut versuchen." }, 429);
|
|
}
|
|
|
|
const result = await verifyCode(code);
|
|
if (!result.ok) {
|
|
const { count } = recordFailedAttempt(key);
|
|
logAction("anonym", "login.failed", null, { attempt: count });
|
|
if (result.reason === "locked") return json(res, { ok: false, error: "Dieser Zugang ist gesperrt." }, 403);
|
|
if (result.reason === "expired") return json(res, { ok: false, error: "Dieser Zugang ist abgelaufen." }, 403);
|
|
return json(res, { ok: false, error: "Falscher Code." }, 401);
|
|
}
|
|
|
|
clearAttempts(key);
|
|
|
|
if (result.isOwner) {
|
|
const token = await createSession({ isOwner: true });
|
|
logAction("owner", "login.success", null, null);
|
|
return json(res, { ok: true, token, isOwner: true, name: "Dogi", roleName: "Hauptadministrator" });
|
|
}
|
|
|
|
const token = await createSession({ isOwner: false, userId: result.user.id });
|
|
db.prepare(`UPDATE users SET last_login_at = ? WHERE id = ?`).run(nowIso(), result.user.id);
|
|
|
|
const roleRow = result.user.role_id
|
|
? db.prepare(`SELECT name, permissions FROM roles WHERE id = ?`).get(result.user.role_id)
|
|
: null;
|
|
const overridesRows = db.prepare(`SELECT permission, allowed FROM user_permission_overrides WHERE user_id = ?`).all(result.user.id);
|
|
const overrides = {};
|
|
for (const r of overridesRows) overrides[r.permission] = !!r.allowed;
|
|
const permissions = roleRow ? JSON.parse(roleRow.permissions || "[]") : [];
|
|
|
|
logAction(result.user.username, "login.success", null, null);
|
|
|
|
return json(res, {
|
|
ok: true,
|
|
token,
|
|
isOwner: false,
|
|
name: result.user.name,
|
|
username: result.user.username,
|
|
roleName: roleRow?.name || "Ohne Rolle",
|
|
permissions,
|
|
overrides,
|
|
});
|
|
}
|
|
|
|
export async function logout(req, res) {
|
|
const auth = req.headers["authorization"] || "";
|
|
const token = auth.startsWith("Bearer ") ? auth.slice(7) : null;
|
|
await endSession(token);
|
|
return json(res, { ok: true });
|
|
}
|