Add EpicNext CMS foundation layer and fix critical security gaps
- 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
@@ -0,0 +1,67 @@
|
||||
export class DomainError extends Error {
|
||||
public readonly status: number;
|
||||
|
||||
constructor(message: string, status: number = 500) {
|
||||
super(message);
|
||||
this.name = "DomainError";
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
|
||||
export class NotFoundError extends DomainError {
|
||||
constructor(entity: string, id?: number | string) {
|
||||
super(id ? `${entity} #${id} not found` : `${entity} not found`, 404);
|
||||
this.name = "NotFoundError";
|
||||
}
|
||||
}
|
||||
|
||||
export class UnauthorizedError extends DomainError {
|
||||
constructor(message: string = "Unauthorized") {
|
||||
super(message, 401);
|
||||
this.name = "UnauthorizedError";
|
||||
}
|
||||
}
|
||||
|
||||
export class ForbiddenError extends DomainError {
|
||||
constructor(message: string = "Forbidden") {
|
||||
super(message, 403);
|
||||
this.name = "ForbiddenError";
|
||||
}
|
||||
}
|
||||
|
||||
export class ValidationError extends DomainError {
|
||||
public readonly fieldErrors: Record<string, string[]>;
|
||||
|
||||
constructor(fieldErrors: Record<string, string[]>) {
|
||||
super("Validation failed", 422);
|
||||
this.name = "ValidationError";
|
||||
this.fieldErrors = fieldErrors;
|
||||
}
|
||||
}
|
||||
|
||||
export class RateLimitError extends DomainError {
|
||||
public readonly retryAfter: number;
|
||||
|
||||
constructor(retryAfter: number) {
|
||||
super(`Rate limited. Try again in ${retryAfter}s.`, 429);
|
||||
this.name = "RateLimitError";
|
||||
this.retryAfter = retryAfter;
|
||||
}
|
||||
}
|
||||
|
||||
export class ConflictError extends DomainError {
|
||||
constructor(message: string) {
|
||||
super(message, 409);
|
||||
this.name = "ConflictError";
|
||||
}
|
||||
}
|
||||
|
||||
export class DatabaseError extends DomainError {
|
||||
public readonly cause: unknown;
|
||||
|
||||
constructor(message: string, cause?: unknown) {
|
||||
super(message, 500);
|
||||
this.name = "DatabaseError";
|
||||
this.cause = cause;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user