Files
livedash-node/tests/unit/password-reset-token.test.ts
Kaj Kowalski 1eea2cc3e4 refactor: fix biome linting issues and update project documentation
- Fix 36+ biome linting issues reducing errors/warnings from 227 to 191
- Replace explicit 'any' types with proper TypeScript interfaces
- Fix React hooks dependencies and useCallback patterns
- Resolve unused variables and parameter assignment issues
- Improve accessibility with proper label associations
- Add comprehensive API documentation for admin and security features
- Update README.md with accurate PostgreSQL setup and current tech stack
- Create complete documentation for audit logging, CSP monitoring, and batch processing
- Fix outdated project information and missing developer workflows
2025-07-12 00:28:09 +02:00

143 lines
4.7 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from "vitest";
import crypto from "node:crypto";
// Mock crypto to test both real and mocked behavior
const originalRandomBytes = crypto.randomBytes;
describe("Password Reset Token Security", () => {
beforeEach(() => {
// Restore original crypto function for these tests
crypto.randomBytes = originalRandomBytes;
});
describe("Token Generation Security Properties", () => {
it("should generate tokens with 64 characters (32 bytes as hex)", () => {
const token = crypto.randomBytes(32).toString("hex");
expect(token).toHaveLength(64);
});
it("should generate unique tokens on each call", () => {
const token1 = crypto.randomBytes(32).toString("hex");
const token2 = crypto.randomBytes(32).toString("hex");
const token3 = crypto.randomBytes(32).toString("hex");
expect(token1).not.toBe(token2);
expect(token2).not.toBe(token3);
expect(token1).not.toBe(token3);
});
it("should generate tokens with proper entropy (no obvious patterns)", () => {
const tokens = new Set();
const numTokens = 100;
// Generate multiple tokens to check for patterns
for (let i = 0; i < numTokens; i++) {
const token = crypto.randomBytes(32).toString("hex");
tokens.add(token);
}
// All tokens should be unique
expect(tokens.size).toBe(numTokens);
});
it("should generate tokens with hex characters only", () => {
const token = crypto.randomBytes(32).toString("hex");
const hexPattern = /^[0-9a-f]+$/;
expect(token).toMatch(hexPattern);
});
it("should have sufficient entropy to prevent brute force attacks", () => {
// 32 bytes = 256 bits of entropy
// This provides 2^256 possible combinations
const token = crypto.randomBytes(32).toString("hex");
// Verify we have the expected length for 256-bit security
expect(token).toHaveLength(64);
// Verify character distribution is roughly uniform
const charCounts = {};
for (const char of token) {
charCounts[char] = (charCounts[char] || 0) + 1;
}
// Should have at least some variety in characters
expect(Object.keys(charCounts).length).toBeGreaterThan(5);
});
it("should be significantly more secure than Math.random() approach", () => {
// Generate tokens using both methods for comparison
const secureToken = crypto.randomBytes(32).toString("hex");
const weakToken = Math.random().toString(36).substring(2, 15);
// Secure token should be much longer
expect(secureToken.length).toBeGreaterThan(weakToken.length * 4);
// Secure token has proper hex format
expect(secureToken).toMatch(/^[0-9a-f]{64}$/);
// Weak token has predictable format
expect(weakToken).toMatch(/^[0-9a-z]+$/);
expect(weakToken.length).toBeLessThan(14);
});
});
describe("Token Collision Resistance", () => {
it("should have virtually zero probability of collision", () => {
const tokens = new Set();
const iterations = 10000;
// Generate many tokens to test collision resistance
for (let i = 0; i < iterations; i++) {
const token = crypto.randomBytes(32).toString("hex");
expect(tokens.has(token)).toBe(false); // No collisions
tokens.add(token);
}
expect(tokens.size).toBe(iterations);
});
});
describe("Performance Characteristics", () => {
it("should generate tokens in reasonable time", () => {
const startTime = Date.now();
// Generate 1000 tokens
for (let i = 0; i < 1000; i++) {
crypto.randomBytes(32).toString("hex");
}
const endTime = Date.now();
const duration = endTime - startTime;
// Should complete in under 1 second
expect(duration).toBeLessThan(1000);
});
});
describe("Token Format Validation", () => {
it("should always produce lowercase hex", () => {
for (let i = 0; i < 10; i++) {
const token = crypto.randomBytes(32).toString("hex");
expect(token).toBe(token.toLowerCase());
expect(token).toMatch(/^[0-9a-f]{64}$/);
}
});
it("should never produce tokens starting with predictable patterns", () => {
const tokens = [];
for (let i = 0; i < 100; i++) {
tokens.push(crypto.randomBytes(32).toString("hex"));
}
// Check that tokens don't all start with same character
const firstChars = new Set(tokens.map((t) => t[0]));
expect(firstChars.size).toBeGreaterThan(1);
// Check that we don't have obvious patterns like all starting with '0'
const zeroStart = tokens.filter((t) => t.startsWith("0")).length;
expect(zeroStart).toBeLessThan(tokens.length * 0.8); // Should be roughly 1/16
});
});
});