http.test.ts•9.57 kB
import { describe, test, expect } from "bun:test";
import { parseBasicAuth } from "../../../src/auth/http.js";
import { createHash } from "crypto";
describe("HTTP Authentication", () => {
describe("parseBasicAuth", () => {
test("should parse valid Basic Auth header", () => {
const email = "test@example.com";
const password = "mypassword123";
const authHeader = `Basic ${Buffer.from(`${email}:${password}`).toString("base64")}`;
const result = parseBasicAuth(authHeader);
expect(result.email).toBe(email);
expect(result.password).toBe(password);
});
test("should handle special characters in password", () => {
const email = "user@domain.com";
const password = "p@ssw0rd!#$%^&*()";
const authHeader = `Basic ${Buffer.from(`${email}:${password}`).toString("base64")}`;
const result = parseBasicAuth(authHeader);
expect(result.email).toBe(email);
expect(result.password).toBe(password);
});
test("should handle colons in password", () => {
const email = "admin@test.com";
const password = "pass:with:colons";
const authHeader = `Basic ${Buffer.from(`${email}:${password}`).toString("base64")}`;
const result = parseBasicAuth(authHeader);
expect(result.email).toBe(email);
expect(result.password).toBe(password);
});
test("should handle empty password", () => {
const email = "user@example.com";
const password = "";
const authHeader = `Basic ${Buffer.from(`${email}:${password}`).toString("base64")}`;
const result = parseBasicAuth(authHeader);
expect(result.email).toBe(email);
expect(result.password).toBe(password);
});
test("should reject malformed Base64", () => {
const authHeader = "Basic not-valid-base64!!!";
// Will throw or return unexpected results
expect(() => parseBasicAuth(authHeader)).toThrow();
});
test("should reject auth without email", () => {
const authHeader = `Basic ${Buffer.from(":password").toString("base64")}`;
expect(() => parseBasicAuth(authHeader)).toThrow("Invalid Basic Auth format");
});
test("should reject auth without colon separator", () => {
const authHeader = `Basic ${Buffer.from("emailwithoutcolon").toString("base64")}`;
expect(() => parseBasicAuth(authHeader)).toThrow("Invalid Basic Auth format");
});
});
describe("Cache Key Generation (Security)", () => {
// Note: We're testing the concept, not the actual implementation
// since getCacheKey is not exported
function getCacheKey(email: string, password: string): string {
return createHash("sha256")
.update(`${email}:${password}`)
.digest("hex");
}
test("should generate different keys for different passwords", () => {
const email = "test@example.com";
const password1 = "password1";
const password2 = "password2";
const key1 = getCacheKey(email, password1);
const key2 = getCacheKey(email, password2);
expect(key1).not.toBe(key2);
});
test("should generate different keys for different emails", () => {
const email1 = "user1@example.com";
const email2 = "user2@example.com";
const password = "samepassword";
const key1 = getCacheKey(email1, password);
const key2 = getCacheKey(email2, password);
expect(key1).not.toBe(key2);
});
test("should generate same key for identical credentials", () => {
const email = "test@example.com";
const password = "mypassword";
const key1 = getCacheKey(email, password);
const key2 = getCacheKey(email, password);
expect(key1).toBe(key2);
});
test("should generate deterministic hashes", () => {
const email = "consistent@example.com";
const password = "consistent-password";
const key1 = getCacheKey(email, password);
// Wait a bit
const key2 = getCacheKey(email, password);
expect(key1).toBe(key2);
});
test("should produce hex string of correct length", () => {
const key = getCacheKey("user@test.com", "password123");
// SHA-256 produces 64 character hex string
expect(key.length).toBe(64);
expect(/^[0-9a-f]+$/.test(key)).toBe(true);
});
test("should handle special characters in credentials", () => {
const email = "user+tag@example.com";
const password = "p@ss!w0rd#$%";
const key = getCacheKey(email, password);
expect(key.length).toBe(64);
expect(typeof key).toBe("string");
});
test("should prevent authentication bypass attack", () => {
// Attack scenario: Try to use correct email but wrong password
const correctEmail = "legitimate@example.com";
const correctPassword = "correct-password";
const wrongPassword = "wrong-password";
const legitKey = getCacheKey(correctEmail, correctPassword);
const attackKey = getCacheKey(correctEmail, wrongPassword);
// These should be completely different
expect(legitKey).not.toBe(attackKey);
// And have no predictable relationship
expect(legitKey.substring(0, 10)).not.toBe(attackKey.substring(0, 10));
});
});
describe("Session Data TTL Validation", () => {
test("should validate idle timeout correctly", () => {
const CLIENT_IDLE_TIMEOUT = 10 * 60 * 1000; // 10 minutes
const now = Date.now();
// Valid: accessed 5 minutes ago
const validSession = {
lastAccessedAt: now - 5 * 60 * 1000,
createdAt: now - 30 * 60 * 1000,
};
const idleTime = now - validSession.lastAccessedAt;
expect(idleTime).toBeLessThan(CLIENT_IDLE_TIMEOUT);
// Invalid: accessed 15 minutes ago
const expiredSession = {
lastAccessedAt: now - 15 * 60 * 1000,
createdAt: now - 30 * 60 * 1000,
};
const expiredIdleTime = now - expiredSession.lastAccessedAt;
expect(expiredIdleTime).toBeGreaterThan(CLIENT_IDLE_TIMEOUT);
});
test("should validate max lifetime correctly", () => {
const CLIENT_MAX_LIFETIME = 60 * 60 * 1000; // 1 hour
const now = Date.now();
// Valid: created 30 minutes ago
const validSession = {
lastAccessedAt: now,
createdAt: now - 30 * 60 * 1000,
};
const lifetime = now - validSession.createdAt;
expect(lifetime).toBeLessThan(CLIENT_MAX_LIFETIME);
// Invalid: created 90 minutes ago (even if recently accessed)
const expiredSession = {
lastAccessedAt: now, // Just accessed!
createdAt: now - 90 * 60 * 1000,
};
const expiredLifetime = now - expiredSession.createdAt;
expect(expiredLifetime).toBeGreaterThan(CLIENT_MAX_LIFETIME);
});
test("should require both idle and lifetime checks to pass", () => {
const CLIENT_IDLE_TIMEOUT = 10 * 60 * 1000;
const CLIENT_MAX_LIFETIME = 60 * 60 * 1000;
const now = Date.now();
// Case 1: Valid idle but expired lifetime
const case1 = {
lastAccessedAt: now - 5 * 60 * 1000, // 5 min ago - VALID
createdAt: now - 90 * 60 * 1000, // 90 min ago - EXPIRED
};
const case1Valid =
now - case1.lastAccessedAt < CLIENT_IDLE_TIMEOUT &&
now - case1.createdAt < CLIENT_MAX_LIFETIME;
expect(case1Valid).toBe(false); // Should be invalid
// Case 2: Expired idle but valid lifetime
const case2 = {
lastAccessedAt: now - 20 * 60 * 1000, // 20 min ago - EXPIRED
createdAt: now - 30 * 60 * 1000, // 30 min ago - VALID
};
const case2Valid =
now - case2.lastAccessedAt < CLIENT_IDLE_TIMEOUT &&
now - case2.createdAt < CLIENT_MAX_LIFETIME;
expect(case2Valid).toBe(false); // Should be invalid
// Case 3: Both valid
const case3 = {
lastAccessedAt: now - 5 * 60 * 1000, // 5 min ago - VALID
createdAt: now - 30 * 60 * 1000, // 30 min ago - VALID
};
const case3Valid =
now - case3.lastAccessedAt < CLIENT_IDLE_TIMEOUT &&
now - case3.createdAt < CLIENT_MAX_LIFETIME;
expect(case3Valid).toBe(true); // Should be valid
});
});
describe("Sliding Window Behavior", () => {
test("should extend session lifetime with access", () => {
const CLIENT_IDLE_TIMEOUT = 10 * 60 * 1000;
const now = Date.now();
// Session accessed at T=0
let lastAccessed = now;
// Access at T+5min (within timeout)
lastAccessed = now + 5 * 60 * 1000;
expect(lastAccessed - now).toBeLessThan(CLIENT_IDLE_TIMEOUT);
// Access again at T+12min (would have expired without previous access)
const newNow = now + 12 * 60 * 1000;
expect(newNow - lastAccessed).toBeLessThan(CLIENT_IDLE_TIMEOUT); // Valid due to sliding window
});
test("should not extend beyond max lifetime", () => {
const CLIENT_IDLE_TIMEOUT = 10 * 60 * 1000;
const CLIENT_MAX_LIFETIME = 60 * 60 * 1000;
const createdAt = Date.now();
// Even with continuous access every 5 minutes...
const lastAccessedAt = createdAt + 90 * 60 * 1000; // 90 minutes later
const now = createdAt + 90 * 60 * 1000;
const idleValid = now - lastAccessedAt < CLIENT_IDLE_TIMEOUT; // true
const lifetimeValid = now - createdAt < CLIENT_MAX_LIFETIME; // false
expect(idleValid).toBe(true);
expect(lifetimeValid).toBe(false);
expect(idleValid && lifetimeValid).toBe(false); // Session invalid despite recent access
});
});
});