Skip to main content

Admin-machine surface and auto-admin for Intent API products.

Project description

Intent API Admin

Console-issued admin tokens and zero-boilerplate admin surfaces for any Intent API product.

intent-api-admin plugs into intent-api to add a dedicated admin-machine surface, Ed25519/JWKS-verified admin tokens, an automatic CRUD admin generator from your SQLAlchemy models, role-aware role gates, audit correlation, and a schema document the console can render directly.

Install

pip install intent-api-admin

Requires intent-api>=0.5.0 and Python 3.10+.

Three-step integration

from intent_api import IntentRouter
from intent_api_admin import (
    configure_admin_auth, auto_admin, admin_machine_auth,
)

configure_admin_auth(
    issuer="https://console.example.com",
    audience="myapp",
    base=Base,
)

router = IntentRouter()
router.register("User", UserService())
auto_admin(router, expose=["User", "Team"])

app.include_router(router.build(get_user=get_clerk_user, get_db=get_db))
app.include_router(router.build_admin_machine(get_user=admin_machine_auth(), get_db=get_db))

That is the entire integration. Console operators now get full read/write CRUD on every model in expose=[...], and your handlers are unchanged.

Impersonation (Clerk actor tokens)

Impersonation consumes Clerk's native actor tokens, minted per-product via the product's own Clerk SDK. The console never holds product Clerk credentials and never mints Clerk tokens itself.

The visual indicator is a UX hint, not a guarantee. Always confirm impersonation state via the console's session list before assuming the absence of an indicator means you're not impersonating. The security boundary is server-side.

Two-step integration

Step 1 — Mint. Add a User.create_clerk_actor_token custom action that calls clerk_sdk.actor_tokens.create(...). The full reference adapter is in docs/CLERK_ACTOR_TOKEN_ADAPTER.md.

Step 2 — Enforce. Call enforce_actor_writes at the top of any write handler, OR install actor_writes_middleware once in your runtime.

# Per-handler
from intent_api_admin import enforce_actor_writes

@custom_action(admin_only=True)
async def ban(self, *, db, user, context, id, payload, session_claims=None):
    enforce_actor_writes(session_claims or {}, action="custom",
                         is_read_only_custom_action=False)
    ...

# Or middleware (one-time install)
from intent_api_admin import actor_writes_middleware
runtime = build_runtime(middlewares=[actor_writes_middleware])

Audit context — one line in get_user

bind_impersonation_context populates acting_admin_id and impersonation_session_id as structlog contextvars so every log line in the handler inherits them. One line in your auth dependency:

from intent_api_admin import bind_impersonation_context

async def get_current_user(...):
    auth_state = clerk_sdk.authenticate_request(...)
    bind_impersonation_context(auth_state.payload)   # one-liner
    context.clerk_claims = auth_state.payload
    ...

CSP — frame-ancestors 'none'

The console always launches impersonation in a new tab via window.open(url, "_blank", "noopener,noreferrer"). Set CSP frame-ancestors 'none' on every product response so this architecture self-enforces — even if a future change tries to embed your product, browsers will refuse.

@app.middleware("http")
async def add_csp_headers(request, call_next):
    response = await call_next(request)
    response.headers["Content-Security-Policy"] = "frame-ancestors 'none'"
    return response

See docs/PRODUCT_ADOPTION.md for the full per-product checklist (~60 LOC) and SECURITY.md for the V1 threat model.

Roles

Three roles, ordered by rank:

Role Rank Default for
owner 3 delete on auto-admin services
admin 2 update, default for @custom_action(admin_only=True)
support 1 list, read

A higher-ranked role satisfies any lower-ranked requirement. So an owner token can perform any action that requires support, admin, or owner.

Heuristic defaults

auto_admin() reads each SQLAlchemy model and picks sensible defaults you almost never need to override.

  • list_fields — primary key + name / email / title / slug / status / created_at if present, then non-text columns up to 6 total.
  • search_fields — string columns with index=True or unique=True and length<=255.
  • default_sortcreated_at desc if present; otherwise <primary_key> desc.
  • Auto-redaction patterns — any column whose name starts with password, secret, api_key, token, or _, or ends with _hash / _digest. Redacted fields are stripped from responses and refused on update.

Override patterns

auto_admin(
    router,
    expose=["User", "Team", "Order"],
    redact_fields={"User": ["last_login_ip"]},          # add to auto-redaction
    deny_edit_fields={"User": ["email"]},                # read-only in admin
    deny_delete=["Order"],                                # disallow delete entirely
    overrides={                                           # surgical schema override
        "User": {"default_sort": "email asc",
                 "list_fields": ["id", "email", "name", "plan", "is_active"]},
    },
)

Custom admin actions

Use the augmented @custom_action from intent_api_admin for admin-only commands. They are dispatched on the admin-machine surface only — invisible to standard, MCP, machine, and the legacy admin route.

from intent_api_admin import custom_action

class UserService(IntentService):
    @custom_action(admin_only=True, min_role="admin")
    async def ban(self, *, db, user, context, id, payload):
        ...

    @custom_action(admin_only=True, min_role="support", read_only=True)
    async def view_audit(self, *, db, user, context, id, payload):
        ...

read_only=True is honored by enforce_actor_writes (see above) so the action remains callable under a read-only Clerk actor session.

Common errors and fixes

Code When Fix
ADMIN_AUTH_NOT_CONFIGURED auto_admin() called before configure_admin_auth() Call configure_admin_auth(...) first.
CONFIGURATION_ORDER_ERROR configure_admin_auth() called again after auto_admin() ran Reset only in tests; in prod call exactly once at startup.
MODEL_NOT_FOUND expose=["X"] but X isn't on Base.registry Make sure the model is imported before auto_admin().
WRONG_SURFACE Admin token on /api/intent Use /api/admin-machine-intent for admin tokens.
IMPERSONATION_WRITE_DENIED A write ran under a read-only Clerk actor session Mint the actor token with allow_writes=true, or mark the action read_only=True.
INSUFFICIENT_ROLE Token role rank is below the action's min_role Issue a token with the required role.
FIELD_REDACTED Update payload references a redacted field Drop the field from payload, or remove from redact_fields.
FIELD_READ_ONLY Update payload references a deny_edit_fields field Drop the field, or remove the deny entry.
DELETE_DENIED Delete on a deny_delete model Remove the model from deny_delete=[...].
UNSUPPORTED_FILTER_OP List filter uses an op the auto-admin filter parser doesn't support Use one of eq, ne, lt, lte, gt, gte, contains, in, is_null.

See TOKEN_CONTRACT.md for the full token spec.

Reference test suite

tests/wbs_reference/ is an exhaustive contract suite that mirrors the WBS success criteria S1–S52. Copy it into your repo to validate every adoption.

Project details


Download files

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

Source Distribution

intent_api_admin-0.2.0.tar.gz (52.4 kB view details)

Uploaded Source

Built Distribution

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

intent_api_admin-0.2.0-py3-none-any.whl (37.4 kB view details)

Uploaded Python 3

File details

Details for the file intent_api_admin-0.2.0.tar.gz.

File metadata

  • Download URL: intent_api_admin-0.2.0.tar.gz
  • Upload date:
  • Size: 52.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.13

File hashes

Hashes for intent_api_admin-0.2.0.tar.gz
Algorithm Hash digest
SHA256 0a7127fd34d8fc77a8b75ec53c84705fe5cf3ef1e0ac23ed5e01fbb090faf257
MD5 b475be3661463d58a1c29f71198b5c65
BLAKE2b-256 9631ccee81f19867479c07016400ad969dbe5a65d5f9315d6497fff466c82e35

See more details on using hashes here.

File details

Details for the file intent_api_admin-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for intent_api_admin-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e20dd46a87681c1340a95de5c49b222c6af728070562be4325e0147aebd150b6
MD5 0676d171c41f5040484495517dae25e4
BLAKE2b-256 a64655c0fb6178669167445728f48c476e8b93427f8f38f326794bd5378ba3bf

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