Closes the two gaps 010-identity-auth explicitly deferred (password reset, login rate-limiting), plus a shared password-strength validator both the reset-consume endpoint and admin account creation now depend on. - Password reset: single-use, paired-Redis-key tokens (never in Postgres), identical response regardless of account existence, stubbed delivery via a structured log line (no email infrastructure exists yet). - Password strength: one validatePasswordStrength() call site, wired into both POST /admin/users and the reset-consume flow. - Login rate-limiting: checkRateLimit keyed by submitted email, checked before any credential verification. Also fixes tests/helpers/auth.ts's shared loginAs() helper, which reused two fixed accounts across the whole integration suite via upsert — now rate-limited per email, that collided across ~30 files sharing one budget. Each call now gets a unique email; no call sites needed to change. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
38 lines
1.3 KiB
TypeScript
38 lines
1.3 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { AuthService } from '@/modules/identity/auth/service/auth.service';
|
|
import * as cache from '@/infrastructure/cache';
|
|
|
|
describe('AuthService.login rate-limit ordering (User Story 3)', () => {
|
|
beforeEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it('checks the rate limit before ever looking up the account', async () => {
|
|
const findByEmail = vi.fn().mockResolvedValue(null);
|
|
const repo = { findByEmail } as never;
|
|
const service = new AuthService(repo);
|
|
|
|
vi.spyOn(cache, 'checkRateLimit').mockResolvedValue({ allowed: false, count: 6 });
|
|
|
|
await expect(
|
|
service.login({ email: 'agent@example.com', password: 'anything' }),
|
|
).rejects.toMatchObject({ statusCode: 429 });
|
|
|
|
expect(findByEmail).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('proceeds to credential checks once the rate limit allows the attempt', async () => {
|
|
const findByEmail = vi.fn().mockResolvedValue(null);
|
|
const repo = { findByEmail } as never;
|
|
const service = new AuthService(repo);
|
|
|
|
vi.spyOn(cache, 'checkRateLimit').mockResolvedValue({ allowed: true, count: 1 });
|
|
|
|
await expect(
|
|
service.login({ email: 'agent@example.com', password: 'anything' }),
|
|
).rejects.toMatchObject({ statusCode: 401 });
|
|
|
|
expect(findByEmail).toHaveBeenCalledWith('agent@example.com');
|
|
});
|
|
});
|