Skip to main content

s-authkit-client — слой ДОСТУПА

Библиотека, отвечающая на один вопрос: сервис меня узнаёт? Всё, что нужно, чтобы представиться сервису и удержать это представление живым — хранилище сессий, хранилище пары токенов, OAuth-обновление по сроку и живая проба доступа.

Браузера внутри нет: ни импорта, ни зависимости, ни лениво (см. ниже).

pip install s-authkit-client          # Python >= 3.11
pip install "s-authkit-client[probe]" # + сетевое колесо для живой пробы

Место в семействе китов

    clientkit                     <- сборщик клиентов по декларации
    /        \
authkit-client  browserkit        <- доступ (кто ты) / чеканка (как войти)
    \        /
      netkit                      <- сеть (чем ходить)
         |
      corekit                     <- основание (значения и чистые правила)

Обязательная зависимость одна — s-corekit (значения SessionRef, ExecutionContext, канон диагностики доступа). Соседи сверху и сбоку — отдельные пакеты, и ни один из них этому киту не нужен.

Что внутри

модуль что делает
authkit_client.sessions SessionStore: каталог — источник правды, SQLite — производный индекс без секретов, storage_state под Fernet (DEK в .dek.enc, обёрнут KEK из keyring / env / парольной фразы)
authkit_client.secret_store SecretStore: пара токенов (access/refresh) в keyring с прозрачным file-fallback (Windows CredWrite ломается на длинных JWT — WinError 1783)
authkit_client.oauth OAuthSession: проактивное обновление по TTL (живой токен = ноль сетевых вызовов), single-flight, окончательный OAuthRevoked вместо вечного ретрая
authkit_client.access_probe ЖИВАЯ проба: спросить сервер, а не посмотреть на файл с куками
authkit_client.session_fingerprint инвариант «один egress + один профиль»: сессия, отчеканенная через один выход, не уезжает в другой
authkit_client.ports протоколы SessionStorePort / SecretStorePort / FetchPort / MinterPort и реестр чеканщиков
authkit_client.context_port ссылка на носителя ExecutionContext (мультитенантность), если его завёл вышележащий слой
authkit_client.diagnosis реэкспорт AccessState / AccessVerdict / classify_access из corekit

Импорт самого кита ничего тяжёлого не исполняет: sqlite3, cryptography, keyring, platformdirs подтягиваются по месту использования (PEP 562) — платит тот, кто действительно открыл хранилище или полез в keyring.

Хранилище сессий

import asyncio
from corekit import SessionRef
from authkit_client import SessionStore

store = SessionStore(root="~/.myapp")           # состояние ляжет в <root>/sessions/
ref = SessionRef(profile="work", social="example", account_id="me@example.com")

async def main() -> None:
    await store.save_state(ref, {"cookies": [], "origins": []})  # шифруется
    state = await store.load_state(ref)                          # расшифровывается
    print(store.path_for(ref), await store.list(profile="work"))

asyncio.run(main())

Каталоги 0700 / файлы 0600 выставляются кросс-платформенно (на Windows — через ACL). storage_state шифруется Fernet-ом: случайный DEK лежит в .dek.enc, обёрнутый KEK из OS keyring, переменной окружения или парольной фразы (Argon2id). Открытым текстом состояние молча не пишется никогда: если KEK недоступен, а среда неинтерактивна — отказ с громким предупреждением.

Пара токенов

from authkit_client import SecretStore

secrets = SecretStore("myapp")               # keyring-namespace = "myapp"
secrets.save_tokens("me@example.com", access="…", refresh="…")
access, refresh = secrets.load_tokens("me@example.com")

