Skip to content

service accounts: Phase 1 — backend CRUD and API key token exchange#2943

Draft
GregorShear wants to merge 1 commit into
masterfrom
greg/service-accounts-phase1
Draft

service accounts: Phase 1 — backend CRUD and API key token exchange#2943
GregorShear wants to merge 1 commit into
masterfrom
greg/service-accounts-phase1

Conversation

@GregorShear
Copy link
Copy Markdown
Contributor

@GregorShear GregorShear commented May 13, 2026

Summary

  • Adds internal.service_accounts and internal.api_keys tables for non-human identities that authenticate via API keys and are authorized through the existing user_grants / role_grants system
  • Implements GraphQL mutations (createServiceAccount, disableServiceAccount, enableServiceAccount, createApiKey, revokeApiKey) and a paginated serviceAccounts query, all gated on admin capability of the service account's prefix
  • Extends generate_access_token to accept a flow_sa_-prefixed API key alongside the existing refresh token path — the two input shapes are mutually exclusive, and existing callers are unaffected

Phase 1 of the service accounts plan. Unlocks Phases 2–7 (management UI, OIDC, flowctl integration).

Key design decisions

  • Service accounts are auth.users rows. All existing RLS policies, PostgREST authorization, user_roles() resolution, and role_grants traversal work unchanged. This avoids putting the PostgREST→GraphQL migration on the critical path.
  • One user_grants row per service account. Disabling deletes the grant and all API keys; re-enabling restores the grant from the service account's stored prefix/capability.
  • API keys use fixed expiry (not sliding window like refresh tokens). The flow_sa_ prefix routes server-side lookups to internal.api_keys and prevents collisions with base64-encoded refresh tokens.
  • generate_access_token drops the old 2-arg signature and replaces it with a 3-arg version (all with defaults). Existing positional and named callers continue to work unchanged.

Test plan

  • CI build passes (native deps not available locally — jemalloc/rocksdb are in the build VM)
  • sqlx prepare — new queries need to be added to the offline cache in CI
  • Integration test test_service_account_lifecycle covers:
    • Create service account → create API key → exchange for access token
    • Authorization checks (Bob can't manage Alice's service accounts)
    • Revoke API key → exchange fails
    • Disable service account → API keys revoked, grants removed, new key creation blocked
    • Re-enable service account → new keys can be created, exchange works
    • Idempotency guards (disable-while-disabled and enable-while-enabled both error)
    • List query scoped to admin prefixes

Adds service account lifecycle management via GraphQL and API key authentication
via the existing generate_access_token RPC.

Data model:
- internal.service_accounts: non-human identities keyed by auth.users.id, with
  owning prefix, capability, and disabled state
- internal.api_keys: long-lived credentials (bcrypt-hashed) exchanged for
  short-lived JWTs, with fixed expiry (no sliding window)

GraphQL mutations (all require admin on the service account's prefix):
- createServiceAccount: creates auth.users + service_accounts + user_grants rows
- disableServiceAccount: revokes all API keys and grants, sets disabled_at
- enableServiceAccount: clears disabled_at, restores the user_grants row
- createApiKey: mints a flow_sa_-prefixed secret (returned once, stored as hash)
- revokeApiKey: deletes the API key row

GraphQL query:
- serviceAccounts: paginated list scoped to caller's admin prefixes, includes
  nested apiKeys (without secrets)

Token exchange:
- generate_access_token gains an optional api_key parameter alongside the existing
  refresh_token_id + secret path. The two shapes are mutually exclusive. Existing
  callers (positional or named) are unaffected since the new parameter defaults to null.

Phase 1 of the service accounts plan (#2857).
@GregorShear GregorShear marked this pull request as draft May 13, 2026 22:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant