Skip to content

Commit 2edee73

Browse files
committed
fix: api key revocation
1 parent af48531 commit 2edee73

File tree

4 files changed

+48
-12
lines changed

4 files changed

+48
-12
lines changed

apps/webapp/src/components/settings/product/product-secrets-card.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,7 @@
33
import { useState } from "react";
44
import { Copy, Eye, EyeOff } from "lucide-react";
55
import { toast } from "sonner";
6-
import {
7-
Card,
8-
CardContent,
9-
CardFooter,
10-
} from "@refref/ui/components/card";
6+
import { Card, CardContent, CardFooter } from "@refref/ui/components/card";
117
import { Button } from "@refref/ui/components/button";
128
import { Input } from "@refref/ui/components/input";
139
import { Label } from "@refref/ui/components/label";

apps/webapp/src/server/api/routers/api-keys.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
ensureServiceAccountForOrganization,
66
getApiKeysForOrganization,
77
validateApiKeyPermission as validateOrgApiKeyPermission,
8+
deleteApiKeyForOrganization,
89
} from "@refref/coredb";
910
import { auth } from "@/lib/auth";
1011

@@ -156,12 +157,20 @@ export const apiKeysRouter = createTRPCRouter({
156157
});
157158
}
158159

159-
// Delete the API key using Better Auth
160-
await auth.api.deleteApiKey({
161-
body: {
162-
keyId: input.keyId,
163-
},
164-
});
160+
// Delete the API key directly from the database
161+
// (Better Auth's deleteApiKey requires ownership match, but keys belong to service account)
162+
const deleted = await deleteApiKeyForOrganization(
163+
ctx.db,
164+
ctx.activeOrganizationId,
165+
input.keyId,
166+
);
167+
168+
if (!deleted) {
169+
throw new TRPCError({
170+
code: "NOT_FOUND",
171+
message: "API key not found or does not belong to this organization",
172+
});
173+
}
165174

166175
return { success: true };
167176
}),

packages/coredb/src/lib/organization-service-account.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,35 @@ export async function validateApiKeyPermission(
219219
return userRole === "admin" || userRole === "owner";
220220
}
221221

222+
/**
223+
* Delete an API key for an organization
224+
* Verifies the key belongs to the organization's service account before deletion
225+
* @returns true if deleted, false if not found or doesn't belong to organization
226+
*/
227+
export async function deleteApiKeyForOrganization(
228+
db: PostgresJsDatabase<any>,
229+
organizationId: string,
230+
keyId: string,
231+
): Promise<boolean> {
232+
// Get service account for this organization
233+
const serviceAccount = await findServiceAccountForOrganization(
234+
db,
235+
organizationId,
236+
);
237+
238+
if (!serviceAccount) {
239+
return false;
240+
}
241+
242+
// Delete the key only if it belongs to the organization's service account
243+
const result = await db
244+
.delete(apikey)
245+
.where(and(eq(apikey.id, keyId), eq(apikey.userId, serviceAccount.id)))
246+
.returning({ id: apikey.id });
247+
248+
return result.length > 0;
249+
}
250+
222251
/**
223252
* Clean up old product service accounts (migration helper)
224253
*/

packages/widget/src/widget/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ function mountWidget() {
6767
// Reset inherited properties at shadow boundary, then apply widget styles
6868
// @link https://github.com/tailwindlabs/tailwindcss/discussions/1935
6969
const sheet = new CSSStyleSheet();
70-
sheet.replaceSync(`:host { all: initial; }\n` + styles.replaceAll(":root", ":host"));
70+
sheet.replaceSync(
71+
`:host { all: initial; }\n` + styles.replaceAll(":root", ":host"),
72+
);
7173

7274
// Apply CSS variable overrides from actual config
7375
if (config.cssVariables && Object.keys(config.cssVariables).length > 0) {

0 commit comments

Comments
 (0)