import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import type { Request } from 'express' const users = vi.hoisted(() => ({ list: [] as { id: string; username: string; role: string }[], })) vi.mock('../logger.js', () => ({ logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() }, })) vi.mock('../users.js', () => ({ hasUsers : () => users.list.length > 0, findByUsername: (name: string) => users.list.find(u => u.username.toLowerCase() === name.toLowerCase()), createUser : (username: string, _password: string, role: string) => { const user = { id: `id-${username}`, username, role } users.list.push(user) return user }, })) const ENV_KEYS = ['PROXY_AUTH_HEADER', 'PROXY_AUTH_TRUSTED_PROXIES', 'PROXY_AUTH_AUTO_CREATE', 'PROXY_AUTH_LOGOUT_URL'] async function load(env: Record) { for (const key of ENV_KEYS) delete process.env[key] Object.assign(process.env, env) vi.resetModules() return import('./proxy-auth.js') } function request(remoteAddress: string, headers: Record = {}): Request { return { socket: { remoteAddress }, headers } as unknown as Request } beforeEach(() => { users.list = [{ id: 'id-alice', username: 'Alice', role: 'admin' }] }) afterEach(() => { for (const key of ENV_KEYS) delete process.env[key] }) describe('adresses de confiance', () => { it('reconnaît une plage IPv4, une adresse exacte et une IPv4 vue par un socket IPv6', async () => { const { parseTrustedProxies, isTrustedAddress } = await load({}) const trusted = parseTrustedProxies('172.18.0.0/16, 10.0.0.5, fd00::1') expect(isTrustedAddress('172.18.3.4', trusted)).toBe(true) expect(isTrustedAddress('::ffff:172.18.0.2', trusted)).toBe(true) expect(isTrustedAddress('10.0.0.5', trusted)).toBe(true) expect(isTrustedAddress('FD00::1', trusted)).toBe(true) expect(isTrustedAddress('172.19.0.1', trusted)).toBe(false) expect(isTrustedAddress('10.0.0.6', trusted)).toBe(false) expect(isTrustedAddress(undefined, trusted)).toBe(false) }) it('ignore les entrées invalides', async () => { const { parseTrustedProxies } = await load({}) expect(parseTrustedProxies('pas-une-ip, 10.0.0.0/40, fd00::/64')).toHaveLength(0) }) }) describe('proxyAuthUser', () => { const enabled = { PROXY_AUTH_HEADER: 'X-User', PROXY_AUTH_TRUSTED_PROXIES: '172.18.0.0/16' } it('est désactivé sans adresse de confiance, même si l\'en-tête est défini', async () => { const mod = await load({ PROXY_AUTH_HEADER: 'X-User' }) expect(mod.PROXY_AUTH_ENABLED).toBe(false) expect(mod.proxyAuthUser(request('172.18.0.2', { 'x-user': 'alice' }))).toBeUndefined() }) it('renvoie le compte désigné par un proxy de confiance, sans tenir compte de la casse', async () => { const mod = await load(enabled) expect(mod.proxyAuthUser(request('172.18.0.2', { 'x-user': 'alice' }))?.id).toBe('id-alice') }) it('ignore l\'en-tête venant d\'une adresse non déclarée', async () => { const mod = await load(enabled) expect(mod.proxyAuthUser(request('192.168.1.50', { 'x-user': 'alice' }))).toBeUndefined() }) it('ne fait rien sans en-tête', async () => { const mod = await load(enabled) expect(mod.proxyAuthUser(request('172.18.0.2'))).toBeUndefined() }) it('ne crée pas de compte inconnu par défaut', async () => { const mod = await load(enabled) expect(mod.proxyAuthUser(request('172.18.0.2', { 'x-user': 'bob' }))).toBeUndefined() expect(users.list).toHaveLength(1) }) it('crée un compte utilisateur avec PROXY_AUTH_AUTO_CREATE', async () => { const mod = await load({ ...enabled, PROXY_AUTH_AUTO_CREATE: 'true' }) const user = mod.proxyAuthUser(request('172.18.0.2', { 'x-user': 'bob' })) expect(user).toMatchObject({ username: 'bob', role: 'user' }) }) it('laisse l\'assistant créer le premier compte', async () => { users.list = [] const mod = await load({ ...enabled, PROXY_AUTH_AUTO_CREATE: 'true' }) expect(mod.proxyAuthUser(request('172.18.0.2', { 'x-user': 'bob' }))).toBeUndefined() expect(users.list).toHaveLength(0) }) it('n\'expose l\'adresse de déconnexion que si l\'authentification par proxy est active', async () => { expect((await load({ PROXY_AUTH_LOGOUT_URL: '/oauth2/sign_out' })).PROXY_AUTH_LOGOUT_URL).toBe('') expect((await load({ ...enabled, PROXY_AUTH_LOGOUT_URL: '/oauth2/sign_out' })).PROXY_AUTH_LOGOUT_URL).toBe('/oauth2/sign_out') }) })