Harden CMS security and theme contrast

This commit is contained in:
Simo committed 2026-07-11 20:27:20 +02:00
1 parent 2465ff2170
commit 4a1e1115b3
57 files changed
+1023 -231

No files matched your search

+10 -6
View File
@@ -1,6 +1,8 @@
import { redirect } from "next/navigation";
import { isStaff } from "@/lib/admin/is-staff";
import { resolveStaffUser } from "@/lib/admin/staff-user";
import { auth } from "@/lib/auth";
import { prisma } from "@/lib/prisma";
import { siteSettings } from "@/lib/services/site-settings";
import { clientIp, rateLimit } from "@/lib/rate-limit";
@@ -25,12 +27,14 @@ export async function requireStaff(): Promise<StaffUser> {
const session = await auth();
if (!session?.user?.id) redirect("/login");
const minRank = await getMinStaffRank();
if (!isStaff(session.user.rank, minRank)) redirect("/");
return {
id: Number(session.user.id),
rank: session.user.rank,
username: session.user.name ?? "",
};
const staff = await resolveStaffUser(session.user.id, minRank, (id) =>
prisma.user.findUnique({
where: { id },
select: { id: true, rank: true, username: true },
}),
);
if (!staff) redirect("/");
return staff;
}
/**
+24
View File
@@ -0,0 +1,24 @@
import { describe, expect, it } from "vitest";
import { adminMutationNotice } from "@/lib/admin/notice";
describe("adminMutationNotice", () => {
it("maps a successful redirect to a safe notice", () => {
expect(adminMutationNotice({ saved: "1" })).toEqual({
tone: "ok",
label: "Saved",
message: "Changes were saved successfully.",
});
});
it("maps an error code without reflecting arbitrary query text", () => {
expect(adminMutationNotice({ error: "<script>" })).toEqual({
tone: "danger",
label: "Error",
message: "The operation could not be completed.",
});
});
it("returns null when there is no mutation result", () => {
expect(adminMutationNotice({})).toBeNull();
});
});
+26
View File
@@ -0,0 +1,26 @@
export interface AdminMutationNotice {
tone: "ok" | "danger";
label: "Saved" | "Error";
message: string;
}
export function adminMutationNotice(params: {
saved?: string;
error?: string;
}): AdminMutationNotice | null {
if (params.error) {
return {
tone: "danger",
label: "Error",
message: "The operation could not be completed.",
};
}
if (params.saved === "1") {
return {
tone: "ok",
label: "Saved",
message: "Changes were saved successfully.",
};
}
return null;
}
+20
View File
@@ -0,0 +1,20 @@
import { describe, expect, it } from "vitest";
import { resolveStaffUser } from "@/lib/admin/staff-user";
describe("resolveStaffUser", () => {
it("rejects a session whose database rank has been revoked", async () => {
const findUser = async () => ({ id: 7, rank: 1, username: "Alice" });
await expect(resolveStaffUser("7", 7, findUser)).resolves.toBeNull();
});
it("returns the current database identity instead of stale JWT values", async () => {
const findUser = async () => ({ id: 7, rank: 8, username: "CurrentName" });
await expect(resolveStaffUser("7", 7, findUser)).resolves.toEqual({
id: 7,
rank: 8,
username: "CurrentName",
});
});
});
+21
View File
@@ -0,0 +1,21 @@
import { isStaff } from "@/lib/admin/is-staff";
export interface StaffUserRecord {
id: number;
rank: number;
username: string;
}
export type FindStaffUser = (id: number) => Promise<StaffUserRecord | null>;
export async function resolveStaffUser(
sessionUserId: string,
minStaffRank: number,
findUser: FindStaffUser,
): Promise<StaffUserRecord | null> {
const id = Number(sessionUserId);
if (!Number.isSafeInteger(id) || id <= 0) return null;
const user = await findUser(id);
return user && isStaff(user.rank, minStaffRank) ? user : null;
}