Files
dogfather-universe/cloudflare-worker/src/routes/auth.js
T
DogFatherGitandClaude Opus 5 597988a9a2 Interner Zugang auffindbar + Owner kann eigenen Code selbst aendern
- Footer hat jetzt einen eigenen Block "Intern" mit Links zum
  Bewerbungs-Postfach und zu Zugaenge & Rollen (vorher nur ein
  winziger Link ganz unten)
- Postfach-Login verlinkt auf die Zugaenge-Seite
- Neue Tabelle app_settings + Endpunkt /admin/owner/set-code:
  der Hauptadministrator kann seinen Code selbst aendern, mit
  Bestaetigung des aktuellen Codes; gespeichert wird nur der Hash
- Neuer Tab "Mein Zugang" in zugaenge.html (nur fuer den Owner)

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-31 02:19:36 +02:00

109 lines
4.2 KiB
JavaScript

/* =====================================================================
routes/auth.js — Login / Logout
===================================================================== */
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(request, env, session) {
if (!session || !session.isOwner) {
return json({ ok: false, error: "Das darf nur der Hauptadministrator." }, 403);
}
const body = await request.json().catch(() => ({}));
const currentCode = String(body.currentCode || "");
const newCode = String(body.newCode || "");
const check = await verifyCode(env, currentCode);
if (!check.ok || !check.isOwner) {
await logAction(env, "owner", "owner.code_change_failed", null, null);
return json({ ok: false, error: "Der aktuelle Code stimmt nicht." }, 403);
}
if (newCode.length < 10) {
return json({ ok: false, error: "Der neue Code muss mindestens 10 Zeichen haben." }, 400);
}
if (newCode === currentCode) {
return json({ ok: false, error: "Der neue Code muss sich vom alten unterscheiden." }, 400);
}
const hash = await sha256Hex(newCode);
await env.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`
)
.bind(hash, nowIso())
.run();
await logAction(env, "owner", "owner.code_changed", null, null);
return json({ ok: true });
}
export async function login(request, env) {
const body = await request.json().catch(() => ({}));
const code = String(body.code || "");
const key = `login:${clientKey(request)}`;
if (await isLockedOut(env, key)) {
return json({ ok: false, error: "Zu viele Fehlversuche. Bitte in ein paar Minuten erneut versuchen." }, 429);
}
const result = await verifyCode(env, code);
if (!result.ok) {
const { count } = await recordFailedAttempt(env, key);
await logAction(env, "anonym", "login.failed", null, { attempt: count });
if (result.reason === "locked") return json({ ok: false, error: "Dieser Zugang ist gesperrt." }, 403);
if (result.reason === "expired") return json({ ok: false, error: "Dieser Zugang ist abgelaufen." }, 403);
return json({ ok: false, error: "Falscher Code." }, 401);
}
await clearAttempts(env, key);
if (result.isOwner) {
const token = await createSession(env, { isOwner: true });
await logAction(env, "owner", "login.success", null, null);
return json({ ok: true, token, isOwner: true, name: "Dogi", roleName: "Hauptadministrator" });
}
const token = await createSession(env, { isOwner: false, userId: result.user.id });
await env.DB.prepare(`UPDATE users SET last_login_at = ? WHERE id = ?`).bind(nowIso(), result.user.id).run();
const roleRow = result.user.role_id
? await env.DB.prepare(`SELECT name, permissions FROM roles WHERE id = ?`).bind(result.user.role_id).first()
: null;
const overridesRows = await env.DB.prepare(
`SELECT permission, allowed FROM user_permission_overrides WHERE user_id = ?`
)
.bind(result.user.id)
.all();
const overrides = {};
for (const r of overridesRows.results || []) overrides[r.permission] = !!r.allowed;
const permissions = roleRow ? JSON.parse(roleRow.permissions || "[]") : [];
await logAction(env, result.user.username, "login.success", null, null);
return json({
ok: true,
token,
isOwner: false,
name: result.user.name,
username: result.user.username,
roleName: roleRow?.name || "Ohne Rolle",
permissions,
overrides,
});
}
export async function logout(request, env) {
const auth = request.headers.get("Authorization") || "";
const token = auth.startsWith("Bearer ") ? auth.slice(7) : null;
await endSession(env, token);
return json({ ok: true });
}