Files
ccs/tests/unit/web-server/auth-middleware.test.ts
T

168 lines
5.2 KiB
TypeScript

/**
* Auth Middleware Tests
* Tests for dashboard authentication middleware and routes.
*/
import { describe, it, expect, beforeEach, afterEach, mock } from 'bun:test';
import bcrypt from 'bcrypt';
// Mock the config loader
const mockAuthConfig = {
enabled: false,
username: '',
password_hash: '',
session_timeout_hours: 24,
};
mock.module('../../src/config/unified-config-loader', () => ({
getDashboardAuthConfig: () => ({ ...mockAuthConfig }),
}));
describe('Dashboard Auth', () => {
beforeEach(() => {
// Reset to default disabled state
mockAuthConfig.enabled = false;
mockAuthConfig.username = '';
mockAuthConfig.password_hash = '';
});
describe('getDashboardAuthConfig', () => {
it('returns disabled by default', async () => {
const { getDashboardAuthConfig } = await import('../../src/config/unified-config-loader');
const config = getDashboardAuthConfig();
expect(config.enabled).toBe(false);
});
it('returns 24 hour default session timeout', async () => {
const { getDashboardAuthConfig } = await import('../../src/config/unified-config-loader');
const config = getDashboardAuthConfig();
expect(config.session_timeout_hours).toBe(24);
});
});
describe('bcrypt password hashing', () => {
it('generates valid bcrypt hash', async () => {
const password = 'testpassword123';
const hash = await bcrypt.hash(password, 10);
expect(hash).toMatch(/^\$2[aby]\$\d{2}\$.{53}$/);
});
it('verifies correct password', async () => {
const password = 'testpassword123';
const hash = await bcrypt.hash(password, 10);
const isValid = await bcrypt.compare(password, hash);
expect(isValid).toBe(true);
});
it('rejects incorrect password', async () => {
const password = 'testpassword123';
const hash = await bcrypt.hash(password, 10);
const isValid = await bcrypt.compare('wrongpassword', hash);
expect(isValid).toBe(false);
});
it('timing-safe comparison for wrong password', async () => {
const password = 'testpassword123';
const hash = await bcrypt.hash(password, 10);
// bcrypt.compare should take similar time for wrong vs right password
// This is a basic check that the function works
const start1 = performance.now();
await bcrypt.compare('wrongpassword', hash);
const time1 = performance.now() - start1;
const start2 = performance.now();
await bcrypt.compare(password, hash);
const time2 = performance.now() - start2;
// Both should complete (timing comparison is handled by bcrypt internally)
expect(time1).toBeGreaterThan(0);
expect(time2).toBeGreaterThan(0);
});
});
describe('public paths', () => {
const PUBLIC_PATHS = ['/api/auth/login', '/api/auth/check', '/api/auth/setup', '/api/health'];
it('identifies public paths correctly', () => {
const isPublicPath = (path: string) =>
PUBLIC_PATHS.some((p) => path.toLowerCase().startsWith(p));
expect(isPublicPath('/api/auth/login')).toBe(true);
expect(isPublicPath('/api/auth/check')).toBe(true);
expect(isPublicPath('/api/auth/setup')).toBe(true);
expect(isPublicPath('/api/health')).toBe(true);
expect(isPublicPath('/api/profiles')).toBe(false);
expect(isPublicPath('/api/config')).toBe(false);
});
it('handles case-insensitive paths', () => {
const isPublicPath = (path: string) =>
PUBLIC_PATHS.some((p) => path.toLowerCase().startsWith(p));
expect(isPublicPath('/API/AUTH/LOGIN')).toBe(true);
expect(isPublicPath('/Api/Health')).toBe(true);
});
});
describe('session configuration', () => {
it('session timeout converts to milliseconds correctly', () => {
const hours = 24;
const maxAge = hours * 60 * 60 * 1000;
expect(maxAge).toBe(86400000); // 24 hours in ms
});
it('custom session timeout works', () => {
const hours = 8;
const maxAge = hours * 60 * 60 * 1000;
expect(maxAge).toBe(28800000); // 8 hours in ms
});
});
describe('auth flow logic', () => {
it('bypasses auth when disabled', () => {
mockAuthConfig.enabled = false;
const shouldSkip = !mockAuthConfig.enabled;
expect(shouldSkip).toBe(true);
});
it('requires auth when enabled', () => {
mockAuthConfig.enabled = true;
mockAuthConfig.username = 'admin';
mockAuthConfig.password_hash = '$2b$10$test';
const shouldSkip = !mockAuthConfig.enabled;
expect(shouldSkip).toBe(false);
});
it('validates username match', () => {
mockAuthConfig.username = 'admin';
const usernameMatch = 'admin' === mockAuthConfig.username;
expect(usernameMatch).toBe(true);
});
it('rejects wrong username', () => {
mockAuthConfig.username = 'admin';
const usernameMatch = 'wrong' === mockAuthConfig.username;
expect(usernameMatch).toBe(false);
});
});
describe('rate limiting config', () => {
it('rate limit window is 15 minutes', () => {
const windowMs = 15 * 60 * 1000;
expect(windowMs).toBe(900000);
});
it('max attempts is 5', () => {
const maxAttempts = 5;
expect(maxAttempts).toBe(5);
});
});
});