File size: 5,439 Bytes
e166039
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
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();