Add EpicNext CMS foundation layer and fix critical security gaps
Local Build and Deploy / deploy (push) Successful in 1m1s
Local Build and Deploy / deploy (push) Successful in 1m1s
- Create src/lib/foundation/ (860 LOC, 9 files): typed action wrappers, DbService with health checks, CSRF validation, safe redirects, AsyncLocalStorage request tracing, branded types, reusable Zod schemas - Migrate moderation.ts and user-settings.ts to foundation patterns - Fix abuse-guard.ts: bound in-memory Maps with LRU eviction (was unbounded) - Fix access-guard.ts: separate try/catch per check, log degradation instead of blanket fail-open - Replace raw redirect() calls with safeRedirect() in guard.ts and permissions.ts to prevent open-redirect attacks - Add CSRF validation to api-handler.ts for mutating methods - Add canonicalizeFormData() utility for FormData input sanitization
This commit is contained in:
1 parent
8bf1aa2fa7
commit
f6ad030c5b
16 files changed
+1012
-96
No files matched your search
+3
-11
@@ -1,7 +1,7 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import { isStaff } from "@/lib/admin/is-staff";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { clientIp, rateLimit } from "@/lib/rate-limit";
|
||||
import { redirectSafe } from "@/lib/foundation/security";
|
||||
|
||||
export { isStaff };
|
||||
|
||||
@@ -11,23 +11,15 @@ export interface StaffUser {
|
||||
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, permissions } = await getAdminContext();
|
||||
if (!canAccess(permissions, PERMS.ADMIN_DASHBOARD, session.user.rank)) redirect("/");
|
||||
if (!canAccess(permissions, PERMS.ADMIN_DASHBOARD, session.user.rank)) redirectSafe("/", "/");
|
||||
return { id: session.user.id, rank: session.user.rank, username: session.user.username };
|
||||
}
|
||||
|
||||
/**
|
||||
* 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");
|
||||
if (!(await rateLimit(`admin:${staff.id}:${ip}`, 30, 60_000)).ok) redirectSafe("/admin?error=ratelimit", "/admin");
|
||||
return staff;
|
||||
}
|
||||
Reference in new issue
Block a user