Skip to content

[personal-service-auth] Service-key + X-On-Behalf-Of delegation for agents-side calls #38

@artugro

Description

@artugro

Context

Today wisdom-agents authenticates to wisdom via a user-scoped INTUNO_API_KEY — a key generated by a logged-in operator from /dashboard/integrations. That means every entity wisdom-agents hosts makes its network calls as that one user, breaking multi-tenant attribution:

  • User A's entity creates networks owned by user B (whoever made the key)
  • intuno_discover / network_send runs under a shared identity
  • Operator setup requires grabbing a personal key from a logged-in browser

This ticket introduces a proper service-to-service auth mode, mirroring the X-User-Id trust pattern we already use in the other direction (wisdom → wisdom-agents, IntunoAI/wisdom-agents#77).

Scope

1. New env setting

  • AGENTS_SERVICE_API_KEY: str = "" in src/core/settings.py — infra secret (e.g. a random 32-byte hex), not tied to any user, not stored in the database

2. New auth dependency

src/core/auth.py:

async def get_current_user_or_service(
    credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
    x_service_key: Optional[str] = Header(None, alias="X-Service-Key"),
    x_on_behalf_of: Optional[UUID] = Header(None, alias="X-On-Behalf-Of"),
    auth_service: AuthService = Depends(),
) -> User:
    """Accept either:
    - Normal user JWT / API key → returns that user, or
    - X-Service-Key matching AGENTS_SERVICE_API_KEY + X-On-Behalf-Of
      → returns the specified user (after verifying they're active)
    """

Rules:

  • Service key + no X-On-Behalf-Of → 400 (must delegate to somebody)
  • Wrong service key → 401 regardless of other headers
  • Service key + valid X-On-Behalf-Of → load and return the target user; 404 if target doesn't exist or is inactive

3. Apply to routes wisdom-agents calls

Audit the endpoints wisdom-agents hits via intuno-sdk and switch their dep from get_current_user to get_current_user_or_service:

  • /registry/discover, /registry/agents/*
  • /broker/invoke
  • /networks/* (create, list, participants, context, messages)
  • /a2a/agents/import (when wisdom-agents auto-imports A2A agents for an entity)

Legacy user-key auth keeps working — this is strictly additive.

Acceptance criteria

  • AGENTS_SERVICE_API_KEY setting wired
  • get_current_user_or_service dep added with tests:
    • rejects wrong service key (401)
    • rejects service key without X-On-Behalf-Of (400)
    • rejects unknown X-On-Behalf-Of uuid (404)
    • rejects inactive target user (401)
    • accepts valid service key + active user (returns correct User)
    • still accepts plain JWT / API key (backward compat)
  • One representative route per domain (networks, registry, broker) switched to the new dep and tested
  • No existing test breaks

Out of scope

  • Auditing every endpoint — just the ones wisdom-agents calls. Gradual rollout is fine.
  • Rate-limiting per service key (follows later with the quota work).

Relates to

  • IntunoAI/wisdom-agents#77 — the other half of the bridge (we already trust X-User-Id going the other way)
  • IntunoAI/wisdom-agents#56 — per-entity Intuno registration, unblocked by this
  • Paired with intuno-sdk SDK update + wisdom-agents refactor (companion tickets)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions