feat(platform): add API key authentication and management#366
Conversation
📝 WalkthroughWalkthroughThis 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
Comment |
There was a problem hiding this comment.
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.
- 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.
a112f89 to
6c27ecd
Compare
…ata on org switch
Allow users to easily copy the organization ID for API integration, support requests, and debugging purposes.
Summary
x-api-keyheader and session cookie authentication/docsTest plan
x-api-keyheader/docs🤖 Generated with Claude Code
Summary by CodeRabbit