Skip to content

fix(api): bake org context into API keys via tRPC wrapper#1378

Merged
saddlepaddle merged 1 commit intomainfrom
fix/api-key-org-context
Feb 10, 2026
Merged

fix(api): bake org context into API keys via tRPC wrapper#1378
saddlepaddle merged 1 commit intomainfrom
fix/api-key-org-context

Conversation

@saddlepaddle
Copy link
Copy Markdown
Collaborator

@saddlepaddle saddlepaddle commented Feb 10, 2026

Summary

  • Adds a apiKey.create tRPC procedure that injects activeOrganizationId from the session into the API key metadata server-side — no client-side org passing needed
  • MCP endpoint now reads org from key metadata instead of querying the members table (fixes wrong-org bug for multi-org users)
  • Stops leaking the raw API key secret in the token field ("api-key" sentinel instead)
  • Adds !userId null guard on API key verification

Changes

  • packages/trpc/src/router/api-key/ — new apiKey.create mutation wrapping auth.api.createApiKey with org metadata injection
  • packages/trpc/src/root.ts — register apiKeyRouter
  • apps/api/.../route.ts — read org from metadata, null guards, no secret leak
  • apps/desktop/.../ApiKeysSettings.tsx — use apiTrpcClient.apiKey.create.mutate() instead of authClient.apiKey.create()

Test plan

  • bun run lint:fix — clean
  • bun run typecheck — 18/18 pass
  • bun test — 1221 pass, 0 fail
  • Manual: create API key in desktop settings, use in .mcp.json, confirm MCP tools respond with correct org context
  • Manual: verify multi-org user gets the correct org baked into the key

Summary by CodeRabbit

Release Notes

  • Refactor
    • Streamlined API key authentication process by removing database lookups and using metadata-based validation instead.
    • Reorganized API key management system through new router integration for improved consistency.
    • Updated API key creation workflow to use simplified internal infrastructure.

API key creation now goes through a tRPC procedure that injects the
user's activeOrganizationId into key metadata server-side. The MCP
endpoint reads org from metadata instead of falling back to a members
query. Also stops leaking the raw API key secret in the token field.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 10, 2026

📝 Walkthrough

Walkthrough

The changes refactor API-key authentication and management by introducing a new tRPC-based router for API key creation, removing direct database access in the agent authentication route, and sourcing organizationId from API key metadata instead of database lookups.

Changes

Cohort / File(s) Summary
Agent API-Key Authentication
apps/api/src/app/api/agent/[transport]/route.ts
Removed database and Drizzle ORM imports; simplified API-key verification to validate userId/organizationId presence and extract organizationId from API key metadata instead of database lookup; added explicit null/undefined guards with specific error logging; changed token return value from bearerToken to "api-key".
tRPC API-Key Router
packages/trpc/src/router/api-key/api-key.ts, packages/trpc/src/router/api-key/index.ts, packages/trpc/src/root.ts
Introduced new tRPC router for API key management with a protected create procedure that validates active organization and delegates to ctx.auth.api.createApiKey; integrated router into appRouter and exported updated AppRouter type signature.
Desktop API-Key Creation UI
apps/desktop/src/renderer/routes/_authenticated/settings/api-keys/components/ApiKeysSettings/ApiKeysSettings.tsx
Updated API key creation from direct authClient.apiKey.create() to tRPC apiTrpcClient.apiKey.create.mutate(); adjusted result handling to read key from result.key instead of result.data?.key.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 With metadata in hand, no database to call,
tRPC routers now handle it all!
Keys organized, auth flows run fleet,
Simpler paths make the system complete!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: server-side org context injection into API keys via a tRPC wrapper, matching the PR's primary objective.
Description check ✅ Passed The PR description is well-structured with summary, detailed changes, and test plan; it covers the template sections effectively despite some manual tests still pending.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/api-key-org-context

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

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.

Caution

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

⚠️ Outside diff range comments (1)
apps/desktop/src/renderer/routes/_authenticated/settings/api-keys/components/ApiKeysSettings/ApiKeysSettings.tsx (1)

84-88: ⚠️ Potential issue | 🟡 Minor

Missing user-facing error feedback on key creation failure.

The catch block logs to console but never shows a toast or other UI feedback. Users will see the "Generating..." button reset with no indication of what went wrong. The component already imports toast from @superset/ui/sonner.

Proposed fix
 		} catch (error) {
 			console.error("[api-keys] Failed to generate API key:", error);
+			toast.error("Failed to generate API key. Please try again.");
 		} finally {
🧹 Nitpick comments (2)
packages/trpc/src/router/api-key/api-key.ts (1)

6-27: Wrap createApiKey call with error handling and logging.

If ctx.auth.api.createApiKey throws (network error, auth service down, invalid body, etc.), the raw error bubbles up without context. Per coding guidelines, errors should be logged with context before rethrowing. Also, result.key is not null-checked before being returned.

Proposed fix
-		const result = await ctx.auth.api.createApiKey({
-			headers: ctx.headers,
-			body: {
-				name: input.name,
-				metadata: { organizationId },
-			},
-		});
-
-		return { key: result.key };
+		let result: { key?: string | null };
+		try {
+			result = await ctx.auth.api.createApiKey({
+				headers: ctx.headers,
+				body: {
+					name: input.name,
+					metadata: { organizationId },
+				},
+			});
+		} catch (error) {
+			console.error("[apiKey/create] Failed to create API key:", error);
+			throw new TRPCError({
+				code: "INTERNAL_SERVER_ERROR",
+				message: "Failed to create API key",
+			});
+		}
+
+		if (!result.key) {
+			throw new TRPCError({
+				code: "INTERNAL_SERVER_ERROR",
+				message: "API key creation returned no key",
+			});
+		}
+
+		return { key: result.key };

As per coding guidelines, "Never swallow errors silently; at minimum log errors with context before rethrowing or handling them explicitly" and "Validate external API data as untrusted by handling missing fields, unknown enums, and unexpected shapes with tolerant parsing and explicit fallbacks".

apps/api/src/app/api/agent/[transport]/route.ts (1)

44-48: JSON.parse can throw on malformed metadata strings.

If result.key.metadata is a non-JSON string, JSON.parse throws and the outer catch logs a misleading "API key verification failed" message. Consider adding a targeted try/catch around the parse, or validating the shape more defensively.

Also, the as string | undefined cast on Line 48 skips runtime type checking — if organizationId is unexpectedly a non-string truthy value, it'll pass through.

Proposed fix
-			const metadata =
-				typeof result.key.metadata === "string"
-					? JSON.parse(result.key.metadata)
-					: result.key.metadata;
-			const organizationId = metadata?.organizationId as string | undefined;
+			let metadata: Record<string, unknown> | undefined;
+			try {
+				metadata =
+					typeof result.key.metadata === "string"
+						? JSON.parse(result.key.metadata)
+						: (result.key.metadata as Record<string, unknown> | undefined);
+			} catch {
+				console.error("[mcp/auth] API key metadata is not valid JSON");
+				return undefined;
+			}
+			const organizationId =
+				typeof metadata?.organizationId === "string"
+					? metadata.organizationId
+					: undefined;

As per coding guidelines, "Validate external API data as untrusted by handling missing fields, unknown enums, and unexpected shapes with tolerant parsing and explicit fallbacks".

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 10, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch
  • ✅ Electric Fly.io app
  • ✅ Streams Fly.io app

Thank you for your contribution! 🎉

@saddlepaddle saddlepaddle merged commit beda4ae into main Feb 10, 2026
14 of 15 checks passed
@Kitenite Kitenite deleted the fix/api-key-org-context branch February 11, 2026 17:59
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