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

Metadata

Release files for omnixys-security 4.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 omnixys-security 4.1.0
File Size Uploaded
omnixys_security-4.1.0.tar.gz 76.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for omnixys-security 4.1.0
File Interpreter ABI Platform
omnixys_security-4.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 86.9 kB

Release files / omnixys_security-4.1.0.tar.gz

Download URL omnixys_security-4.1.0.tar.gz
Size 76.7 kB
Tags Source
SHA-256 checksum
How to use checksums
5041f341324fdcdc3dbc1992fb94568d02e8732344f8c7a86a84cc6dec194ff1
BLAKE2b-256 checksum
How to use checksums
730eaf11aa3b444f9faad370d807c28f2417ad0ba86a197f5478ec52d1415d86
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","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}

Release files / omnixys_security-4.1.0-py3-none-any.whl

Download URL omnixys_security-4.1.0-py3-none-any.whl
Size 10.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1ca63535e2d7118d294fda846ea32a84be8d523e844561773a6811638f32452a
BLAKE2b-256 checksum
How to use checksums
f20c7247d955001ef15587f3bf3c6b9c56d0fd0a9b85a17c1f259a2fa1ea8988
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","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}

Release history Release notifications | RSS feed

5.0.1

2 release files

This release

4.1.0 This release

2 release files

4.0.0

2 release files

3.0.0

2 release files

2.0.3

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