Skip to main content

omnixys-security

Shared security toolkit for Omnixys services: JWT validation against a JWKS endpoint, pluggable ASGI middleware, per-request context, rate limiting, and timing-safe Argon2id password hashing.

Installation

pip install omnixys-security

Features

  • JwtValidator — validates RS256 JWTs against a remote JWKS with in-memory caching, issuer and audience checks.
  • SecurityMiddleware — ASGI middleware that resolves the request context from a bearer token or internal API key.
  • RequestContext — typed, context-local request metadata (user, roles, tenant, request/correlation id, client IP).
  • RateLimiter / RateLimitMiddleware — fixed-window Redis-backed rate limiting by IP, user, and tenant.
  • HashService — Argon2id password hashing with optional pepper and timing-safe dummy_verify.
  • SecurityError hierarchy — typed exceptions with stable codes for authz failures, expired tokens, tenant errors.

Quick start

from security import JwtValidator, SecurityMiddleware, RateLimitMiddleware, RateLimiter

jwt_validator = JwtValidator(
    jwks_url="https://auth.omnixys.com/.well-known/jwks.json",
    issuer="https://auth.omnixys.com/realms/omnixys",
    audience="omnixys-api",
)

redis = redis.asyncio.from_url("redis://localhost:6379")
limiter = RateLimiter(redis, default_limit=120, default_window_ms=60000)

app = SecurityMiddleware(
    app=my_asgi_app,
    jwt_validator=jwt_validator,
    internal_api_key=os.getenv("INTERNAL_API_KEY"),
)
app = RateLimitMiddleware(app=app, limiter=limiter)

Request context

Within a handled request you can read the resolved identity at any point:

from security import current_request_context

async def handler():
    ctx = current_request_context()
    if ctx.user_id and "admin" in ctx.roles:
        await do_admin_thing(ctx.tenant_id)
    else:
        raise AccessDeniedError()

RequestContext carries user_id, username, email, first_name, last_name, roles, scopes, tenant_ids, tenant_id (pinned via the x-tenant-id header when it is contained in the token), correlation_id, request_id, client_ip, is_authenticated, and is_internal.

The middleware always resets the context after the request, so stale identity never leaks between requests.

JWT validation

from security import JwtValidator

validator = JwtValidator(
    jwks_url="...",
    issuer="...",
    audience="...",
    cache_ttl_seconds=900,
)

claims = await validator.validate(token)
assert claims.user_id == claims.sub
assert claims.roles == ["admin"]

validate raises ValueError when the token is invalid, expired, from a different issuer, or signed by an unknown key. JWKS responses are cached for cache_ttl_seconds to avoid a fetch per request.

Rate limiting

from security import RateLimiter, RateLimitMiddleware

limiter = RateLimiter(redis, default_limit=120, default_window_ms=60000)

async def view():
    if not await limiter.is_allowed(await limiter.user_key(ctx.user_id)):
        return json_response({"error": "rate_limit_exceeded"}, status=429)

RateLimitMiddleware checks IP, then user, then tenant (in that order) and responds 429 {"error":"rate_limit_exceeded"} when a limit is hit. Paths such as /health and /metrics are excluded by default and never limited.

Password hashing

from security import HashOptions, HashService

service = HashService(HashOptions(pepper=os.getenv("PEPPER", "")))

stored = service.hash(password)          # $argon2id$...
ok = service.verify(stored, password)    # never raises
needs_upgrade = service.needs_rehash(stored)

# call for unknown users to equalize timing with known-user lookups
service.dummy_verify()

Errors

All domain errors subclass SecurityError and carry a stable code:

from security import AccessDeniedError, InvalidCredentialsError

try:
    authorize(ctx, "events.write")
except AccessDeniedError as exc:
    print(exc.code, exc.to_dict())

The hierarchy includes TokenInvalidError, InvalidCredentialsError, RefreshTokenExpiredError, AccessDeniedError, EventAccessDeniedError, TenantNotFoundError, TenantDisabledError, TenantMembershipNotFoundError, and TenantMembershipInactiveError.

Development

uv sync
uv run pytest -q
uv run ruff check .
uv run mypy src/

License

GPL-3.0-or-later

Download files

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

Source Distribution

omnixys_security-4.0.0.tar.gz (53.7 kB view details)

Uploaded Source

Built Distribution

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

omnixys_security-4.0.0-py3-none-any.whl (9.8 kB view details)

Uploaded Python 3

File details

Details for the file omnixys_security-4.0.0.tar.gz.

File metadata

  • Download URL: omnixys_security-4.0.0.tar.gz
  • Upload date:
  • Size: 53.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for omnixys_security-4.0.0.tar.gz
Algorithm Hash digest
SHA256 703bca6008e571e74f76a79c8e3785e15aa05576ad56f426e8fb4080fa100676
MD5 65e2316885ed870173e13cdc9eb751c9
BLAKE2b-256 4f74b601488ed927f3d05dd69835ce6911ff1056c8d47fdd1af1f0fd6f1a13fe

See more details on using hashes here.

File details

Details for the file omnixys_security-4.0.0-py3-none-any.whl.

File metadata

  • Download URL: omnixys_security-4.0.0-py3-none-any.whl
  • Upload date:
  • Size: 9.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for omnixys_security-4.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8a4d92340eb5042416859b2bd62305433b756903c312d1801bb7a50e1f9a2069
MD5 f65db2c16b30d400ffa18ff0fc67861b
BLAKE2b-256 ce26e95592756a9a6689e93066ff5c0434a49df907146f24dcd6ced6b52aa058

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

4.0.0 This release

2 files

3.0.0

2 files

2.0.3

2 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