import { NobleCryptoPlugin, ScureBase32Plugin, TOTP } from 'otplib'; import { buildTotpUri, generateTotpSecret, verifyTotpCode } from './totp.util'; const crypto = new NobleCryptoPlugin(); const base32 = new ScureBase32Plugin(); describe('totp.util', () => { it('generates a base32 secret', () => { const secret = generateTotpSecret(); expect(secret).toMatch(/^[A-Z2-7]+$/); }); it('builds an otpauth:// URI carrying the issuer and label', () => { const uri = buildTotpUri('JBSWY3DPEHPK3PXP', 'admin@apicore.local'); expect(uri).toContain('otpauth://totp/'); expect(uri).toContain('ApiCore'); expect(uri).toContain('secret=JBSWY3DPEHPK3PXP'); }); it('verifies a code generated for the same secret', async () => { const secret = generateTotpSecret(); const totp = new TOTP({ secret, crypto, base32 }); const code = await totp.generate(); await expect( verifyTotpCode(secret, 'admin@apicore.local', code), ).resolves.toBe(true); }); it('rejects an incorrect code', async () => { const secret = generateTotpSecret(); const totp = new TOTP({ secret, crypto, base32 }); const validCode = await totp.generate(); const wrongCode = validCode === '000000' ? '111111' : '000000'; await expect( verifyTotpCode(secret, 'admin@apicore.local', wrongCode), ).resolves.toBe(false); }); it('rejects a code generated for a different secret', async () => { const secretA = generateTotpSecret(); const secretB = generateTotpSecret(); const totpB = new TOTP({ secret: secretB, crypto, base32 }); const codeFromB = await totpB.generate(); await expect( verifyTotpCode(secretA, 'admin@apicore.local', codeFromB), ).resolves.toBe(false); }); });