Skip to main content

bloonio_auth_relay_client

Backend SDK for the bloonio_auth relay (auth-relay.example.com).

Integrate push-based sudo approval (TOTP / golden number / biometric via the bloonio_auth authenticator app) into any Python backend in ~10 lines. Framework-agnostic core, thin FastAPI + Django adapters.

Install

pip install "bloonio-auth-relay-client[fastapi,redis]"
# or
pip install "bloonio-auth-relay-client[django,redis]"

Available extras: fastapi, django, redis, mongo, all.

Concepts

  • RelayClient / AsyncRelayClient — HMAC-signed HTTP wrapper around the relay. Sync version for Django, async for FastAPI; same surface.
  • SudoInstructionStore — short-lived (~180 s default). Tracks "this instruction passed sudo, here's the re-call window with X-Sudo-Instruction-Key". Default: Redis.
  • PendingOpStore — longer-lived (10–30 min). Holds the original mutation for server-side replay after approval — required for v2 group quorum + v3 cross-org modes. v1 single-actor doesn't use it but the interface ships now to avoid future API breakage.
  • @sudo_required — decorator. Same arguments work in FastAPI and Django.
  • DataType + ValidationField — structured display blocks the auth app renders (currency, IBAN, date, entity ref, etc.). Replaces unstructured description strings.

FastAPI quick start — pick the style that fits your codebase

Style A — declarative (greenfield, named routes)

For backends where each sudo-protected route is named and the developer knows up-front what fields to display:

from fastapi import FastAPI
from bloonio_auth_relay_client import DataType, SudoActionType, ValidationField
from bloonio_auth_relay_client.adapters.fastapi import BloonioAuthAdapter

app = FastAPI()
bloonio = BloonioAuthAdapter.from_env(app)   # reads BLOONIO_RELAY_* env

@app.post(
    "/transfer/execute",
    dependencies=[bloonio.sudo_required(
        expected_action="transfer_funds",
        custom_type=SudoActionType.LOCAL_AUTH,
        user_socket_hash=lambda req: req.state.user.socket_hash,
        title="Confirm wire transfer",
        fields=lambda req: [
            ValidationField(key="amount", title="Amount",
                            value=str(req.state.body["amount"]),
                            data_type=DataType.CURRENCY_USD),
            ValidationField(key="to", title="Recipient",
                            value=req.state.body["recipient"],
                            data_type=DataType.PARTY_NAME),
        ],
    )],
)
async def transfer_execute(...): ...

BloonioAuthAdapter.from_env(app) does all of:

  • builds RelaySettings from BLOONIO_RELAY_* env vars
  • builds an AsyncRelayClient
  • builds Redis-backed instruction + pending-op stores
  • mounts the dispatch middleware
  • mounts the auto submit-response router at /_bloonio/submit-response
  • mounts the relay callback router at settings.callback_path

Style B — RBAC-driven (dynamic / generic routes)

For backends where which-endpoint-needs-sudo is determined at runtime by querying RBAC config (e.g. routes like /generic/add/{collection_name}), pass a sudo_resolver callable. The resolver returns a SudoInfo describing what to challenge with — the SDK takes it from there.

from bloonio_auth_relay_client import (
    DataType, SudoActionType, SudoInfo, ValidationField, ValidationMode, Validator,
)
from bloonio_auth_relay_client.adapters.fastapi import BloonioAuthAdapter
from starlette.requests import Request

async def my_rbac_resolver(request: Request) -> SudoInfo | None:
    """Backend writes only this — the rest is the SDK."""
    rbac = await fetch_rbac_for_path(request.url.path)
    if not rbac or not rbac.is_sudo_action:
        return None

    user = request.state.user
    return SudoInfo(
        required=True,
        mode=ValidationMode.SINGLE_ACTOR,
        custom_type=pick_random_confirmation_type(rbac),
        expected_action=rbac.expected_action,
        description=rbac.totp_app_description_str,
        actor=Validator(
            socket_hash=user.user_account_socket_hash,
            display_name=f"{user.first_name} {user.last_name}",
        ),
        display_title="Confirm action",
        display_fields=build_fields_from_request(request),
    )

bloonio = BloonioAuthAdapter.from_env(app, sudo_resolver=my_rbac_resolver)

# All routes — including generic ones — are now sudo-aware.
# When required=True, the SDK:
#   • Creates an instruction_id
#   • Writes Redis state (180s TTL by default)
#   • Calls relay.send_auth_challenge(...) → FCM push
#   • Returns 403 with {error: "SUDO_INSTRUCTION_KEY_REQUIRED", instruction_id}
# When the device approves (POST /_bloonio/submit-response), state flips to
# "validated". The next call with X-Sudo-Instruction-Key passes through.

