import { AuthService } from '../src/identity/auth.service'; import { AuthStore } from '../src/identity/auth.store'; import { PasswordService } from '../src/identity/password.service'; import { RateLimitService } from '../src/identity/rate-limit.service'; import { parseEnvironment } from '../src/config/environment'; describe('login policy', () => { const store = { findUser: jest.fn(), createSession: jest.fn(), failedLogin: jest.fn(), }; const passwords = { verify: jest.fn(), dummyVerify: jest.fn() }; const limits = { consume: jest.fn() }; const service = new AuthService( store as unknown as AuthStore, passwords as unknown as PasswordService, limits as unknown as RateLimitService, parseEnvironment({ DATABASE_URL: 'postgresql://localhost/mani' }), ); const input = { organizationId: 'organization', email: 'user@example.com', password: 'password', }; beforeEach(() => { jest.resetAllMocks(); store.findUser.mockResolvedValue({ id: 'user', organizationId: 'organization', status: 'ACTIVE', passwordHash: 'hash', }); passwords.verify.mockResolvedValue(true); }); it('passes only the token digest to persistence', async () => { const result = await service.login(input); expect(store.createSession).toHaveBeenCalledWith( 'user', 'hash', expect.stringMatching(/^[a-f0-9]{64}$/), expect.any(Date), ); expect(JSON.stringify(store.createSession.mock.calls)).not.toContain( result.accessToken, ); }); it('does not create sessions for unapproved users', async () => { store.findUser.mockResolvedValue({ id: 'user', status: 'PENDING', organizationId: 'organization', passwordHash: 'hash', }); await expect(service.login(input)).rejects.toThrow('Invalid credentials'); expect(store.createSession).not.toHaveBeenCalled(); }); it('does equivalent hashing work for unknown users', async () => { store.findUser.mockResolvedValue(null); await expect(service.login(input)).rejects.toThrow('Invalid credentials'); expect(passwords.dummyVerify).toHaveBeenCalledWith(input.password); }); it('stops before querying users when throttled', async () => { limits.consume.mockRejectedValue(new Error('limited')); await expect(service.login(input)).rejects.toThrow('limited'); expect(store.findUser).not.toHaveBeenCalled(); }); });