50 lines
1.6 KiB
TypeScript
50 lines
1.6 KiB
TypeScript
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";
|
|
|
|
export { isStaff };
|
|
|
|
export async function getMinStaffRank(): Promise<number> {
|
|
const n = Number(await siteSettings.get("min_staff_rank", "7"));
|
|
return Number.isFinite(n) ? n : 7;
|
|
}
|
|
|
|
export interface StaffUser {
|
|
id: number;
|
|
rank: number;
|
|
username: string;
|
|
}
|
|
|
|
/**
|
|
* Gate for admin pages and actions: redirects to /login when unauthenticated
|
|
* and to / when authenticated but not staff. Returns the staff user otherwise.
|
|
*/
|
|
export async function requireStaff(): Promise<StaffUser> {
|
|
const session = await auth();
|
|
if (!session?.user?.id) redirect("/login");
|
|
const minRank = await getMinStaffRank();
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Like requireStaff but also rate-limits the action per staff user (30 requests
|
|
* per minute). Use on sensitive admin actions (ban, rank-change, settings edit).
|
|
*/
|
|
export async function requireStaffRateLimited(): Promise<StaffUser> {
|
|
const staff = await requireStaff();
|
|
const ip = await clientIp();
|
|
if (!(await rateLimit(`admin:${staff.id}:${ip}`, 30, 60_000)).ok) redirect("/admin?error=ratelimit");
|
|
return staff;
|
|
}
|