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
@@ -1,5 +1,4 @@
|
||||
import { unstable_cache } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
import { cache } from "react";
|
||||
import { auth } from "./auth";
|
||||
import { sessionUserId } from "./auth/session-user";
|
||||
@@ -7,6 +6,7 @@ import { prisma } from "./prisma";
|
||||
import { logAuthorizationEvent } from "./admin/authorization-events";
|
||||
import { isDynamicSuperAdmin } from "./admin/authorization-policy";
|
||||
import { resolveAuthorizationState } from "./admin/rank-authority";
|
||||
import { redirectSafe } from "./foundation/security";
|
||||
|
||||
// Re-export PERMS from the standalone file (safe for client components)
|
||||
export { PERMS } from "./permission-slugs";
|
||||
@@ -121,13 +121,13 @@ const getCurrentAuthorizationState = cache(async (userId: number) =>
|
||||
export async function getAdminContext() {
|
||||
const session = await auth();
|
||||
if (!session?.user) {
|
||||
redirect("/login");
|
||||
redirectSafe("/login", "/login");
|
||||
}
|
||||
|
||||
const userId = sessionUserId(session.user.id);
|
||||
if (!userId) redirect("/login");
|
||||
if (!userId) redirectSafe("/login", "/login");
|
||||
const state = await getCurrentAuthorizationState(userId);
|
||||
if (!state) redirect("/login");
|
||||
if (!state) redirectSafe("/login", "/login");
|
||||
const permissions = await loadUserPermissions(userId, state.actor.rank, state.highestRank);
|
||||
return {
|
||||
session: {
|
||||
@@ -175,15 +175,15 @@ export function canAccess(permissions: PermissionSet, slug: string, _rank?: numb
|
||||
export async function getModContext() {
|
||||
const session = await auth();
|
||||
if (!session?.user) {
|
||||
redirect("/login");
|
||||
redirectSafe("/login", "/login");
|
||||
}
|
||||
|
||||
const userId = sessionUserId(session.user.id);
|
||||
if (!userId) redirect("/login");
|
||||
if (!userId) redirectSafe("/login", "/login");
|
||||
const state = await getCurrentAuthorizationState(userId);
|
||||
if (!state) redirect("/");
|
||||
if (!state) redirectSafe("/", "/");
|
||||
const permissions = await loadUserPermissions(userId, state.actor.rank, state.highestRank);
|
||||
if (!canAccess(permissions, PERMS.MOD_DASHBOARD)) redirect("/");
|
||||
if (!canAccess(permissions, PERMS.MOD_DASHBOARD)) redirectSafe("/", "/");
|
||||
return {
|
||||
session: {
|
||||
...session,
|
||||
|
||||
Reference in new issue
Block a user