The two styles can coexist in the same app — declarative for explicit routes, resolver for catch-all ones.

Django quick start

# settings.py
INSTALLED_APPS = [..., "bloonio_auth_relay_client.adapters.django"]
MIDDLEWARE = [..., "bloonio_auth_relay_client.adapters.django.middleware.SudoActionMiddleware"]

BLOONIO_AUTH_RELAY = {
    "BASE_URL": "https://auth-relay.example.com",
    "TENANT_ID": os.environ["RELAY_TENANT_ID"],
    "TENANT_SECRET": os.environ["RELAY_TENANT_SECRET"],
    "STATE_BACKEND": "redis",
    "STATE_BACKEND_URL": os.environ["REDIS_URL"],
    "CALLBACK_PATH": "/api/sudo-callback/",
}
# urls.py
from bloonio_auth_relay_client.adapters.django import urls as relay_urls
urlpatterns = [..., path("", include(relay_urls))]
# views.py
from bloonio_auth_relay_client.adapters.django import sudo_required
from bloonio_auth_relay_client import DataType, SudoActionType, ValidationField

@sudo_required(
    expected_action="transfer_funds",
    custom_type=SudoActionType.LOCAL_AUTH,
    user_socket_hash=lambda req: req.user.socket_hash,
    title="Confirm wire transfer",
    fields=lambda req: [
        ValidationField(key="amount", title="Amount",
                        value=str(req.POST["amount"]),
                        data_type=DataType.CURRENCY_USD),
    ],
)
def transfer_execute(request):
    ...

Works on plain views, DRF @api_view, DRF APIView/ViewSet methods, and async views (Django 4.1+). Same decorator, same arguments — just req.user instead of req.state.user, req.POST instead of req.state.body.

Pairing handshake (called once per device pairing)

from bloonio_auth_relay_client import AsyncRelayClient, RelaySettings

relay = AsyncRelayClient(RelaySettings())

# In your QR pairing handler, after the user is authenticated:
result = await relay.prepare_pairing(
    user_socket_hash=user.socket_hash,
    backend_user_id=str(user.id),
    user_email=user.email,
    user_phone=user.phone,
    first_name=user.first_name,
    last_name=user.last_name,
    display_name="bloonio_apps_api",
    display_logo_url="https://cdn.example.com/logo.png",
)
pairing_proof = result["pairing_proof"]   # forward to device along with the rest of /auth/get-pairing-data

Two-call protocol (preserved from existing flow)

  1. Client POST /transfer/execute with no X-Sudo-Instruction-Key. → 403 {"error": "SUDO_INSTRUCTION_KEY_REQUIRED", "instruction_id": "abc..."}
  2. Device receives push → user approves → relay POSTs ApprovalEvent to your callback (HMAC-verified) → SudoInstructionStore marks instruction_id as validated.
  3. Client re-issues POST /transfer/execute with header X-Sudo-Instruction-Key: abc.... → request goes through.

v2 / v3 (deferred)

The decorator accepts mode=ValidationMode.GROUP_QUORUM etc. but raises NotImplementedError until v2/v3 land. The data-model and types are stable.

Configuration via env

All settings can be passed to RelaySettings(...) directly or sourced from env (BLOONIO_RELAY_BASE_URL, BLOONIO_RELAY_TENANT_ID, BLOONIO_RELAY_TENANT_SECRET, BLOONIO_RELAY_STATE_BACKEND_URL, etc.).

Download files

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

Source Distribution

bloonio_auth_relay_client-0.3.1.tar.gz (46.4 kB view details)

Uploaded Source

Built Distribution

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

bloonio_auth_relay_client-0.3.1-py3-none-any.whl (53.2 kB view details)

Uploaded Python 3

File details

Details for the file bloonio_auth_relay_client-0.3.1.tar.gz.

File metadata

File hashes

Hashes for bloonio_auth_relay_client-0.3.1.tar.gz
Algorithm Hash digest
SHA256 0b2a713a15572ba97df60b9c752cf2c45abafa1f74f7ec1bac4f18aac802fb18
MD5 e76a84cbb1c676004355cf3fd2831a83
BLAKE2b-256 539eac388df9819d0197b5528c395e9d29ba360c37d64f069fe69dc2afb5598e

See more details on using hashes here.

File details

Details for the file bloonio_auth_relay_client-0.3.1-py3-none-any.whl.

File metadata

File hashes

Hashes for bloonio_auth_relay_client-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 715cfe7b5466c5c2ac3240e8cb4fb2ffdb300b36390336ad6dcec1695798757c
MD5 1092160b28fc82397f5b5608944625f4
BLAKE2b-256 c3ae2108e6c7b7dec4e971a08db0e9db392b803dfb35ffb500b1ec9ece16f633

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 Sentry Error logging StatusPage Status page