import { describe, expect, it } from 'vitest'; import { signInitData, validateInitData } from '../src/auth/telegram.ts'; import { base32Decode, base32Encode, generateTotpSecret, totpCode, verifyTotp } from '../src/auth/totp.ts'; const TOKEN = '123456:test-token'; const user = { id: 42, first_name: 'Anna', language_code: 'ru' }; describe('Telegram initData', () => { it('принимает корректную подпись', () => { const res = validateInitData(signInitData(TOKEN, user, { startParam: 'l-ru' }), TOKEN, 3600); expect(res).toMatchObject({ ok: true, user: { id: 42 }, startParam: 'l-ru' }); }); it('отклоняет отсутствующие, подделанные, чужим токеном и просроченные данные', () => { expect(validateInitData(undefined, TOKEN, 3600)).toEqual({ ok: false, reason: 'MISSING' }); const signed = signInitData(TOKEN, user); const tampered = signed.replace('%22id%22%3A42', '%22id%22%3A43'); expect(tampered).not.toBe(signed); expect(validateInitData(tampered, TOKEN, 3600)).toEqual({ ok: false, reason: 'BAD_HASH' }); expect(validateInitData(signed, '999:other', 3600)).toEqual({ ok: false, reason: 'BAD_HASH' }); expect(validateInitData(signed.replace(/hash=[0-9a-f]+/, 'hash=zz'), TOKEN, 3600)).toEqual({ ok: false, reason: 'BAD_HASH' }); const old = signInitData(TOKEN, user, { authDate: new Date(Date.now() - 2 * 3600_000) }); expect(validateInitData(old, TOKEN, 3600)).toEqual({ ok: false, reason: 'EXPIRED' }); }); it('без токена бота не принимает ничего', () => { expect(validateInitData(signInitData(TOKEN, user), '', 3600)).toEqual({ ok: false, reason: 'NO_TOKEN' }); }); it('совпадает с эталонным алгоритмом Telegram (порядок ключей не важен)', () => { const signed = new URLSearchParams(signInitData(TOKEN, user)); const reordered = new URLSearchParams([...signed.entries()].reverse()); expect(validateInitData(reordered.toString(), TOKEN, 3600).ok).toBe(true); }); }); describe('TOTP', () => { it('RFC 6238: эталонный вектор SHA-1', () => { // секрет "12345678901234567890", T = 59 c → 94287082 (8 цифр) → последние 6: 287082 const secret = base32Encode(Buffer.from('12345678901234567890')); expect(totpCode(secret, 59_000)).toBe('287082'); expect(base32Decode(secret).toString()).toBe('12345678901234567890'); }); it('проверяет код с допуском ±30 с', () => { const secret = generateTotpSecret(); const now = Date.now(); expect(verifyTotp(secret, totpCode(secret, now), now)).toBe(true); expect(verifyTotp(secret, totpCode(secret, now - 30_000), now)).toBe(true); expect(verifyTotp(secret, totpCode(secret, now - 120_000), now)).toBe(false); expect(verifyTotp(secret, 'abcdef', now)).toBe(false); }); });