Skip to content

feat(platform): add API key authentication and management#366

Merged
larryro merged 14 commits into
mainfrom
feat/319-api-key-authentication
Feb 5, 2026
Merged

feat(platform): add API key authentication and management#366
larryro merged 14 commits into
mainfrom
feat/319-api-key-authentication

Conversation

@larryro
Copy link
Copy Markdown
Collaborator

@larryro larryro commented Feb 5, 2026

Summary

  • Add API gateway with support for x-api-key header and session cookie authentication
  • Create API keys settings page with create, view, and revoke functionality
  • Add OpenAPI spec generation with Scalar docs viewer at /docs
  • Patch convex-helpers for API key bearer token support

Test plan

  • Verify API key creation flow works correctly
  • Test API key revocation
  • Confirm API gateway authenticates requests with x-api-key header
  • Verify session cookie fallback authentication works
  • Check OpenAPI docs render correctly at /docs

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • API Keys: Create, manage, and revoke API keys with customizable expiration periods from organization settings for external integrations
    • API Documentation: Interactive documentation page with Swagger UI for discovering, testing, and exploring API endpoints with authentication support
  • Documentation
    • Added comprehensive localization for new API Keys and API Documentation features

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 5, 2026

📝 Walkthrough

Walkthrough

This PR implements API key management functionality for the platform. It adds backend support via a Convex API gateway, Better Auth API key plugin integration, and database schema; creates frontend components and hooks for API key creation, display, and revocation; integrates API documentation with Swagger UI; updates routing and navigation; and includes supporting infrastructure such as OpenAPI specification generation, translation strings, and configuration updates. The changes span authentication, HTTP routing, database schema, UI components, hooks, and build tooling.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • talecorp/poc2#166: Both PRs modify the Better Auth integration in services/platform/convex/auth.ts, adding and configuring auth plugins (apiKey plugin configuration, plugin list updates).
  • feat(platform): add Entra ID SSO with auto-provisioning #354: Both PRs modify Convex auth and HTTP routing in services/platform/convex/auth.ts and services/platform/convex/http.ts, extending authentication and request handling capabilities.
  • talecorp/poc2#27: Both PRs extend authentication and authorization code paths by modifying auth configuration, client plugins, and permission/role-based access control logic.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🤖 Fix all issues with AI agents
In
`@services/platform/app/features/settings/api-keys/components/api-key-create-dialog.tsx`:
- Around line 52-58: The schema's name validation currently uses
z.string().min(1, nameRequiredError).transform(...) which runs min before
trimming and allows whitespace-only names; update the name schema inside the
useMemo-created schema (symbol: schema) to use z.string().trim().min(1,
nameRequiredError) instead of the current transform so trimming happens before
the min check (remove the separate transform for name).

In `@services/platform/app/features/settings/api-keys/hooks/use-api-keys.ts`:
- Around line 31-46: The mutationFn in use-api-keys.ts currently returns empty
strings when authClient.apiKey.create succeeds but returns no key/id; instead
detect when result.data?.key or result.data?.id is missing and throw an Error
(or reject) with a clear message so the UI doesn't treat this as success. Update
the mutationFn used by useMutation (the async ({ name, expiresIn }:
CreateApiKeyParams): Promise<CreateApiKeyResult> block) to validate
result.data.key and result.data.id after checking result.error and throw a
descriptive error (e.g., "API key creation returned missing key or id") rather
than returning fallback empty strings.
- Around line 15-25: The hook useApiKeys currently uses a static queryKey
['api-keys'], causing stale caches across organization switches; change
useApiKeys to accept an organizationId parameter and include it in the queryKey
(e.g., ['api-keys', organizationId]) and pass organizationId into the queryFn
that calls authClient.apiKey.list() so results are scoped per org, then update
useCreateApiKey and useRevokeApiKey to accept organizationId (or receive it
where they already exist) and invalidate the same scoped queryKey (['api-keys',
organizationId]) after creating or revoking keys so React Query refetches the
correct org-scoped data.

In `@services/platform/app/features/settings/api-keys/types.ts`:
- Around line 1-11: The ApiKey interface has two similar fields, start and
prefix, causing ambiguous usage (see table usage row.original.start ||
row.original.prefix); clarify their roles by either consolidating them into a
single field (e.g., keep only prefix and migrate usages) or adding concise JSDoc
comments for ApiKey.start and ApiKey.prefix that explain their exact meaning and
precedence, then update all consumers (notably the table rendering logic) to use
the canonical field or explicit fallback order; ensure any
database/serialization mappings and tests are adjusted to match the chosen
change.

In `@services/platform/app/routes/dashboard/`$id/settings/api-keys.tsx:
- Around line 52-60: The check that shows ApiKeysSettingsSkeleton treats both
undefined and null the same; update the conditional so only undefined (loading)
returns ApiKeysSettingsSkeleton and allow null (resolved no-membership) to
continue to access-control logic—i.e., change the test on memberContext in the
component that reads getCurrentMemberContext/memberContext so null falls through
and the userRole/hasAccess logic (userRole = (memberContext?.role ??
'').toLowerCase(); hasAccess = userRole === 'admin' || userRole === 'developer')
will cause AccessDenied to render for null membership instead of the skeleton.

