🔐 Access API — контроль доступа

Централизованная авторизация сотрудников. Сервис спрашивает нас, можно ли пускать пользователя; при увольнении мы сами отзываем доступ через webhook. Источник правды — статус сотрудника (работает/уволен). Идентификация по Telegram-аккаунту.

Аутентификация

Каждый запрос к API — с вашим ключом в заголовке. Ключ выдаётся в Telegram-боте (меню «🔌 API-доступы»). Ключ секретный, показывается один раз.

Authorization: Bearer <ВАШ_КЛЮЧ>

POST /v1/access/check

Вызывайте при входе пользователя и на каждой новой сессии. Возвращает решение.

Запрос:

{
  "tg_id": 5496126295,
  "tg_username": "nicksmi"
}

tg_username обязателен — решение принимается только по нему (штат ведётся по Telegram-никам). Без него ответ всегда allow: false. tg_id передавайте тоже: он нужен, чтобы адресно отозвать доступ и заметить смену ника, но на решение он не влияет.

⚠️ Присылайте только подлинные пары tg_id + tg_username. Мы запоминаем связку tg_id → username из ваших запросов и используем её для адресации отзыва. Она не влияет на выдачу доступа, но если прислать выдуманный tg_id с чужим ником, вы засорите карту и отзыв может уйти не на тот аккаунт. Берите пару из доверенного источника: initData Mini App (подписан Telegram) или getChat(id) — не собирайте её вручную.

Ответ:

{
  "allow": true,
  "status": "active",      // active | fired | unknown | conflict
  "reason": "Сотрудник работает",
  "username": "nicksmi",
  "position": "Админ ночной",   // должность из штата
  "department": "Редакция",     // департамент
  "direction": "Новости SMI1",  // направление
  "unit": "Коржаков"            // отдел (заполнен не у всех — бывает null)
}

Поля position / department / direction / unit могут быть null, если в штате не заполнены. Отдел (unit) заполнен примерно у двух третей сотрудников — не полагайтесь на него как на обязательный. Решение о доступе (allow) они не меняют — это данные для вашей фильтрации, см. ниже.

🎭 Фильтрация по ролям и департаментам

Система отвечает только «работает / не работает». Кому какую роль выдать — решает сам сервис по полям position и department.

Пример: пускать только Редакцию, при этом ночным админам давать роль наблюдателя:

r = check(tg_id, tg_username)
if not r["allow"]:
    deny()

# фильтр по департаменту
if r["department"] != "Редакция":
    deny()

# маппинг должности на свою роль
pos = (r["position"] or "").lower()
if "ночной" in pos:
    grant("наблюдатель")
elif "редактор" in pos:
    grant("админ")
else:
    grant("обычный")

⚠️ Должность в штате — свободный текст, который заполняют руками («Админ», «Админ ночной», «Админ предложки», «Админ, редактор выходного дня»). Сравнивайте по вхождению и без учёта регистра, а не точным равенством — иначе новый вариант написания сломает вашу логику.

Пример (curl):

curl -X POST https://access-api.smi1.net/v1/access/check \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"tg_id": 5496126295, "tg_username": "nicksmi"}'

GET /v1/access/active

Список работающих — для периодической сверки на вашей стороне (страховка).

curl https://access-api.smi1.net/v1/access/active -H "Authorization: Bearer $KEY"

Ответ:

{
  "count": 83,
  "usernames": ["ivanov", "petrov", ...],
  "employees": [
    {"username": "ivanov", "position": "Админ ночной", "department": "Редакция",
      "direction": "Новости SMI1", "unit": "Коржаков"},
    ...
  ]
}

Фильтры (необязательные, ищут по вхождению без учёта регистра):

# только Редакция
curl "https://access-api.smi1.net/v1/access/active?department=Редакция" -H "Authorization: Bearer $KEY"

# только ночные админы
curl "https://access-api.smi1.net/v1/access/active?position=ночной" -H "Authorization: Bearer $KEY"

# только отдел (кол. F)
curl "https://access-api.smi1.net/v1/access/active?unit=Коржаков" -H "Authorization: Bearer $KEY"

Доступные фильтры: department, position, direction, unit. Можно комбинировать. Без фильтров возвращаются все работающие.

Webhook отзыва (мы → вы)

Когда сотрудника увольняют, мы сами шлём POST на ваш webhook-URL (укажите его в карточке сервиса в боте). Тело подписано HMAC-SHA256 на вашем webhook_secret — проверяйте подпись, чтобы доверять запросу.

Что прилетит:

POST <ваш webhook_url>
X-Event: revoke
X-Signature: <hex HMAC-SHA256 тела>

{"event":"revoke","username":"ivanov","tg_ids":[12345678],"ts":1720000000}

Ваша задача по этому запросу — закрыть доступ / убить сессии пользователя.

Проверка подписи (Python):

import hmac, hashlib
def verify(secret: str, raw_body: bytes, signature: str) -> bool:
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

Как правильно интегрироваться

МоментДействие
Вход / новая сессияСпросить /v1/access/check, пускать при allow=true
УвольнениеПринять наш webhook revoke → закрыть доступ
СтраховкаРаз в сутки сверяться с /v1/access/active

Не кэшируйте «allow» навсегда — перепроверяйте на каждой новой сессии. Так потерянный webhook не оставит уволенного с доступом.

Вопросы по подключению — к администратору сервиса.