Skip to main content

JWT RS256 + refresh-tokens + Argon2 hashing — чистая инфраструктура авторизации для Skillery проектов. 0 завязок на конкретное приложение.

Project description

s-authkit

Ядро авторизации для Skillery проектов: JWT RS256, refresh-token management, Argon2 hashing.

Версия: 0.1 (первый релиз)
Лицензия: MIT
Зависимости: python-jose[cryptography], passlib[argon2], cryptography

Что входит

JWT access-токены (RS256)

  • JoseTokenIssuer — выпуск и верификация JWT
  • TokenPair / TokenClaims — value objects для типизации
  • ensure_jwt_keypair — генерация RSA 2048 ключей

Persistent refresh-tokens

  • IRefreshTokenStore — protocol для БД-реализации
  • RefreshTokenRecord — агрегат с хешированием и валидацией
  • Rotation с audit-trail (rotated_from)

Password hashing

  • Argon2PasswordHasher — Argon2id через passlib

Примеры

1. Инициализация

from pathlib import Path
from authkit import (
    JoseTokenIssuer,
    Argon2PasswordHasher,
    ensure_jwt_keypair,
)

# Создаём ключи (если нет)
keys_dir = Path.home() / ".myapp" / "keys"
ensure_jwt_keypair(
    private_path=keys_dir / "jwt_private.pem",
    public_path=keys_dir / "jwt_public.pem",
)

# Прочитаем ключи
private_key = (keys_dir / "jwt_private.pem").read_text()
public_key = (keys_dir / "jwt_public.pem").read_text()

# Создаём компоненты
hasher = Argon2PasswordHasher()
issuer = JoseTokenIssuer(
    private_key_pem=private_key,
    public_key_pem=public_key,
    issuer="myproject.com",
    access_ttl_min=15,
    refresh_ttl_days=30,
    refresh_store=your_store_impl,  # реализуете вы
)

2. Login (выпуск пары токенов)

async def login_with_password(username: str, password: str):
    # Получаем юзера из БД
    user = await db.get_user_by_username(username)
    if not user:
        raise ValueError("User not found")
    
    # Проверяем пароль
    if not hasher.verify(password, user.password_hash):
        raise ValueError("Invalid password")
    
    # Выпускаем пару
    pair = await issuer.issue_pair(
        user_id=str(user.id),
        company_id=str(user.active_company_id) if user.active_company_id else None,
        permissions={"skill.read", "skill.install"},
    )
    return pair

3. Middleware (верификация токена)

from authkit import TokenError

async def auth_middleware(request, call_next):
    auth_header = request.headers.get("Authorization", "")
    if not auth_header.startswith("Bearer "):
        return Response("Unauthorized", status_code=401)
    
    token = auth_header[7:]
    try:
        claims = issuer.verify_access(token)
    except TokenError as e:
        return Response(f"Invalid token: {e}", status_code=401)
    
    request.state.claims = claims
    return await call_next(request)

4. Refresh (rotation)

async def refresh_session(refresh_token: str):
    try:
        new_pair = await issuer.rotate_refresh(refresh_token)
    except RefreshTokenError as e:
        raise Unauthorized(f"Refresh failed: {e}")
    return new_pair

5. Реализация IRefreshTokenStore

from authkit import RefreshTokenRecord, IRefreshTokenStore

class PostgresRefreshTokenStore:
    def __init__(self, db_engine):
        self.engine = db_engine
    
    async def save(self, record: RefreshTokenRecord) -> None:
        # INSERT/UPDATE в БД
        async with self.engine.begin() as conn:
            await conn.execute(
                "INSERT INTO refresh_tokens (user_id, token_hash, ...) VALUES (...)"
            )
    
    async def get_by_hash(self, token_hash: str) -> RefreshTokenRecord | None:
        # SELECT * FROM refresh_tokens WHERE token_hash = ?
        ...
    
    async def revoke_all_for_user(self, user_id: str) -> int:
        # UPDATE refresh_tokens SET revoked_at = NOW() WHERE user_id = ?
        ...
    
    # Реализуете остальные методы Protocol'а

Архитектура

authkit/
├── __init__.py              # Public API
├── exceptions.py            # AuthKitError, TokenError, ...
├── token/
│   ├── models.py            # TokenPair, TokenClaims
│   ├── jose_issuer.py       # JoseTokenIssuer
│   └── keys.py              # ensure_jwt_keypair
├── refresh/
│   └── models.py            # RefreshTokenRecord, IRefreshTokenStore
└── password/
    └── hasher.py            # Argon2PasswordHasher

Тестирование

pytest tests/                      # Все тесты
pytest tests/ -v --cov           # С coverage report
pytest tests/ -k "test_verify"   # Конкретный тест

Coverage gate: ≥ 80%.

Что НЕ входит (v0.1)

  • OAuth (YandexOAuth, ExchangeCode) — в v0.2+
  • PermissionResolver — остаются на стороне приложения
  • Redis cache invalidation — в v0.2+ как optional
  • Signing PK/SK rotation — будущая фича
  • Sync обёртки — только async API в v0.1

Интеграция в существующее приложение

Типовой путь перевода существующего приложения на модуль:

  1. Импортирует from authkit import ... вместо локального кода
  2. Обёрнет RefreshTokenRecord в ORM-адаптер для своей БД
  3. Оставит PermissionResolver/OAuth/ExchangeCode локально

Лицензия

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

s_authkit-0.1.0.tar.gz (26.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

s_authkit-0.1.0-py3-none-any.whl (15.2 kB view details)

Uploaded Python 3

File details

Details for the file s_authkit-0.1.0.tar.gz.

File metadata

  • Download URL: s_authkit-0.1.0.tar.gz
  • Upload date:
  • Size: 26.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.27 {"installer":{"name":"uv","version":"0.9.27","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for s_authkit-0.1.0.tar.gz
Algorithm Hash digest
SHA256 25423787fb0837850c124b1a5f04b50deac58ed88f7f30dfafeeca1f6d7d1f5e
MD5 3fa5546af71311ba88d5fb75a1af3a2f
BLAKE2b-256 9056d869ad990eacf6de9604a62c29c938aa72cc70c95ce854686ea2b90ae0e8

See more details on using hashes here.

File details

Details for the file s_authkit-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: s_authkit-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 15.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.27 {"installer":{"name":"uv","version":"0.9.27","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for s_authkit-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 55a4af1376774e7edf560fe3a3313f84455bd417fa7035157777682df24e6d36
MD5 706d5adddc42c99aa635017e183b0c57
BLAKE2b-256 36804c4d0b91105e0c2ddc22bab24b3bcea263daa71c11476018a079212c20f9

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page