Порядок чтения: env MYAPP_ACCESS_TOKEN / MYAPP_REFRESH_TOKEN → keyring → файловый fallback. Fallback включается сам, когда keyring отказал (например, Windows Credential Manager на blob'е длиннее ~2.5 КБ).

OAuth с проактивным обновлением

from authkit_client import OAuthSession, OAuthTokens

session = OAuthSession(
    OAuthTokens(access_token="…", refresh_token="…", expires_at=1893456000.0),
    refresher=refresh_call,   # async (payload) -> Mapping: чем ходить — дело вызывающего
    on_tokens=persist,        # куда сохранять обновлённую пару
)
await session.maybe_refresh()  # внутри срока — сравнение чисел, ноль запросов

Своего HTTP-клиента модуль не имеет: обмен кода и обмен refresh-токена делает колбэк потребителя. Отказ invalid_grant считается окончательным — дальнейшие вызовы падают OAuthRevoked мгновенно, вместо того чтобы долбить сервер.

Живая проба доступа

from authkit_client import probe_access, probe_for

@probe_for("example", endpoint_markers=("unknown method",))
async def _probe(ctx):
    return await client.get("https://example.com/", follow_redirects=True)

verdict = await probe_access("example", ctx)
if verdict.needs_human:
    ...  # сервер действительно отверг куки, а не «сети нет»

Вердикт выносится по подписи ответа, а не по наличию файла с куками: «сервер увёл на страницу входа» (нужен человек) и «до сервера не дошли» (чинить выход) — разные состояния, и путать их дорого. Сеть приносит сама проба: своего HTTP-клиента у кита нет, подойдёт httpx, curl_cffi или что угодно утиное.

Браузера здесь нет

Минт сессии — дело браузерного слоя, и он приходит сюда через порт:

from authkit_client.ports import register_minter, minted_state

class NodriverMinter:                       # живёт в браузерном ките
    name = "nodriver"
    async def mint(self, ref, ctx=None): ...

register_minter(NodriverMinter())
state = await minted_state("nodriver", ref)  # слой доступа не знает про браузер

Причина простая: клиенту белого API с api-ключом браузер не нужен никогда, а жадный импорт стоил бы ему playwright/nodriver на каждом старте. Инвариант закреплён tests/test_no_browser_imports.py — тест смотрит на sys.modules свежего интерпретатора, а не на строчки исходника.

Хранилища подменяемы

SessionStorePort / SecretStorePort — структурные протоколы: подходит любой объект с нужными методами. Реализация «из памяти» проходит их без единой строки SQL, и код потребителя при подмене не правится (tests/test_ports.py).

Совместимость

Кит выделен из s-librarykit, поэтому у тех, кто ставит их вместе, прежние пути импорта — librarykit.sessions, librarykit.secret_store, librarykit.access_probe, librarykit.oauth, librarykit.session_fingerprint — продолжают работать: там это sys.modules-алиасы на те же объекты модулей, а не копии, включая приватные имена, которые патчат тесты. Сам по себе s-authkit-client самодостаточен и librarykit не требует.

Сохранены и прежние имена переменных окружения / keyring-namespace'ов: новые ключи пишутся в нейтральный namespace, а ранее созданные сессии по-прежнему расшифровываются через fallback.

Лицензия

MIT — см. LICENSE.

Metadata

Release files for s-authkit-client 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for s-authkit-client 0.1.0
File Size Uploaded
s_authkit_client-0.1.0.tar.gz 79.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for s-authkit-client 0.1.0
File Interpreter ABI Platform
s_authkit_client-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 149.7 kB

Release files / s_authkit_client-0.1.0.tar.gz

Download URL s_authkit_client-0.1.0.tar.gz
Size 79.9 kB
Tags Source
SHA-256 checksum
How to use checksums
a319a52ee784169df5c78af6845df04cbbc13efc77d35eb459ba04d6c779b2d1
BLAKE2b-256 checksum
How to use checksums
fed8d1523f0d51da81f893668ed8f27500c4891b2d6b8596e4c02e455d0932be
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / s_authkit_client-0.1.0-py3-none-any.whl

Download URL s_authkit_client-0.1.0-py3-none-any.whl
Size 69.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
149d27318de153947ad858432c91f090b6e992a1976c887e1f9dec8978c05559
BLAKE2b-256 checksum
How to use checksums
a7cf621c596f5edbd201a1c1e50c39f0c2fbb2e1c03bce8eeeafc8c48582f354
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

0.1.1

2 release files

This release

0.1.0 This release

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page