Apps, web, i18n, UI, partners, payouts, backoffice, bariki, prod infra
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
45d7512d2b
commit
3489e0b816
319 files changed
+45483
-121
No files matched your search
@@ -0,0 +1,53 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user