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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions