Skip to content

feat: add support to mark API keys as external#2507

Merged
wilsonrivera merged 6 commits intomainfrom
wilson/eng-8936-api-key-add-external-api-keys
Feb 16, 2026
Merged

feat: add support to mark API keys as external#2507
wilsonrivera merged 6 commits intomainfrom
wilson/eng-8936-api-key-add-external-api-keys

Conversation

@wilsonrivera
Copy link
Copy Markdown
Contributor

@wilsonrivera wilsonrivera commented Feb 13, 2026

external

Summary by CodeRabbit

  • New Features
    • Added support for external API keys managed by external services.
    • External API keys are now displayed with an "External" badge in the UI with explanatory tooltip.
    • External API keys are read-only and cannot be modified or deleted through the platform.

Checklist

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 13, 2026

No actionable comments were generated in the recent review. 🎉


Walkthrough

This pull request introduces an "external" boolean field to API keys across the stack. The field indicates whether an API key is managed externally and is added to protobuf definitions, database schema, repository layer, service layer, types, and the UI component, with conditional logic to restrict modification of external keys.

Changes

Cohort / File(s) Summary
Proto Definitions
proto/wg/cosmo/platform/v1/platform.proto, connect/src/wg/cosmo/platform/v1/platform_pb.ts
Added external boolean field to APIKey message (tag 8) and CreateAPIKeyRequest (tag 6) in proto; updated generated TypeScript with new field property and protobuf metadata.
Database Schema
controlplane/migrations/0135_magical_moira_mactaggert.sql, controlplane/migrations/meta/_journal.json, controlplane/src/db/schema.ts
Added non-nullable external boolean column (default false) to api_keys table with corresponding migration entry and schema definition.
Repository Layer
controlplane/src/core/repositories/ApiKeyRepository.ts
Added isExternal to input model; updated addAPIKey to persist field; modified getAPIKeysCount signature to accept includeExternal parameter with conditional filtering logic; updated getAPIKeyByName and getAPIKeys to return external field in results.
Service Layer
controlplane/src/core/bufservices/api-key/createAPIKey.ts, controlplane/src/core/bufservices/api-key/getAPIKeys.ts, controlplane/src/core/bufservices/proposal/updateProposal.ts
Passed isExternal flag in API key creation; added includeExternal parameters to repository calls; replaced hardcoded user-agent string with centralized constant.
Constants & Types
controlplane/src/core/constants.ts, controlplane/src/types/index.ts
Added hubUserAgent constant; added external: boolean field to APIKeyDTO interface.
UI Component
studio/src/pages/[organizationSlug]/apikeys.tsx
Added "External" badge display for external keys with tooltip; conditionally disabled update/delete actions when key is external.
Tests
controlplane/src/core/test-util.ts, controlplane/test/delete-user.test.ts
Added isExternal: false field to API key creation payloads in test fixtures.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding support to mark API keys as external.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Feb 13, 2026

Router image scan passed

✅ No security vulnerabilities found in image:

ghcr.io/wundergraph/cosmo/router:sha-5f660fbc70db98e00e7cc556b80ba6ea9652d05b

@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 13, 2026

Codecov Report

❌ Patch coverage is 41.66667% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 36.73%. Comparing base (668c80a) to head (3c5176c).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
studio/src/pages/[organizationSlug]/apikeys.tsx 0.00% 21 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             main    #2507       +/-   ##
===========================================
- Coverage   62.17%   36.73%   -25.45%     
===========================================
  Files         231      948      +717     
  Lines       24134   124096    +99962     
  Branches        0     5113     +5113     
===========================================
+ Hits        15006    45589    +30583     
- Misses       7863    76951    +69088     
- Partials     1265     1556      +291     
Files with missing lines Coverage Δ
...plane/src/core/bufservices/api-key/createAPIKey.ts 86.79% <100.00%> (ø)
...olplane/src/core/bufservices/api-key/getAPIKeys.ts 100.00% <100.00%> (ø)
...ne/src/core/bufservices/proposal/updateProposal.ts 75.05% <100.00%> (ø)
controlplane/src/core/constants.ts 100.00% <100.00%> (ø)
...rolplane/src/core/repositories/ApiKeyRepository.ts 83.41% <100.00%> (ø)
controlplane/src/core/test-util.ts 88.26% <100.00%> (ø)
controlplane/src/db/schema.ts 100.00% <100.00%> (ø)
controlplane/src/types/index.ts 100.00% <ø> (ø)
studio/src/pages/[organizationSlug]/apikeys.tsx 0.00% <0.00%> (ø)

... and 723 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
studio/src/pages/[organizationSlug]/apikeys.tsx (1)

908-941: ⚠️ Potential issue | 🟡 Minor

Table layout issue: missing cell for external keys when user can manage API keys.

