Skip to content

feat(api): allow owners and admins to create an API key for another member - #4925

Open
airflow-ommax wants to merge 1 commit into
Dokploy:canaryfrom
airflow-ommax:feat/create-api-key-on-behalf-of-member
Open

feat(api): allow owners and admins to create an API key for another member#4925
airflow-ommax wants to merge 1 commit into
Dokploy:canaryfrom
airflow-ommax:feat/create-api-key-on-behalf-of-member

Conversation

@airflow-ommax

Copy link
Copy Markdown

Problem

user.createApiKey can only ever mint a key for the calling session's user: the service function already forwards a userId to better-auth, but the tRPC route hardcodes ctx.user.id.

That leaves automation with no way to bootstrap a per-user key. Anything that provisions users (SSO/SCIM-style onboarding, an internal developer platform handing each engineer their own credential) has to fall back to sharing a single admin key across everyone, which loses per-user attribution and per-user revocation, the two properties API keys exist for.

Change

createApiKey accepts an optional userId.

Minting for someone else is gated:

  • the caller must be an organization owner or admin
  • metadata.organizationId is required in that case
  • the target user must be a member of that organization

Omitting userId keeps the existing behaviour byte for byte, so this is backwards compatible and the UI is unchanged. The audit entry records when a key was created on behalf of another user.

Security

A key minted this way carries the target user's permissions, not the caller's, so the authorization decision is deliberately kept in one pure place (canCreateApiKeyForAnotherUser in lib/api-keys.ts, next to the existing api-key name validation) rather than inlined. It refuses an absent or empty role instead of defaulting to allowed, and it is case-sensitive.

Tests

Unit tests for the guard added alongside the existing apiKeyNameSchema tests in __test__/api/api-key-name.test.ts (owner/admin allowed, member refused, absent/empty role refused).

Note: I could not run the full suite locally without a complete monorepo install, so CI is the check for the rest.

Backwards compatibility

No migration, no schema change, no UI change. Existing callers that omit userId are unaffected.

…ember

createApiKey already forwards a userId to better-auth, but the tRPC route
hardcoded the caller, so a key could only ever be minted for the calling
session's user. Automation that provisions users (SSO/SCIM-style onboarding)
therefore had no way to bootstrap a per-user key, and the only workaround was
sharing one admin key across everyone, which loses attribution and per-user
revocation.

Accept an optional userId. Minting for someone else requires the caller to be
an organization owner or admin, requires metadata.organizationId, and verifies
the target is a member of that organization. Omitting userId keeps the previous
behaviour exactly, so this is backwards compatible. The audit entry records
that the key was created on behalf of another user.

The authorization decision is a pure helper in lib/api-keys.ts with unit tests,
next to the existing api-key name validation.
@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. enhancement New feature or request labels Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant