Skip to content

Sudo (step-up)

Sudo (step-up / elevation) asks one or more human validators to approve a sensitive action from their Bloonio Authenticator app before the backend executes it. The relay exposes a dispatch primitive (HMAC #1 signed); the two-call protocol on the tenant side — 403 then re-call — is provided by the Python SDK.

  1. The client calls your protected route (e.g. POST /transfer/execute) without the X-Sudo-Instruction-Key header. → the SDK creates an instruction_id, triggers the dispatch + push, and returns 403 { "error": "SUDO_INSTRUCTION_KEY_REQUIRED", "instruction_id": "abc..." }.
  2. The device receives the push → the user approves → the relay POSTs the ApprovalEvent to your callback (HMAC-verified) → the store marks the instruction_id as validated.
  3. The client re-issues the same request with the X-Sudo-Instruction-Key: abc... header. → the request goes through.

POST /relay/sudo/dispatch creates a validation event and broadcasts the challenge pushes to the participants. The relay forwards it to auth_api and returns event_id, status, and expires_at.

Body validation rules:

  • event_type{ sudo_action, sudo_group_action, sudo_delegated_action }.
  • action_type{ creation, deletion, update, upsert }.
  • At least one of relay_user_linked_id_list (individual targets) or relay_group_linked_id_list (group targets) must be non-empty.
  • data_items is required when data_access_type=static; data_fetch_url when data_access_type=dynamic.
  • tenant_id is not in the body — the relay derives it from the authenticated context.
Fenêtre de terminal
curl $BASE_URL/api/v1/relay/sudo/dispatch \
-X POST \
-H "X-Bloonio-Tenant-Id: tnt_..." \
-H "X-Bloonio-Timestamp: 1718966400000" \
-H "X-Bloonio-Signature: <hex_hmac_sha256>" \
-H "Content-Type: application/json" \
-d '{
"event_type": "sudo_action",
"action_type": "update",
"idempotency_key": "idem_abc123",
"relay_user_linked_id_list": ["652f1f77bcf86cd799439011"],
"title": "Confirm the transfer",
"description": "Approve a transfer of 1,000 USD to ACME Corp.",
"data_access_type": "static",
"data_items": [
{ "display_title": "Amount", "display_value": "1000 USD", "data_type": "CURRENCY_USD" },
{ "display_title": "Beneficiary", "display_value": "ACME Corp", "data_type": "PARTY_NAME" }
],
"on_validate_callback_url": "https://api.example.com/relay-callbacks/sudo-validated",
"on_reject_callback_url": "https://api.example.com/relay-callbacks/sudo-rejected"
}'
{
"success": true,
"status_code": 201,
"message": "Dispatched.",
"data": { "event_id": "...", "status": "pending", "expires_at": "2026-06-21T..." }
}
  • relay_user_linked_id_list — opaque relay-side identifiers of individual validators (relay_user_id).
  • relay_group_linked_id_list — group identifiers; auth_api expands the membership and broadcasts the pushes to the members. The relay cannot expand groups itself (membership lives on auth_api).

The relay_user_id pivot is the universal validator: see Tenancy & relay_user_id.

To pass relay_user_linked_id / relay_group_linked_id to dispatch, resolve them first from the public identifiers. These GETs are pure pass-throughs (HMAC #1 inbound); auth_api enforces the tenant scope.

MethodPathRole
GET/relay/sudo/groupsList the validation groups visible to the tenant.
GET/relay/sudo/groups/by-public-idResolve a bgr-… to a relay_group_linked_id (?public_id_str=).
GET/relay/sudo/paired-usersList paired users (potential validators).
GET/relay/sudo/paired-users/by-public-idResolve a bth-… to a relay_user_linked_id (?public_id_str=).
GET/relay/sudo/pending-validationsPending validations for a user (?relay_user_linked_id=).
GET/relay/sudo/paired-users/signatureA user’s signature image (?relay_user_linked_id=).
Fenêtre de terminal
curl "$BASE_URL/api/v1/relay/sudo/groups/by-public-id?public_id_str=bgr-abc123" \
-H "X-Bloonio-Tenant-Id: tnt_..." \
-H "X-Bloonio-Timestamp: 1718966400000" \
-H "X-Bloonio-Signature: <hex_hmac_sha256>"

POST /relay/sudo/verify-totp validates the TOTP issued by Bloonio Authenticator for the pairing identified by relay_user_linked_id, without the tenant holding the per-user secret. Returns 200 on a match, 401 on a wrong / unknown code.

Fenêtre de terminal
curl $BASE_URL/api/v1/relay/sudo/verify-totp \
-X POST \
-H "X-Bloonio-Tenant-Id: tnt_..." \
-H "X-Bloonio-Timestamp: 1718966400000" \
-H "X-Bloonio-Signature: <hex_hmac_sha256>" \
-H "Content-Type: application/json" \
-d '{ "relay_user_linked_id": "652f1f77bcf86cd799439011", "totp": "123456" }'

In FastAPI, the decorator mounts the entire two-call protocol on the route:

@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 the transfer",
fields=lambda req: [
ValidationField(key="amount", title="Amount",
value=str(req.state.body["amount"]),
data_type=DataType.CURRENCY_USD),
],
)],
)
async def transfer_execute(...): ...

See the Python SDK for installation, configuration, and the Django equivalent.