When canManageAPIKeys is true and external is true, the table header cell (line 851-853) will be rendered but the body cell will be skipped, causing a column mismatch and potential layout issues.

Consider rendering an empty cell to maintain alignment:

📝 Proposed fix
-                        {canManageAPIKeys && !external && (
+                        {canManageAPIKeys && (
                           <TableCell>
+                            {!external ? (
                             <DropdownMenu>
                               <div className="flex justify-center">
                                 <DropdownMenuTrigger asChild>
                                   <Button variant="ghost" size="icon">
                                     <EllipsisVerticalIcon className="h-4 w-4" />
                                   </Button>
                                 </DropdownMenuTrigger>
                               </div>
                               <DropdownMenuContent align="end">
                                 <DropdownMenuItem
                                   onClick={() => {
                                     setOpenUpdateDialog(true);
                                     setSelectedGroup({
                                       apiKeyName: name,
                                       groupId: group?.id,
                                     });
                                   }}
                                 >
                                   Update group
                                 </DropdownMenuItem>
                                 <DropdownMenuItem
                                   onClick={() => {
                                     setDeleteApiKeyName(name);
                                     setOpenDeleteDialog(true);
                                   }}
                                 >
                                   Delete
                                 </DropdownMenuItem>
                               </DropdownMenuContent>
                             </DropdownMenu>
+                            ) : null}
                           </TableCell>
                         )}
🤖 Fix all issues with AI agents
In `@controlplane/src/core/bufservices/api-key/deleteAPIKey.ts`:
- Around line 47-55: The error message returned when checking apiKey.external
uses incorrect grammar and plurality; update the details string in the
conditional that checks apiKey.external (where clientHdr and hubUserAgent are
used) to read "You cannot delete an external API key." so it uses "an" and
singular "key" (i.e., replace the existing details text in the return block).

In `@controlplane/src/core/bufservices/api-key/getAPIKeys.ts`:
- Around line 33-36: The pagination mismatch comes from calling
apiKeyRepo.getAPIKeysCount with includeExternal: true while getAPIKeys returns
all keys; fix by updating getAPIKeys to accept an includeExternal boolean (add
parameter to getAPIKeys signature, default to false or mirror callers), pass
that flag into both apiKeyRepo.getAPIKeys and apiKeyRepo.getAPIKeysCount so both
queries use the same external filter, and ensure any callers of getAPIKeys
propagate the desired includeExternal value so totals and returned rows align.

In `@controlplane/src/core/bufservices/api-key/updateAPIKey.ts`:
- Around line 47-55: The User-Agent substring check using ctx.requestHeader and
hubUserAgent is not a reliable authorization guard and should be removed;
instead gate updates to apiKey.external using a trusted identity/assertion from
the authenticated principal (e.g., a service account flag or internal claim on
ctx.auth/ctx.principal) that cannot be spoofed. Replace the clientHdr-based
early return with a check like "if (apiKey.external &&
!principalHasInternalServiceIdentity(ctx.principal)) { return error }" (where
principalHasInternalServiceIdentity is your code that inspects a verified claim
or service account flag), and ensure the existing isOrganizationApiKeyManager
RBAC logic remains intact. Use unique symbols apiKey.external,
ctx.requestHeader/clientHdr, hubUserAgent, and the RBAC check
(isOrganizationApiKeyManager) to locate where to remove the User-Agent logic and
add the new trusted-identity check.

In `@controlplane/src/core/repositories/ApiKeyRepository.ts`:
- Around line 163-169: Rename the misleading parameter includeExternal to
isExternal in the getAPIKeysCount method signature and update all uses inside
the method (the where clause: eq(apiKeys.external, input.includeExternal)) to
use input.isExternal instead; also update any callers or types that pass this
argument (e.g., places that currently pass true/false for external filtering) so
they reference the renamed property to preserve the existing exclusive filter
behavior. Ensure the method signature in ApiKeyRepository.getAPIKeysCount and
any related interface/type definitions reflect isExternal.

Comment thread controlplane/src/core/bufservices/api-key/deleteAPIKey.ts Outdated
Comment thread controlplane/src/core/bufservices/api-key/getAPIKeys.ts
Comment thread controlplane/src/core/bufservices/api-key/updateAPIKey.ts Outdated
Comment thread controlplane/src/core/repositories/ApiKeyRepository.ts Outdated
Copy link
Copy Markdown
Contributor

@StarpTech StarpTech left a comment

Choose a reason for hiding this comment

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

LGTM

@wilsonrivera wilsonrivera merged commit 483d5c8 into main Feb 16, 2026
45 checks passed
@wilsonrivera wilsonrivera deleted the wilson/eng-8936-api-key-add-external-api-keys branch February 16, 2026 17:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants