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]>
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
-- =====================================================================
|
||||
-- Allgemeine Einstellungen des internen Bereichs.
|
||||
-- Aktuell genutzt für: owner_code_hash — der SHA-256-Hash des
|
||||
-- Hauptadministrator-Codes, damit Dogi seinen eigenen Zugangscode
|
||||
-- selbst ändern kann (vorher lag er fest im Worker-Secret).
|
||||
-- Der Klartext-Code wird NIRGENDS gespeichert.
|
||||
-- =====================================================================
|
||||
CREATE TABLE IF NOT EXISTS app_settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
@@ -59,10 +59,24 @@ export async function clearAttempts(env, key) {
|
||||
|
||||
/** Prüft einen eingegebenen Code gegen Owner-Secret und Team-Datenbank. */
|
||||
export async function verifyCode(env, code) {
|
||||
if (env.POSTFACH_CODE && timingSafeEqual(code, env.POSTFACH_CODE)) {
|
||||
const hash = await sha256Hex(code);
|
||||
|
||||
// 1) Owner-Code aus der Datenbank — den kann Dogi selbst ändern.
|
||||
// Sobald hier einer hinterlegt ist, gilt NUR noch dieser (kein Zweitschlüssel).
|
||||
const ownerRow = await env.DB.prepare(
|
||||
`SELECT value FROM app_settings WHERE key = 'owner_code_hash'`
|
||||
)
|
||||
.first()
|
||||
.catch(() => null);
|
||||
|
||||
if (ownerRow && ownerRow.value) {
|
||||
if (timingSafeEqual(hash, ownerRow.value)) return { ok: true, isOwner: true };
|
||||
} else if (env.POSTFACH_CODE && timingSafeEqual(code, env.POSTFACH_CODE)) {
|
||||
// 2) Erstzugang: solange noch kein eigener Code gesetzt wurde, gilt das Worker-Secret.
|
||||
return { ok: true, isOwner: true };
|
||||
}
|
||||
const hash = await sha256Hex(code);
|
||||
|
||||
// 3) Normale Teammitglieder
|
||||
const user = await env.DB.prepare(
|
||||
`SELECT id, name, username, role_id, status, expires_at FROM users WHERE code_hash = ?`
|
||||
)
|
||||
|
||||
@@ -3,9 +3,48 @@
|
||||
===================================================================== */
|
||||
import { verifyCode, createSession, endSession, isLockedOut, recordFailedAttempt, clearAttempts, clientKey } from "../lib/auth.js";
|
||||
import { logAction } from "../lib/audit.js";
|
||||
import { nowIso } from "../lib/crypto.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 || "");
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
===================================================================== */
|
||||
import { json, corsHeaders } from "./lib/http.js";
|
||||
import { requireSession } from "./lib/auth.js";
|
||||
import { login, logout } from "./routes/auth.js";
|
||||
import { login, logout, setOwnerCode } from "./routes/auth.js";
|
||||
import {
|
||||
listRoles, createRole, updateRole, deleteRole,
|
||||
} from "./routes/roles.js";
|
||||
@@ -59,6 +59,7 @@ async function handleLiveStatusSet(request, env, session) {
|
||||
// Routen, die eine gültige Session brauchen. `session` wird injiziert.
|
||||
const AUTHED_ROUTES = {
|
||||
"POST /auth/logout": (req, env) => logout(req, env),
|
||||
"POST /admin/owner/set-code": (req, env, session) => setOwnerCode(req, env, session),
|
||||
"POST /live-status/set": (req, env, session) => handleLiveStatusSet(req, env, session),
|
||||
|
||||
"POST /admin/applications/list": (req, env, session) => listApplications(req, env, session),
|
||||
|
||||
Reference in New Issue
Block a user