(Feat-Fix): Lots of fixes done, reporting system fixed, stricter types

This commit is contained in:
2025-12-19 18:48:05 +00:00
parent 01400ad4e1
commit 865e0bf00e
61 changed files with 10072 additions and 6645 deletions

View File

@@ -10,54 +10,57 @@ export async function rateLimit(ctx: Context, next: Next): Promise<void> {
const now = Date.now();
const windowMs = config.RATE_LIMIT_WINDOW_MS;
const maxRequests = config.RATE_LIMIT_MAX_REQUESTS;
const record = rateLimitStore.get(ip);
if (!record || now > record.resetTime) {
rateLimitStore.set(ip, { count: 1, resetTime: now + windowMs });
} else {
record.count++;
if (record.count > maxRequests) {
ctx.response.status = 429;
ctx.response.body = {
ctx.response.body = {
error: "Too many requests",
retryAfter: Math.ceil((record.resetTime - now) / 1000)
retryAfter: Math.ceil((record.resetTime - now) / 1000),
};
return;
}
}
await next();
}
// Security headers middleware
export async function securityHeaders(ctx: Context, next: Next): Promise<void> {
await next();
// Prevent clickjacking
ctx.response.headers.set("X-Frame-Options", "DENY");
// Prevent MIME type sniffing
ctx.response.headers.set("X-Content-Type-Options", "nosniff");
// XSS protection
ctx.response.headers.set("X-XSS-Protection", "1; mode=block");
// Referrer policy
ctx.response.headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
ctx.response.headers.set(
"Referrer-Policy",
"strict-origin-when-cross-origin",
);
// Content Security Policy
ctx.response.headers.set(
"Content-Security-Policy",
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';"
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';",
);
// Strict Transport Security (only in production with HTTPS)
if (config.isProduction()) {
ctx.response.headers.set(
"Strict-Transport-Security",
"max-age=31536000; includeSubDomains"
"max-age=31536000; includeSubDomains",
);
}
}
@@ -65,27 +68,35 @@ export async function securityHeaders(ctx: Context, next: Next): Promise<void> {
// CORS middleware
export async function cors(ctx: Context, next: Next): Promise<void> {
const origin = ctx.request.headers.get("Origin");
const allowedOrigins = config.CORS_ORIGIN.split(",").map(o => o.trim());
const allowedOrigins = config.CORS_ORIGIN.split(",").map((o) => o.trim());
// Check if origin is allowed
if (origin && (allowedOrigins.includes(origin) || allowedOrigins.includes("*"))) {
if (
origin && (allowedOrigins.includes(origin) || allowedOrigins.includes("*"))
) {
ctx.response.headers.set("Access-Control-Allow-Origin", origin);
} else if (config.isDevelopment()) {
// Allow all origins in development
ctx.response.headers.set("Access-Control-Allow-Origin", origin || "*");
}
ctx.response.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
ctx.response.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
ctx.response.headers.set(
"Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS",
);
ctx.response.headers.set(
"Access-Control-Allow-Headers",
"Content-Type, Authorization",
);
ctx.response.headers.set("Access-Control-Allow-Credentials", "true");
ctx.response.headers.set("Access-Control-Max-Age", "86400");
// Handle preflight requests
if (ctx.request.method === "OPTIONS") {
ctx.response.status = 204;
return;
}
await next();
}
@@ -93,19 +104,21 @@ export async function cors(ctx: Context, next: Next): Promise<void> {
export async function requestLogger(ctx: Context, next: Next): Promise<void> {
const start = Date.now();
const { method, url } = ctx.request;
await next();
const ms = Date.now() - start;
const status = ctx.response.status;
// Color code based on status
let statusColor = "\x1b[32m"; // Green for 2xx
if (status >= 400 && status < 500) statusColor = "\x1b[33m"; // Yellow for 4xx
if (status >= 500) statusColor = "\x1b[31m"; // Red for 5xx
console.log(
`${new Date().toISOString()} - ${method} ${url.pathname} ${statusColor}${status}\x1b[0m ${ms}ms`
`${
new Date().toISOString()
} - ${method} ${url.pathname} ${statusColor}${status}\x1b[0m ${ms}ms`,
);
}
@@ -125,18 +138,32 @@ export function isValidEmail(email: string): boolean {
}
// Validate password strength
export function isStrongPassword(password: string): { valid: boolean; message?: string } {
export function isStrongPassword(
password: string,
): { valid: boolean; message?: string } {
if (password.length < 8) {
return { valid: false, message: "Password must be at least 8 characters long" };
return {
valid: false,
message: "Password must be at least 8 characters long",
};
}
if (!/[A-Z]/.test(password)) {
return { valid: false, message: "Password must contain at least one uppercase letter" };
return {
valid: false,
message: "Password must contain at least one uppercase letter",
};
}
if (!/[a-z]/.test(password)) {
return { valid: false, message: "Password must contain at least one lowercase letter" };
return {
valid: false,
message: "Password must contain at least one lowercase letter",
};
}
if (!/[0-9]/.test(password)) {
return { valid: false, message: "Password must contain at least one number" };
return {
valid: false,
message: "Password must contain at least one number",
};
}
return { valid: true };
}