/** * Security Test Utilities * Shared helpers for security-related tests */ import type { H3Event } from "vinxi/http"; import { SignJWT } from "jose"; import { env } from "~/env/server"; /** * Create a mock H3Event for testing * Creates a minimal structure that works with our cookie/header fallback logic */ export function createMockEvent(options: { headers?: Record; cookies?: Record; method?: string; url?: string; }): H3Event { const { headers = {}, cookies = {}, method = "POST", url = "http://localhost:3000/" } = options; // Build the cookie header string from the cookies object only const cookieString = Object.entries(cookies) .map(([key, value]) => `${key}=${value}`) .join("; "); // Build request headers: spread individual headers, then add the cookie header // This keeps headers and cookies separate — headers stay as headers, // cookies are serialized into the Cookie header only. const allHeaders: Record = { ...headers, ...(cookieString ? { cookie: cookieString } : {}) }; // Try to create Headers object, fall back to plain object if headers contain invalid values let headersObj: Headers | Record; try { headersObj = new Headers(allHeaders); } catch (e) { // If Headers constructor fails (e.g., unicode in headers), use plain object headersObj = allHeaders; } // Create mock event with headers accessible via .headers.get() and .node.req.headers const mockEvent = { headers: headersObj, node: { req: { headers: allHeaders }, res: { cookies: {} } } } as unknown as H3Event; return mockEvent; } /** * Generate a valid JWT token for testing */ export async function createTestJWT( userId: string, expiresIn: string = "1h" ): Promise { const secret = new TextEncoder().encode(env.JWT_SECRET_KEY); return await new SignJWT({ id: userId }) .setProtectedHeader({ alg: "HS256" }) .setExpirationTime(expiresIn) .sign(secret); } /** * Generate an expired JWT token for testing */ export async function createExpiredJWT(userId: string): Promise { const secret = new TextEncoder().encode(env.JWT_SECRET_KEY); return await new SignJWT({ id: userId }) .setProtectedHeader({ alg: "HS256" }) .setExpirationTime("-1h") // Expired 1 hour ago .sign(secret); } /** * Generate a JWT with invalid signature */ export async function createInvalidSignatureJWT( userId: string ): Promise { const wrongSecret = new TextEncoder().encode("wrong-secret-key"); return await new SignJWT({ id: userId }) .setProtectedHeader({ alg: "HS256" }) .setExpirationTime("1h") .sign(wrongSecret); } /** * Generate test credentials */ export function createTestCredentials() { return { email: `test-${Date.now()}@example.com`, password: "TestPass123!@#", passwordConfirmation: "TestPass123!@#" }; } /** * Common SQL injection payloads */ export const SQL_INJECTION_PAYLOADS = [ "' OR '1'='1", "'; DROP TABLE User; --", "admin'--", "' UNION SELECT * FROM User--", "1' OR 1=1--", "' OR 'x'='x", "1; DELETE FROM User WHERE 1=1--", "' AND 1=0 UNION ALL SELECT * FROM User--" ]; /** * Common XSS payloads */ export const XSS_PAYLOADS = [ "", "", "javascript:alert('XSS')", "", "", "", "" ]; /** * Wait for async operations with timeout */ export async function waitFor( condition: () => boolean | Promise, timeout: number = 5000, interval: number = 100 ): Promise { const startTime = Date.now(); while (Date.now() - startTime < timeout) { if (await condition()) { return; } await new Promise((resolve) => setTimeout(resolve, interval)); } throw new Error(`Timeout waiting for condition after ${timeout}ms`); } /** * Measure execution time */ export async function measureTime( fn: () => Promise ): Promise<{ result: T; duration: number }> { const start = Date.now(); const result = await fn(); const duration = Date.now() - start; return { result, duration }; } /** * Generate random string for testing */ export function randomString(length: number = 10): string { const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; return Array.from( { length }, () => chars[Math.floor(Math.random() * chars.length)] ).join(""); } /** * Generate random IP address */ export function randomIP(): string { return Array.from({ length: 4 }, () => Math.floor(Math.random() * 256)).join( "." ); }