In `@services/platform/app/routes/docs.tsx`:
- Around line 28-57: The code currently patches window.fetch globally in
ApiDocsPage (patchedFetch/Object.assign) which causes side effects; instead
remove the global patch and add a scoped request interceptor to the
swaggerConfig used by Swagger UI (e.g., set requestInterceptor or the equivalent
option your swagger-ui-react version supports) that inspects the request URL and
ensures credentials: 'include' for '/api/' calls; drop the patchedFetch logic
and cleanup, and keep only the requestInterceptor implementation referenced from
swaggerConfig so the credential behavior is limited to the docs UI.

In `@services/platform/convex/api_gateway.ts`:
- Around line 9-16: Replace the current permissive behavior in getCorsHeaders by
validating request.headers.get('origin') against a configured allowlist (e.g.,
an env var like ALLOWED_ORIGINS parsed into a Set) and only echoing the origin
and setting 'Access-Control-Allow-Credentials': 'true' when it matches;
otherwise set a safe default (e.g., no credentials and either omit
Access-Control-Allow-Origin or set it to 'null' or a single trusted domain).
Implement the check inside getCorsHeaders so in production the origin is
validated before returning the CORS headers, and ensure the allowlist is loaded
once (cache the Set) and used by getCorsHeaders.
- Around line 81-99: The upstream fetch to CONVEX_URL needs an
AbortController-based timeout to avoid hanging; create an AbortController, pass
controller.signal into the fetch call (fetch(`${CONVEX_URL}${path}`, { ...,
signal: controller.signal })), start a timer (e.g., TIMEOUT_MS constant) that
calls controller.abort() on expiry, and clear the timer after fetch completes;
also handle the abort case in the catch block to return an appropriate 504
Response using getCorsHeaders(request) (and include Content-Type like the
existing response path), ensuring you still use jwtToken, body, and Response as
in the current code.

In `@services/platform/convex/workflows/executions/complete_execution.ts`:
- Around line 44-56: The current blanket catch around
ctx.storage.delete(oldVariablesStorageId) can hide real failures; change it to
catch (err) and detect "not found" semantics (e.g. err.code === 'NotFound' ||
err.statusCode === 404 || /not.?found/i.test(err?.message)) and silently ignore
only in that case, otherwise emit a warning (use an existing logger like
ctx.logger.warn(...) or console.warn) including err and context
(oldVariablesStorageId, updates.variablesStorageId) so unexpected
permission/outage errors are observable; keep the delete attempt inside the same
shouldDelete block around oldVariablesStorageId and updates.variablesStorageId.

In `@services/platform/scripts/generate-openapi.ts`:
- Around line 44-48: The execSync call that runs `npx convex-helpers
open-api-spec --output-file ${tempYamlPath}` should be replaced with
execFileSync (or spawnSync) to pass the command and each argument separately
(e.g., ['convex-helpers','open-api-spec','--output-file', tempYamlPath]) and
preserve the cwd (platformDir) and stdio options so paths with spaces are
handled safely; also replace any shell cleanup using `rm -f` (the cleanup around
lines referencing tempYamlPath at 123-124) with Node fs APIs (fs.rmSync or
fs.unlinkSync with a check like fs.existsSync) to perform cross-platform removal
and avoid shell-specific commands.

Comment thread services/platform/app/features/settings/api-keys/hooks/use-api-keys.ts Outdated
Comment thread services/platform/app/features/settings/api-keys/types.ts
Comment thread services/platform/app/routes/dashboard/$id/settings/api-keys.tsx Outdated
Comment thread services/platform/app/routes/docs.tsx
Comment thread services/platform/convex/api_gateway.ts
Comment thread services/platform/convex/api_gateway.ts
Comment thread services/platform/convex/workflows/executions/complete_execution.ts
Comment thread services/platform/scripts/generate-openapi.ts
- Add API gateway with support for x-api-key header and session cookie auth
- Create API keys settings page with create, view, and revoke functionality
- Add OpenAPI spec generation with Scalar docs viewer at /docs
- Patch convex-helpers for API key bearer token support
Wrap storage delete calls in try-catch to prevent "storage id not found"
errors when completing workflow executions. This can occur due to race
conditions or when storage files are already deleted.
- Add Caddy proxy rule for /api/run/* to platform:3211
- Support __Secure- prefixed cookies in API Gateway for HTTPS

Fixes 404 on /api/run/* endpoints in Docker and 401 auth errors
when using session cookies on HTTPS connections.
@larryro larryro force-pushed the feat/319-api-key-authentication branch from a112f89 to 6c27ecd Compare February 5, 2026 11:31
Allow users to easily copy the organization ID for API integration,
support requests, and debugging purposes.
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