security hardening

This commit is contained in:
Michael Freno
2025-12-28 20:04:29 -05:00
parent aefd467660
commit 1ba20339a8
22 changed files with 5177 additions and 116 deletions

View File

@@ -0,0 +1,417 @@
/**
* Authorization Tests
* Tests for access control, privilege escalation prevention, and admin access
*/
import { describe, it, expect } from "bun:test";
import { getUserID, getPrivilegeLevel } from "~/server/auth";
import { createMockEvent, createTestJWT } from "./test-utils";
import { env } from "~/env/server";
describe("Authorization", () => {
describe("Admin Access Control", () => {
it("should grant admin access to configured admin user", async () => {
const adminToken = await createTestJWT(env.ADMIN_ID);
const event = createMockEvent({
cookies: { userIDToken: adminToken }
});
const privilege = await getPrivilegeLevel(event);
expect(privilege).toBe("admin");
});
it("should deny admin access to regular users", async () => {
const userToken = await createTestJWT("regular-user-123");
const event = createMockEvent({
cookies: { userIDToken: userToken }
});
const privilege = await getPrivilegeLevel(event);
expect(privilege).toBe("user");
expect(privilege).not.toBe("admin");
});
it("should deny admin access to anonymous users", async () => {
const event = createMockEvent({});
const privilege = await getPrivilegeLevel(event);
expect(privilege).toBe("anonymous");
expect(privilege).not.toBe("admin");
});
it("should not allow privilege escalation through token tampering", async () => {
// Create a regular user token
const regularToken = await createTestJWT("regular-user-123");
// Attacker tries to modify token to include admin ID
// This should fail signature verification
const parts = regularToken.split(".");
const fakeAdminPayload = Buffer.from(
JSON.stringify({ id: env.ADMIN_ID })
).toString("base64url");
const tamperedToken = `${parts[0]}.${fakeAdminPayload}.${parts[2]}`;
const event = createMockEvent({
cookies: { userIDToken: tamperedToken }
});
const privilege = await getPrivilegeLevel(event);
expect(privilege).toBe("anonymous"); // Invalid token = anonymous
});
it("should handle malformed admin ID gracefully", async () => {
const invalidIds = ["", null, undefined, " ", "admin'--"];
for (const invalidId of invalidIds) {
const token = await createTestJWT(invalidId as string);
const event = createMockEvent({
cookies: { userIDToken: token }
});
const privilege = await getPrivilegeLevel(event);
// Should not grant admin access for invalid IDs
expect(privilege).not.toBe("admin");
}
});
});
describe("User Access Control", () => {
it("should grant user access to authenticated users", async () => {
const userToken = await createTestJWT("user-123");
const event = createMockEvent({
cookies: { userIDToken: userToken }
});
const privilege = await getPrivilegeLevel(event);
expect(privilege).toBe("user");
});
it("should deny user access to anonymous requests", async () => {
const event = createMockEvent({});
const privilege = await getPrivilegeLevel(event);
expect(privilege).toBe("anonymous");
expect(privilege).not.toBe("user");
});
it("should maintain user access with valid token", async () => {
const userToken = await createTestJWT("user-456");
const event = createMockEvent({
cookies: { userIDToken: userToken }
});
const userId = await getUserID(event);
expect(userId).toBe("user-456");
const privilege = await getPrivilegeLevel(event);
expect(privilege).toBe("user");
});
});
describe("Privilege Escalation Prevention", () => {
it("should prevent horizontal privilege escalation", async () => {
const user1Token = await createTestJWT("user-1");
const user2Token = await createTestJWT("user-2");
const event1 = createMockEvent({
cookies: { userIDToken: user1Token }
});
const event2 = createMockEvent({
cookies: { userIDToken: user2Token }
});
const user1Id = await getUserID(event1);
const user2Id = await getUserID(event2);
expect(user1Id).toBe("user-1");
expect(user2Id).toBe("user-2");
expect(user1Id).not.toBe(user2Id);
});
it("should prevent vertical privilege escalation", async () => {
// Regular user should not be able to become admin
const userToken = await createTestJWT("regular-user");
const event = createMockEvent({
cookies: { userIDToken: userToken }
});
const privilege = await getPrivilegeLevel(event);
expect(privilege).toBe("user");
// Even with multiple checks, privilege should remain the same
const privilege2 = await getPrivilegeLevel(event);
expect(privilege2).toBe("user");
});
it("should not allow session hijacking through token reuse", async () => {
const user1Token = await createTestJWT("user-1");
// User 1's token should always return user 1's ID
const event1 = createMockEvent({
cookies: { userIDToken: user1Token }
});
const id1 = await getUserID(event1);
// Even if attacker captures token, it still identifies as user 1
const event2 = createMockEvent({
cookies: { userIDToken: user1Token }
});
const id2 = await getUserID(event2);
expect(id1).toBe("user-1");
expect(id2).toBe("user-1");
});
it("should prevent privilege escalation via race conditions", async () => {
const userToken = await createTestJWT("concurrent-user");
const event = createMockEvent({
cookies: { userIDToken: userToken }
});
// Simulate concurrent privilege checks
const results = await Promise.all([
getPrivilegeLevel(event),
getPrivilegeLevel(event),
getPrivilegeLevel(event),
getPrivilegeLevel(event),
getPrivilegeLevel(event)
]);
// All results should be the same
expect(results.every((r) => r === "user")).toBe(true);
});
});
describe("Anonymous Access", () => {
it("should handle missing authentication token", async () => {
const event = createMockEvent({});
const privilege = await getPrivilegeLevel(event);
expect(privilege).toBe("anonymous");
});
it("should handle empty authentication token", async () => {
const event = createMockEvent({
cookies: { userIDToken: "" }
});
const privilege = await getPrivilegeLevel(event);
expect(privilege).toBe("anonymous");
});
it("should handle invalid token format", async () => {
const event = createMockEvent({
cookies: { userIDToken: "not-a-jwt-token" }
});
const privilege = await getPrivilegeLevel(event);
expect(privilege).toBe("anonymous");
});
it("should return null user ID for anonymous users", async () => {
const event = createMockEvent({});
const userId = await getUserID(event);
expect(userId).toBeNull();
});
});
describe("Access Control Edge Cases", () => {
it("should handle user ID with special characters", async () => {
const specialUserId = "user-with-special-!@#$%";
const token = await createTestJWT(specialUserId);
const event = createMockEvent({
cookies: { userIDToken: token }
});
const userId = await getUserID(event);
expect(userId).toBe(specialUserId);
});
it("should handle very long user IDs", async () => {
const longUserId = "user-" + "x".repeat(1000);
const token = await createTestJWT(longUserId);
const event = createMockEvent({
cookies: { userIDToken: token }
});
const userId = await getUserID(event);
expect(userId).toBe(longUserId);
});
it("should handle user ID with unicode characters", async () => {
const unicodeUserId = "user-with-unicode-🔐";
const token = await createTestJWT(unicodeUserId);
const event = createMockEvent({
cookies: { userIDToken: token }
});
const userId = await getUserID(event);
expect(userId).toBe(unicodeUserId);
});
it("should handle admin ID case sensitivity", async () => {
const adminId = env.ADMIN_ID;
const wrongCaseId = adminId.toUpperCase();
// Exact match required
const correctToken = await createTestJWT(adminId);
const wrongCaseToken = await createTestJWT(wrongCaseId);
const correctEvent = createMockEvent({
cookies: { userIDToken: correctToken }
});
const wrongCaseEvent = createMockEvent({
cookies: { userIDToken: wrongCaseToken }
});
const correctPrivilege = await getPrivilegeLevel(correctEvent);
const wrongCasePrivilege = await getPrivilegeLevel(wrongCaseEvent);
expect(correctPrivilege).toBe("admin");
// Wrong case should not get admin access (unless IDs match)
if (adminId !== wrongCaseId) {
expect(wrongCasePrivilege).toBe("user");
}
});
});
describe("Authorization Attack Scenarios", () => {
it("should prevent session fixation attacks", async () => {
// Attacker cannot predict or fix session tokens
const token1 = await createTestJWT("user-1");
const token2 = await createTestJWT("user-1");
// Tokens should be different even for same user
// (Due to different timestamps, though payload is same)
expect(token1).toBeDefined();
expect(token2).toBeDefined();
});
it("should prevent parameter pollution attacks", async () => {
// Multiple cookie values should not cause confusion
const token1 = await createTestJWT("user-1");
const token2 = await createTestJWT("user-2");
// Only first cookie should be used
const event = createMockEvent({
cookies: {
userIDToken: token1
// In practice, duplicate cookies are handled by the framework
}
});
const userId = await getUserID(event);
expect(userId).toBe("user-1");
});
it("should prevent token substitution attacks", async () => {
const legitimateToken = await createTestJWT("victim-user");
const attackerToken = await createTestJWT("attacker-user");
// Each token should only authenticate its respective user
const legitimateEvent = createMockEvent({
cookies: { userIDToken: legitimateToken }
});
const attackerEvent = createMockEvent({
cookies: { userIDToken: attackerToken }
});
const legitimateId = await getUserID(legitimateEvent);
const attackerId = await getUserID(attackerEvent);
expect(legitimateId).toBe("victim-user");
expect(attackerId).toBe("attacker-user");
expect(legitimateId).not.toBe(attackerId);
});
it("should prevent authorization bypass through empty checks", async () => {
const emptyChecks = [null, undefined, "", " ", "null", "undefined"];
for (const check of emptyChecks) {
const event = createMockEvent({
cookies: { userIDToken: check as string }
});
const privilege = await getPrivilegeLevel(event);
expect(privilege).toBe("anonymous");
}
});
});
describe("Multi-User Scenarios", () => {
it("should handle multiple concurrent user sessions", async () => {
const users = ["user-1", "user-2", "user-3", "user-4", "user-5"];
const tokens = await Promise.all(users.map((u) => createTestJWT(u)));
const events = tokens.map((token) =>
createMockEvent({ cookies: { userIDToken: token } })
);
const userIds = await Promise.all(events.map(getUserID));
// All users should be correctly identified
expect(userIds).toEqual(users);
});
it("should maintain separate privileges for different users", async () => {
const adminToken = await createTestJWT(env.ADMIN_ID);
const user1Token = await createTestJWT("user-1");
const user2Token = await createTestJWT("user-2");
const adminEvent = createMockEvent({
cookies: { userIDToken: adminToken }
});
const user1Event = createMockEvent({
cookies: { userIDToken: user1Token }
});
const user2Event = createMockEvent({
cookies: { userIDToken: user2Token }
});
const [adminPriv, user1Priv, user2Priv] = await Promise.all([
getPrivilegeLevel(adminEvent),
getPrivilegeLevel(user1Event),
getPrivilegeLevel(user2Event)
]);
expect(adminPriv).toBe("admin");
expect(user1Priv).toBe("user");
expect(user2Priv).toBe("user");
});
});
describe("Performance", () => {
it("should check privileges efficiently", async () => {
const userToken = await createTestJWT("perf-test-user");
const event = createMockEvent({
cookies: { userIDToken: userToken }
});
const start = performance.now();
for (let i = 0; i < 1000; i++) {
await getPrivilegeLevel(event);
}
const duration = performance.now() - start;
// Should complete 1000 checks in less than 100ms
expect(duration).toBeLessThan(100);
});
it("should extract user IDs efficiently", async () => {
const userToken = await createTestJWT("perf-test-user");
const event = createMockEvent({
cookies: { userIDToken: userToken }
});
const start = performance.now();
for (let i = 0; i < 1000; i++) {
await getUserID(event);
}
const duration = performance.now() - start;
// Should complete 1000 extractions in less than 100ms
expect(duration).toBeLessThan(100);
});
});
});