SDK Python
bloonio_auth_relay_client est le SDK backend de l’Auth Relay. Il enveloppe la
signature HMAC #1, le protocole sudo en deux appels, le handshake d’appairage
et les callbacks — un cœur agnostique du framework, plus de fins adaptateurs
FastAPI et Django.
Installation
Section intitulée « Installation »pip install "bloonio-auth-relay-client[fastapi,redis]"# oupip install "bloonio-auth-relay-client[django,redis]"Extras disponibles : fastapi, django, redis, mongo, all.
Concepts
Section intitulée « Concepts »RelayClient/AsyncRelayClient— wrapper HTTP signé HMAC autour du relais. Version synchrone pour Django, asynchrone pour FastAPI ; même surface.SudoInstructionStore— store court (~180 s) qui suit « cette instruction a passé le sudo, voici la fenêtre de re-appel avecX-Sudo-Instruction-Key». Backend par défaut : Redis.PendingOpStore— store plus long (10–30 min) qui conserve la mutation d’origine pour rejeu côté serveur (requis pour le quorum de groupe v2 / le cross-org v3).@sudo_required— le décorateur ; mêmes arguments en FastAPI et Django.DataType+ValidationField— blocs d’affichage structurés que l’app authentificateur rend (devise, IBAN, date, référence d’entité, etc.).
Configuration par l’environnement
Section intitulée « Configuration par l’environnement »RelaySettings lit le préfixe BLOONIO_RELAY_. Les clés essentielles :
| Variable | Rôle |
|---|---|
BLOONIO_RELAY_BASE_URL | URL de base du relais, ex. https://auth-relay.bloonio.com. |
BLOONIO_RELAY_TENANT_ID | Le tenant_id du tenant appelant. |
BLOONIO_RELAY_TENANT_SECRET | Le tenant_secret (signature HMAC #1). |
BLOONIO_RELAY_STATE_BACKEND | redis (défaut) ou memory. |
BLOONIO_RELAY_STATE_BACKEND_URL | URL Redis quand state_backend=redis. |
BLOONIO_RELAY_CALLBACK_PATH | Chemin où le relais POST les ApprovalEvent (défaut /api/sudo-callback). |
BLOONIO_RELAY_BASE_URL=https://auth-relay.bloonio.comBLOONIO_RELAY_TENANT_ID=tnt_...BLOONIO_RELAY_TENANT_SECRET=sk_...BLOONIO_RELAY_STATE_BACKEND=redisBLOONIO_RELAY_STATE_BACKEND_URL=redis://localhost:6379/0BloonioAuthAdapter.from_env(app) construit RelaySettings depuis
l’environnement, instancie un AsyncRelayClient, monte le middleware de
dispatch, le routeur d’auto-réponse (/_bloonio/submit-response) et le routeur
de callback.
Pour des routes nommées où l’on sait à l’avance quels champs afficher :
from fastapi import FastAPIfrom bloonio_auth_relay_client import DataType, SudoActionType, ValidationFieldfrom bloonio_auth_relay_client.adapters.fastapi import BloonioAuthAdapter
app = FastAPI()bloonio = BloonioAuthAdapter.from_env(app) # lit les BLOONIO_RELAY_*
@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="Confirmer le virement", fields=lambda req: [ ValidationField(key="amount", title="Montant", value=str(req.state.body["amount"]), data_type=DataType.CURRENCY_USD), ValidationField(key="to", title="Bénéficiaire", value=req.state.body["recipient"], data_type=DataType.PARTY_NAME), ], )],)async def transfer_execute(...): ...Pour des routes génériques où le besoin de sudo est déterminé au runtime,
passez un sudo_resolver qui renvoie un SudoInfo (ou None) :
from bloonio_auth_relay_client import ( SudoInfo, ValidationMode, Validator,)from bloonio_auth_relay_client.adapters.fastapi import BloonioAuthAdapterfrom starlette.requests import Request
async def my_rbac_resolver(request: Request) -> SudoInfo | None: 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="Confirmer l'action", display_fields=build_fields_from_request(request), )
bloonio = BloonioAuthAdapter.from_env(app, sudo_resolver=my_rbac_resolver)Quand le sudo est requis, le SDK crée un instruction_id, écrit l’état Redis,
appelle send_auth_challenge (push FCM), et renvoie 403 {error: "SUDO_INSTRUCTION_KEY_REQUIRED", instruction_id}. À l’approbation, l’état
bascule en validated et le re-appel avec X-Sudo-Instruction-Key passe. Voir
Sudo (élévation).
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.bloonio.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/",}from bloonio_auth_relay_client.adapters.django import urls as relay_urlsurlpatterns = [..., path("", include(relay_urls))]from bloonio_auth_relay_client.adapters.django import sudo_requiredfrom 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="Confirmer le virement", fields=lambda req: [ ValidationField(key="amount", title="Montant", value=str(req.POST["amount"]), data_type=DataType.CURRENCY_USD), ],)def transfer_execute(request): ...Fonctionne sur les vues simples, @api_view et APIView/ViewSet de DRF, et
les vues asynchrones (Django 4.1+). Même décorateur, mêmes arguments — juste
req.user au lieu de req.state.user et req.POST au lieu de
req.state.body.
Handshake d’appairage
Section intitulée « Handshake d’appairage »Appelé une fois par appairage d’appareil, depuis votre handler de QR après authentification de l’utilisateur :
from bloonio_auth_relay_client import AsyncRelayClient, RelaySettings
relay = AsyncRelayClient(RelaySettings())
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="Example", display_logo_url="https://example.com/logo.png",)pairing_proof = result["pairing_proof"] # à transmettre à l'appareilVoir Appairage & appareils pour la suite du flux côté appareil.