import { isStaff } from "@/lib/admin/is-staff"; import { redirectSafe } from "@/lib/foundation/security"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { clientIp, rateLimit } from "@/lib/rate-limit"; export { isStaff }; export interface StaffUser { id: number; rank: number; username: string; } export async function requireStaff(): Promise { const { session, permissions } = await getAdminContext(); if (!canAccess(permissions, PERMS.ADMIN_DASHBOARD, session.user.rank)) redirectSafe("/", "/"); return { id: session.user.id, rank: session.user.rank, username: session.user.username, }; } /** * Staff gate plus a specific ACL slug. Use for FormData server actions that * must not stop at admin.dashboard alone (RCON, user edits, settings writes). */ export async function requirePermission( permission: string, ): Promise { const { session, permissions } = await getAdminContext(); if (!canAccess(permissions, PERMS.ADMIN_DASHBOARD, session.user.rank)) redirectSafe("/", "/"); if (!canAccess(permissions, permission, session.user.rank)) redirectSafe("/admin", "/admin"); return { id: session.user.id, rank: session.user.rank, username: session.user.username, }; } export async function requirePermissionRateLimited( permission: string, ): Promise { const staff = await requirePermission(permission); const ip = await clientIp(); if (!(await rateLimit(`admin:${staff.id}:${ip}`, 30, 60_000)).ok) redirectSafe("/admin?error=ratelimit", "/admin"); return staff; } export async function requireStaffRateLimited(): Promise { const staff = await requireStaff(); const ip = await clientIp(); if (!(await rateLimit(`admin:${staff.id}:${ip}`, 30, 60_000)).ok) redirectSafe("/admin?error=ratelimit", "/admin"); return staff; } /** * Mod-lite gate: needs any mod.* / moderation ACL, not admin.dashboard. * Use for `/mod` layout so mid-ranks can access without full housekeeping. */ export async function requireMod(): Promise { const { session, permissions } = await getAdminContext(); const ok = canAccess(permissions, PERMS.MOD_DASHBOARD, session.user.rank) || canAccess(permissions, PERMS.MODERATION_VIEW, session.user.rank) || canAccess(permissions, PERMS.MOD_CFH_VIEW, session.user.rank) || canAccess(permissions, PERMS.MOD_ACTIONS, session.user.rank) || canAccess(permissions, PERMS.MOD_BANS_VIEW, session.user.rank); if (!ok) redirectSafe("/", "/"); return { id: session.user.id, rank: session.user.rank, username: session.user.username, }; } /** Mod-lite page gate: any-of permission list (no admin.dashboard required). */ export async function requireModPermission( permission: string | readonly string[], ): Promise { const staff = await requireMod(); const { permissions } = await getAdminContext(); const needed = Array.isArray(permission) ? permission : [permission]; const ok = needed.some((slug) => canAccess(permissions, slug, staff.rank)); if (!ok) redirectSafe("/mod", "/mod"); return staff; }