ircu2/tools/iauthd-ts/tests/auth/file.test.ts

185 lines
6.5 KiB
TypeScript

/**
* Tests for FileAuthProvider
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { writeFileSync, unlinkSync, mkdtempSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { FileAuthProvider } from '../../src/auth/providers/file.js';
import { generateHash } from '../../src/sasl.js';
describe('FileAuthProvider', () => {
let tempDir: string;
let usersFile: string;
beforeEach(() => {
tempDir = mkdtempSync(join(tmpdir(), 'iauthd-test-'));
usersFile = join(tempDir, 'users');
});
afterEach(() => {
try {
unlinkSync(usersFile);
} catch {
// Ignore if file doesn't exist
}
});
describe('initialization', () => {
it('should initialize with empty users file', async () => {
writeFileSync(usersFile, '');
const provider = new FileAuthProvider({ provider: 'file', path: usersFile });
await provider.initialize();
expect(provider.isHealthy()).toBe(true);
expect(provider.getUserCount()).toBe(0);
});
it('should initialize with non-existent file', async () => {
const provider = new FileAuthProvider({
provider: 'file',
path: '/nonexistent/path/users',
});
await provider.initialize();
expect(provider.isHealthy()).toBe(true);
expect(provider.getUserCount()).toBe(0);
});
it('should load users from file', async () => {
writeFileSync(usersFile, 'testuser:plainpassword\nadmin:adminpass\n');
const provider = new FileAuthProvider({ provider: 'file', path: usersFile });
await provider.initialize();
expect(provider.isHealthy()).toBe(true);
expect(provider.getUserCount()).toBe(2);
});
it('should use default priority of 100', async () => {
writeFileSync(usersFile, '');
const provider = new FileAuthProvider({ provider: 'file', path: usersFile });
expect(provider.priority).toBe(100);
});
it('should use custom priority', async () => {
writeFileSync(usersFile, '');
const provider = new FileAuthProvider({
provider: 'file',
path: usersFile,
priority: 50,
});
expect(provider.priority).toBe(50);
});
});
describe('authentication', () => {
it('should authenticate valid user with plain password', async () => {
writeFileSync(usersFile, 'testuser:testpass\n');
const provider = new FileAuthProvider({ provider: 'file', path: usersFile });
await provider.initialize();
const result = await provider.authenticate('testuser', 'testpass');
expect(result.success).toBe(true);
expect(result.account).toBe('testuser');
});
it('should authenticate with SHA-256 hash', async () => {
const hash = generateHash('secretpass', 'sha256');
writeFileSync(usersFile, `testuser:${hash}\n`);
const provider = new FileAuthProvider({ provider: 'file', path: usersFile });
await provider.initialize();
const result = await provider.authenticate('testuser', 'secretpass');
expect(result.success).toBe(true);
expect(result.account).toBe('testuser');
});
it('should authenticate with SHA-512 hash', async () => {
const hash = generateHash('secretpass', 'sha512');
writeFileSync(usersFile, `testuser:${hash}\n`);
const provider = new FileAuthProvider({ provider: 'file', path: usersFile });
await provider.initialize();
const result = await provider.authenticate('testuser', 'secretpass');
expect(result.success).toBe(true);
expect(result.account).toBe('testuser');
});
it('should reject invalid password', async () => {
writeFileSync(usersFile, 'testuser:correctpass\n');
const provider = new FileAuthProvider({ provider: 'file', path: usersFile });
await provider.initialize();
const result = await provider.authenticate('testuser', 'wrongpass');
expect(result.success).toBe(false);
expect(result.error).toBe('Invalid credentials');
});
it('should reject unknown user', async () => {
writeFileSync(usersFile, 'testuser:testpass\n');
const provider = new FileAuthProvider({ provider: 'file', path: usersFile });
await provider.initialize();
const result = await provider.authenticate('unknownuser', 'testpass');
expect(result.success).toBe(false);
});
it('should be case-insensitive for usernames', async () => {
writeFileSync(usersFile, 'TestUser:testpass\n');
const provider = new FileAuthProvider({ provider: 'file', path: usersFile });
await provider.initialize();
const result = await provider.authenticate('TESTUSER', 'testpass');
expect(result.success).toBe(true);
});
it('should return error if not initialized', async () => {
const provider = new FileAuthProvider({ provider: 'file', path: usersFile });
// Don't call initialize
const result = await provider.authenticate('testuser', 'testpass');
expect(result.success).toBe(false);
expect(result.error).toBe('Users database not loaded');
});
});
describe('reload', () => {
it('should reload users file', async () => {
writeFileSync(usersFile, 'user1:pass1\n');
const provider = new FileAuthProvider({ provider: 'file', path: usersFile });
await provider.initialize();
expect(provider.getUserCount()).toBe(1);
// Modify file
writeFileSync(usersFile, 'user1:pass1\nuser2:pass2\n');
await provider.reload();
expect(provider.getUserCount()).toBe(2);
});
it('should detect file modifications during auth', async () => {
writeFileSync(usersFile, 'user1:pass1\n');
const provider = new FileAuthProvider({ provider: 'file', path: usersFile });
await provider.initialize();
// File is automatically reloaded if modified
// Wait a bit to ensure mtime changes
await new Promise((r) => setTimeout(r, 100));
writeFileSync(usersFile, 'user1:newpass\n');
// Auth should use new password after auto-reload
const result = await provider.authenticate('user1', 'newpass');
expect(result.success).toBe(true);
});
});
describe('shutdown', () => {
it('should mark provider as unhealthy after shutdown', async () => {
writeFileSync(usersFile, 'testuser:testpass\n');
const provider = new FileAuthProvider({ provider: 'file', path: usersFile });
await provider.initialize();
expect(provider.isHealthy()).toBe(true);
await provider.shutdown();
expect(provider.isHealthy()).toBe(false);
});
});
});