Skip to content

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

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

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
  • Adds a new POST /api/v1/auth/token REST endpoint that exchanges either a flow_sa_-prefixed API key or a refresh token for a short-lived access token, selected by the grant_type field
  • Also exposes refresh-token management over GraphQL (refreshTokens query, createRefreshToken / deleteRefreshToken mutations), self-scoped to the authenticated user

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. Created from the SA's stored prefix/capability. Disabling deletes the grant and all API keys; re-enabling restores the grant (but not the keys — new ones must be minted).
  • API keys are verified and exchanged entirely in the application layer, not via generate_access_token. The flow_sa_-prefixed key encodes base64(key_id:secret); the endpoint decodes it, verifies the secret against a bcrypt hash using SQL crypt() (no Rust bcrypt dependency), checks expiry and disabled_at, then mints a 1-hour authenticated JWT directly. The flow_sa_ prefix routes the request and avoids collisions with refresh-token inputs.
  • API keys use fixed expiry (valid_for ISO-8601 duration → now() + interval), not the sliding window used by refresh tokens.
  • generate_access_token is unchanged. The refresh-token branch of /api/v1/auth/token calls the existing generate_access_token(refresh_token_id, secret) and passes its JSON result through. Existing callers are unaffected.

Test plan

  • Create service account → create API key → exchange for access token via /api/v1/auth/token
  • Authorization checks (Bob can't manage or list Alice's service accounts)
  • Revoke API key → exchange fails
  • Disable service account → API keys deleted, grant removed, exchange fails, new key creation blocked
  • Re-enable service account → grant restored, new keys can be created, exchange works
  • Idempotency guards (disable-while-disabled and enable-while-enabled both error)
  • List query scoped to the caller's admin prefixes

@GregorShear GregorShear marked this pull request as draft May 13, 2026 22:49
@GregorShear GregorShear force-pushed the greg/service-accounts-phase1 branch 2 times, most recently from 044861d to 899d324 Compare May 19, 2026 22:22
Comment thread .gitignore

.claude/*
!.claude/skills/
mise.local.toml
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not related to this PR but helping me not commit a file related to a parallel workstream...

@GregorShear GregorShear force-pushed the greg/service-accounts-phase1 branch from 899d324 to 3c9246f Compare May 20, 2026 18:21
@GregorShear GregorShear force-pushed the greg/service-accounts-phase1 branch 2 times, most recently from b07c268 to 66e5620 Compare May 29, 2026 01:20
@GregorShear GregorShear requested a review from jshearer May 29, 2026 01:20
@GregorShear GregorShear marked this pull request as ready for review May 29, 2026 01:20
@GregorShear GregorShear force-pushed the greg/service-accounts-phase1 branch 2 times, most recently from 0917cc1 to 312d85f Compare May 29, 2026 03:01
@GregorShear GregorShear force-pushed the greg/service-accounts-phase1 branch from 312d85f to dc5e4cd Compare May 29, 2026 03:33
…h (and refresh tokens).

also a new token exchange rest api
@GregorShear GregorShear force-pushed the greg/service-accounts-phase1 branch from dc5e4cd to dbbeaeb Compare May 29, 2026 04:21
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.

2 participants