43 lines
2.6 KiB
TypeScript
43 lines
2.6 KiB
TypeScript
import type { FastifyInstance } from "fastify";
|
|
import { applyBody, checkBody, createUpdaterClient, requestId, UpdaterError } from "../modules/selfhost-updater";
|
|
import { requireSystemAdmin } from "../utils/requireSystemAdmin";
|
|
import { getBuildInfo } from "../utils/buildInfo";
|
|
|
|
export default async function selfhostUpdateRoutes(server: FastifyInstance, options: { client?: ReturnType<typeof createUpdaterClient> }) {
|
|
const client = options.client || createUpdaterClient();
|
|
server.addHook("preHandler", async (req, reply) => {
|
|
reply.header("Cache-Control", "no-store");
|
|
await requireSystemAdmin(server, req, reply);
|
|
});
|
|
const forward = async (reply: any, method: "GET" | "POST", path: string, body?: unknown) => {
|
|
try {
|
|
const result = await client(method, path, body);
|
|
return reply.code(result.status).send(path === "/v1/status"
|
|
? { ...result.body as object, backend: getBuildInfo() } : result.body);
|
|
} catch (error) {
|
|
const safe = error instanceof UpdaterError ? error : new UpdaterError("UPDATER_UNAVAILABLE");
|
|
return reply.code(safe.status).send({ error: { code: safe.code, message: safe.message } });
|
|
}
|
|
};
|
|
server.get("/admin/updates/status", async (_req, reply) => forward(reply, "GET", "/v1/status"));
|
|
server.get<{ Params: { id: string } }>("/admin/updates/requests/:id", async (req, reply) => {
|
|
if (!requestId.safeParse(req.params.id).success) return reply.code(400).send({ error: { code: "INVALID_REQUEST" } });
|
|
return forward(reply, "GET", `/v1/requests/${req.params.id}`);
|
|
});
|
|
server.get<{ Params: { id: string } }>("/admin/updates/requests/:id/logs", async (req, reply) => {
|
|
if (!requestId.safeParse(req.params.id).success) return reply.code(400).send({ error: { code: "INVALID_REQUEST" } });
|
|
return forward(reply, "GET", `/v1/requests/${req.params.id}/logs`);
|
|
});
|
|
for (const command of ["check", "apply"] as const) {
|
|
server.post(`/admin/updates/${command}`, { bodyLimit: 4096 }, async (req, reply) => {
|
|
// The auth plugin verifies this JWT. Cookies alone cannot trigger host operations (CSRF).
|
|
if (!/^Bearer \S{11,}$/.test(req.headers.authorization || "")) {
|
|
return reply.code(403).send({ error: { code: "BEARER_REQUIRED", message: "Bitte erneut anmelden." } });
|
|
}
|
|
const parsed = (command === "apply" ? applyBody : checkBody).safeParse(req.body);
|
|
if (!parsed.success) return reply.code(400).send({ error: { code: "INVALID_REQUEST" } });
|
|
return forward(reply, "POST", `/v1/${command}`, parsed.data);
|
|
});
|
|
}
|
|
}
|