Skip to content

feat(authz): wrap Permit behind a FuzeFront authorization API (consumer products must not call the PDP/Permit cloud directly) #254

Description

@izzywdev

Why

FuzeFront's design intent is that Authentik, Permit, and Stripe are internal implementation details wrapped by FuzeFront's own APIs — consumer products integrate with FuzeFront, not with the vendors. Today that holds for Stripe (billing-service wraps it; the Mendys datasets marketplace checks out via /api/v1/billing/*, holds no Stripe keys) and is being brought into line for Authentik (auth broker, #238 + MendysRobotics#215). Permit is the remaining leak: the datasets marketplace currently talks to Permit directly — its PermitClient calls the PDP sidecar (POST {PERMIT_PDP_URL}/allowed) for checks and the Permit cloud API (api.permit.io/v2/facts/...) for user sync + role assignment, using a shared PERMIT_API_KEY. That exposes the vendor, the PDP topology, and a broad cloud key to every product.

Proposed shape (to be frozen by contract-designer, contract-first)

A FuzeFront authorization API (in security-service, alongside the auth broker) that fronts Permit for family products:

  • POST /api/v1/authz/check{ allowed: boolean } — body { productKey, resource, action, resourceKey?, tenant? }; actor resolved from the FuzeFront session/token (same trusted-actor model as billing-service), never a raw user id from the caller. FuzeFront namespaces to <product>_<Resource> internally (the PRODUCT_NS_SEP convention from fix(permit): product-namespace separator '.' → '_' (Permit rejects dots) #237) so products pass bare resource/action.
  • POST /api/v1/authz/roles:assign (+ :revoke) — assign a product role to a user in a tenant; product-scoped, authorized by the internal token, so a product can only touch its own <productKey>_* roles.
  • Optional POST /api/v1/authz/users:sync (or fold identity sync into the auth-broker callback so a separate call isn't needed).
  • Auth: BILLING_INTERNAL_TOKEN-style per-consumer internal token + trusted actor headers (reuse the billing-service middleware pattern). Products never receive PERMIT_API_KEY or the PDP URL.
  • Error surface + fail-closed semantics defined in the contract (a PDP/vendor error is a deny).
  • Generate a @fuzefront/authz-client package (mirrors @fuzefront/billing-client).

Scope notes

  • Contract-first: freeze the OpenAPI before implementation; the product policy registration (resources/roles per product, e.g. mendys-datasets) stays server-side as it is today (backend/src/permit/products/*.policy.ts) — this issue is about the runtime check/assign surface, not schema sync.
  • Keep the direct-PDP path working until the wrapped API is proven, so nothing regresses.
  • Consumer-side migration for the marketplace is tracked in the companion MendysRobotics issue (linked below).
  • Sequencing: parallels feat(auth): reusable themed sign-in/up for consumer products (marketplace token handoff) #238 (auth broker). Both are the "FuzeFront wraps its vendors" cleanup; can share the internal-token + trusted-actor plumbing.

Acceptance criteria

  • A consumer product can make an allow/deny decision and assign a product role without any Permit SDK, PERMIT_API_KEY, or PDP URL in its own config.
  • Decisions match today's direct-PDP behavior for the mendys-datasets policy (parity test), and fail closed on vendor error.
  • @fuzefront/authz-client published; contract lint + tests green.

Companion (consumer side): MendysRobotics issue linked below. Related: #238 (auth broker), billing-service (the existing wrap-the-vendor precedent).

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions