import { beforeAll, describe, expect, it } from 'vitest'; import { hashPassword, verifyPassword, encryptSecret, decryptSecret, signPayload, verifyPayload, numericCode, recoveryCode, normalizeRecoveryCode, hmacToken } from './crypto'; import { newTotpSecret, verifyTotp, currentTotp, totpUri } from './totp'; import { passwordStrength } from './password-strength'; beforeAll(() => { process.env.SESSION_SECRET = 'test-secret-test-secret-test-secret'; }); describe('password hashing', () => { it('hashes and verifies with scrypt', async () => { const h = await hashPassword('correct horse battery'); expect(h.startsWith('scrypt$')).toBe(true); expect(await verifyPassword('correct horse battery', h)).toBe(true); expect(await verifyPassword('wrong', h)).toBe(false); expect(await verifyPassword('x', null)).toBe(false); }); it('strength meter', () => { expect(passwordStrength('short').score).toBe(0); expect(passwordStrength('password12').score).toBe(0); expect(passwordStrength('aaaaaaaaaaaa').score).toBe(0); expect(passwordStrength('Tr0ub4dor&3xyz!!').score).toBeGreaterThanOrEqual(2); expect(passwordStrength('correct horse battery staple hymn').score).toBe(4); }); }); describe('codes & secrets', () => { it('numeric codes are 6 digits and hmac is stable', () => { for (let i = 0; i < 50; i++) expect(numericCode(6)).toMatch(/^\d{6}$/); expect(hmacToken('a', 'x')).toBe(hmacToken('a', 'x')); expect(hmacToken('a', 'x')).not.toBe(hmacToken('a', 'y')); }); it('recovery codes normalise', () => { const c = recoveryCode(); expect(c).toMatch(/^[A-Z2-9]{5}-[A-Z2-9]{5}$/); expect(normalizeRecoveryCode(c.toLowerCase().replace('-', ' '))).toBe(c); }); it('aes-gcm roundtrip', () => { const enc = encryptSecret('JBSWY3DPEHPK3PXP'); expect(enc).not.toContain('JBSWY3DPEHPK3PXP'); expect(decryptSecret(enc)).toBe('JBSWY3DPEHPK3PXP'); }); it('signed payloads expire and detect tampering', () => { const t = signPayload({ uid: 'usr_1', stage: 'mfa' }, 60); expect(verifyPayload<{ uid: string }>(t)?.uid).toBe('usr_1'); expect(verifyPayload(`${t}x`)).toBeNull(); const expired = signPayload({ uid: 'usr_1' }, -1); expect(verifyPayload(expired)).toBeNull(); }); }); describe('totp', () => { it('generates and validates', () => { const s = newTotpSecret(); expect(totpUri(s, 'a@b.c')).toContain('otpauth://totp/RareIndex:a%40b.c'); const code = currentTotp(s); expect(verifyTotp(s, code)).not.toBeNull(); expect(verifyTotp(s, '000000') === null || code === '000000').toBe(true); expect(verifyTotp(s, 'abc')).toBeNull(); }); });