Metadata-Version: 2.5
Name: s-totpkit
Version: 0.1.4
Summary: TOTP-хранилище и генератор одноразовых кодов для любых сервисов: CLI, библиотека и Telegram-бот выдачи кодов
Project-URL: Homepage, https://gitlab.com/S-kits/totpkit
Project-URL: Source, https://gitlab.com/S-kits/totpkit
Author: Dmitry Semyonov
License: MIT
License-File: LICENSE
Requires-Python: >=3.11
Requires-Dist: keyring>=24
Requires-Dist: pyotp>=2.9
Requires-Dist: s-clikit>=0.1.5
Requires-Dist: s-librarykit>=0.7.7
Provides-Extra: bot
Requires-Dist: aiogram>=3.13; extra == 'bot'
Requires-Dist: aiohttp-socks>=0.11.0; extra == 'bot'
Requires-Dist: httpx[socks]>=0.28.1; extra == 'bot'
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Description-Content-Type: text/markdown

# totpkit — одноразовые коды (TOTP) для любых сервисов

Одно шифрованное хранилище вторых факторов на все сервисы: PyPI, GitHub,
Bitrix24, что угодно. Код можно получить командой, а можно кнопкой в Telegram,
когда ноутбука под рукой нет. Другие проекты берут коды из того же стора одной
строкой, не заводя собственных копий секретов.

## Установка

```bash
uv add s-totpkit            # как зависимость
uv sync --extra bot         # с Telegram-ботом
uv run python scripts/self_check.py
```

## Быстрый старт

```bash
totp entry add --uri "otpauth://totp/PyPI:alice?secret=…&issuer=PyPI"
totp list
totp code pypi              # {"code": "366845", "seconds_left": 20}
```

Вывод — JSON по умолчанию; `--text` переключает на человекочитаемый.

## Из своего кода

```python
from totpkit import provider_for

login(username, password, totp_provider=provider_for("pypi"))
```

Провайдер отдаёт код по сигнатуре `(попытка, прошлая ошибка) -> код` и сам
разбирается с границей 30-секундного окна: ждёт ротацию, когда коду осталось
жить меньше трёх секунд, и никогда не отдаёт дважды один и тот же код — иначе
повторная попытка входа сгорает на коде, который сервис уже отверг.

## Модель хранения

Запись — это **сервис + аккаунт + иконка + группа**. Ключ строится как
`сервис` или `сервис:аккаунт` и служит тем, что вы набираете руками: пока
аккаунт один, работает короткое `totp code pypi`; когда их стало два, короткая
форма даёт ошибку со списком вариантов, а не выдаёт код наугад.

Групп по умолчанию нет — всё лежит одним плоским списком, пока группу не
задали.

Секреты пишутся в системное хранилище (keyring). Если рабочего бэкенда нет, кит
честно предупреждает и уходит в файл — проверить можно командой
`totp store status`. Длинные сторы режутся на куски: у Windows Credential
Manager есть жёсткий потолок на размер значения, и без нарезки хранилище молча
деградировало бы в открытый файл ровно тогда, когда записей стало много.

## Telegram-бот

```bash
totp bot setup            # токен бота и первый разрешённый аккаунт
totp bot run
```

Бот отдаёт коды только аккаунтам из белого списка: бот, отвечающий кому угодно,
— это утечка второго фактора. Карточка показывает код, остаток жизни окна и
кнопку «Обновить» — она стоит первой строкой одна на всю ширину, потому что за
ней тянутся чаще всего (код живёт 30 секунд), а цвет кнопки Bot API задавать не
умеет, и заметность даётся шириной и значком.

Навигация: внизу чата постоянный ряд «🏠 Главное меню» + «🗂 Сервисы», а
возврат в начало есть на каждом экране. «Сервисы» — список сервисов с числом
записей; вход в сервис показывает только его записи. «📋 Все записи» на первом
экране появляется, только когда заведены группы: без групп первый экран и так
плоский список, и кнопка вела бы туда, где человек уже стоит.

«⬅️ Назад» ведёт на ШАГ назад и дополняет «Главное меню», а не заменяет его:
из карточки — в тот список, откуда её открыли (группа, сервис или плоский
список), из записей сервиса — в «Сервисы». Точка возврата едет хвостом в
`callback_data` (код экрана плюс, для группы и сервиса, тот же 12-символьный
токен) — 13 байт, весь колбэк карточки укладывается в 28 из 64. Там, где шаг
назад и есть первый экран, отдельной кнопки нет: две кнопки с одинаковым
действием — выбор без разницы. Кнопки из старых сообщений точки возврата не
знают и по-прежнему ведут домой — падать им нельзя, они живут в чате вечно. Иконку сервиса и подписи
кнопок задаёт провайдер (плагин навыка, entry-point `totpkit.providers`); в
тексте сообщений иконка идёт премиум-эмодзи, в кнопках — обычная (Bot API
рисует `custom_emoji` только по entity сообщения). Если Telegram отверг
разметку, сообщение уходит повторно без неё: код важнее картинки.

## Лицензия

MIT
