185 lines
6.5 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
});
|