import { db, usersTable, auditLogsTable } from "@workspace/db"; import { eq, desc } from "drizzle-orm"; import jwt from "jsonwebtoken"; import app from "../src/app.js"; import http from "http"; async function runTest() { console.log("šŸš€ Starting E2E User Creation & Audit Log Verification Test...\n"); let server: http.Server | null = null; try { // 1. Find or create an admin user in the database let [admin] = await db.select().from(usersTable).where(eq(usersTable.role, "admin")).limit(1); if (!admin) { console.log("ā„¹ļø No admin user found. Creating a temporary admin user..."); [admin] = await db.insert(usersTable).values({ email: "admin_" + Date.now() + "@example.com", name: "System Admin", role: "admin", team: "Executive", passwordHash: "dummy_hash", }).returning(); } console.log(`āœ… Using Admin User: ${admin.name} (ID: ${admin.id}, Email: ${admin.email})`); // 2. Generate JWT Token for Admin const token = jwt.sign( { id: admin.id, email: admin.email, role: admin.role, team: admin.team }, process.env.JWT_SECRET || "", { expiresIn: "1h" } ); // 3. Start Express Server on port 5005 server = app.listen(5005); console.log("āœ… Express Test Server listening on port 5005"); // 4. Prepare Test User Payload const newUserData = { name: "E2E Test User", email: "e2e_test_" + Date.now() + "@example.com", password: "SecurePassword123!", role: "member", team: "Engineering", avatarColor: "#336699", // Attempting mass assignment injection id: 99999, passwordHash: "injected_hash", createdAt: "2020-01-01T00:00:00.000Z", }; console.log("\nšŸ“¦ Sending POST /api/users request with payload (including mass-assignment attempts)..."); // 5. Send Request const res = await fetch("http://localhost:5005/api/users", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}`, }, body: JSON.stringify(newUserData), }); const responseBody = await res.json(); console.log(`HTTP Response Status: ${res.status}`); if (res.status !== 201) { throw new Error(`Expected status 201, got ${res.status}. Response: ${JSON.stringify(responseBody)}`); } console.log("āœ… API Response received successfully. Verifying response payload..."); // Verify response does not expose passwordHash or injected fields if (responseBody.passwordHash || responseBody.password || responseBody.id === 99999) { throw new Error("āŒ API Response exposed sensitive fields or allowed mass assignment!"); } console.log("āœ… API Response is perfectly sanitized (password/passwordHash excluded, mass-assignment ignored)."); const createdUserId = responseBody.id; // 6. Query Supabase directly to verify record in users table console.log(`\nšŸ” Querying Supabase for created user (ID: ${createdUserId})...`); const [dbUser] = await db.select().from(usersTable).where(eq(usersTable.id, createdUserId)); if (!dbUser) { throw new Error("āŒ User record not found in Supabase database!"); } console.log("āœ… User record found in database:"); console.log({ id: dbUser.id, name: dbUser.name, email: dbUser.email, role: dbUser.role, team: dbUser.team, createdAt: dbUser.createdAt, passwordHashIsSet: !!dbUser.passwordHash && dbUser.passwordHash !== "injected_hash" && dbUser.passwordHash !== "SecurePassword123!", }); if (!dbUser.passwordHash || dbUser.passwordHash === "SecurePassword123!" || dbUser.passwordHash === "injected_hash") { throw new Error("āŒ Password was not hashed correctly or mass assignment succeeded!"); } console.log("āœ… Password is securely hashed in the database using bcrypt."); // 7. Query Supabase directly for the audit log entry // Wait briefly for async audit log insertion await new Promise(resolve => setTimeout(resolve, 500)); console.log(`\nšŸ” Querying Supabase for audit log entry...`); const [auditLog] = await db.select().from(auditLogsTable) .where(eq(auditLogsTable.event, "role_change")) .orderBy(desc(auditLogsTable.createdAt)) .limit(1); if (!auditLog) { throw new Error("āŒ Audit log entry for 'role_change' not found!"); } console.log("āœ… Audit log entry found in database:"); console.log({ id: auditLog.id, event: auditLog.event, performedById: auditLog.performedById, entityId: auditLog.entityId, entityType: auditLog.entityType, details: auditLog.details, createdAt: auditLog.createdAt, }); if (auditLog.performedById !== admin.id.toString()) { throw new Error(`āŒ Audit log performedById (${auditLog.performedById}) does not match admin ID (${admin.id})!`); } const details: any = auditLog.details; if (details.password || details.passwordHash) { throw new Error("āŒ Audit log details leaked sensitive password information!"); } console.log("\nšŸŽ‰ ALL E2E VERIFICATION CHECKS PASSED SUCCESSFULLY!"); } catch (err) { console.error("\nāŒ Test Failed:", err); } finally { if (server) { server.close(); console.log("\nāœ… Express server closed."); } } } runTest();