Minimale git-remote met publiek/prive-toggle
Deze repository wordt als momentopname gepubliceerd: een clone bevat de volledige inhoud, maar geen commitgeschiedenis.
import { Router } from "express";
import {
checkOwnerCredentials,
clearSessionCookie,
createSessionCookie,
requireOwnerWeb,
throttleKey,
tooManyAttempts,
} from "../auth.ts";
import { config } from "../config.ts";
import { createRepo, deleteRepo, listRepos, updateRepo } from "../repos.ts";
import { recordFailure, recordSuccess } from "../throttle.ts";
import { adminPage, layout, loginPage } from "../views.ts";
export const adminRouter: Router = Router();
/** Alleen paden binnen deze site zijn een geldige redirect na inloggen. */
function safeNext(value: unknown): string {
if (typeof value !== "string") return "/admin";
if (!value.startsWith("/") || value.startsWith("//")) return "/admin";
return value;
}
function backToAdmin(message: string | null, error: string | null): string {
const params = new URLSearchParams();
if (message) params.set("ok", message);
if (error) params.set("error", error);
const query = params.toString();
return query ? `/admin?${query}` : "/admin";
}
/**
* De sessiecookie is SameSite=Lax, dus een andere site kan geen POST met cookie
* versturen. Deze controle vangt de rest af, inclusief formulieren op subdomeinen.
*/
adminRouter.use((req, res, next) => {
if (req.method !== "POST") return next();
const origin = req.get("origin");
if (origin && origin !== config.publicUrl && !origin.startsWith("http://localhost")) {
res.status(403).type("text/plain").send("Ongeldige herkomst.\n");
return;
}
next();
});
adminRouter.get("/login", (req, res) => {
if (req.isOwner) {
res.redirect(safeNext(req.query["next"]));
return;
}
res.type("html").send(
layout({
title: "Inloggen",
isOwner: false,
body: loginPage(safeNext(req.query["next"]), null),
}),
);
});
adminRouter.post("/login", (req, res) => {
if (tooManyAttempts(res, throttleKey(req))) return;
const user = typeof req.body?.user === "string" ? req.body.user : "";
const password = typeof req.body?.password === "string" ? req.body.password : "";
const next = safeNext(req.body?.next);
if (!checkOwnerCredentials(user, password)) {
recordFailure(throttleKey(req));
res
.status(401)
.type("html")
.send(
layout({
title: "Inloggen",
isOwner: false,
body: loginPage(next, "Onjuiste gebruikersnaam of wachtwoord."),
}),
);
return;
}
recordSuccess(throttleKey(req));
res.setHeader("Set-Cookie", createSessionCookie());
res.redirect(next);
});
adminRouter.post("/logout", (_req, res) => {
res.setHeader("Set-Cookie", clearSessionCookie());
res.redirect("/");
});
adminRouter.get("/admin", requireOwnerWeb, async (req, res) => {
const repos = await listRepos();
const message = typeof req.query["ok"] === "string" ? req.query["ok"] : null;
const error = typeof req.query["error"] === "string" ? req.query["error"] : null;
res.type("html").send(
layout({
title: "Beheer",
isOwner: true,
body: adminPage(repos, message, error),
}),
);
});
adminRouter.post("/admin/repos", requireOwnerWeb, async (req, res) => {
const name = typeof req.body?.name === "string" ? req.body.name.trim() : "";
const description = typeof req.body?.description === "string" ? req.body.description.trim() : "";
try {
await createRepo(name, description);
res.redirect(backToAdmin(`Repository "${name}" aangemaakt (privé).`, null));
} catch (error) {
res.redirect(backToAdmin(null, (error as Error).message));
}
});
adminRouter.post("/admin/repos/:name/visibility", requireOwnerWeb, async (req, res) => {
const name = String(req.params["name"] ?? "");
const makePublic = req.body?.public === "1";
try {
await updateRepo(name, { public: makePublic });
res.redirect(
backToAdmin(`"${name}" staat nu op ${makePublic ? "publiek" : "privé"}.`, null),
);
} catch (error) {
res.redirect(backToAdmin(null, (error as Error).message));
}
});
adminRouter.post("/admin/repos/:name/description", requireOwnerWeb, async (req, res) => {
const name = String(req.params["name"] ?? "");
const description = typeof req.body?.description === "string" ? req.body.description.trim() : "";
try {
await updateRepo(name, { description });
res.redirect(backToAdmin(`Beschrijving van "${name}" bijgewerkt.`, null));
} catch (error) {
res.redirect(backToAdmin(null, (error as Error).message));
}
});
adminRouter.post("/admin/repos/:name/delete", requireOwnerWeb, async (req, res) => {
const name = String(req.params["name"] ?? "");
const confirm = typeof req.body?.confirm === "string" ? req.body.confirm.trim() : "";
if (confirm !== name) {
res.redirect(backToAdmin(null, "Typ de naam exact over om te verwijderen."));
return;
}
try {
await deleteRepo(name);
res.redirect(backToAdmin(`"${name}" verwijderd.`, null));
} catch (error) {
res.redirect(backToAdmin(null, (error as Error).message));
}
});