Snapshot of uncommitted work: landing, bots, prod infra, partner cabinet, backoffice stage 1

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
zabudko-ruandClaude Opus 5.5 committed 2026-10-21 01:23:32 +04:00
1 parent 45d7512d2b
commit d452824c62
314 files changed
+44394 -121

No files matched your search

+53
View File
@@ -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);
});
});