diff --git a/apps/api/src/pkg/keys/service.ts b/apps/api/src/pkg/keys/service.ts index bf2886969..8b2dab21f 100644 --- a/apps/api/src/pkg/keys/service.ts +++ b/apps/api/src/pkg/keys/service.ts @@ -57,7 +57,11 @@ type InvalidResponse = { | "DISABLED" | "INSUFFICIENT_PERMISSIONS"; key: Key; - identity: { id: string; externalId: string; meta: Record | null } | null; + identity: { + id: string; + externalId: string; + meta: Record | null; + } | null; api: Api; ratelimit?: { remaining: number; @@ -73,7 +77,11 @@ type ValidResponse = { code?: never; valid: true; key: Key; - identity: { id: string; externalId: string; meta: Record | null } | null; + identity: { + id: string; + externalId: string; + meta: Record | null; + } | null; api: Api; ratelimit?: { remaining: number; @@ -451,6 +459,7 @@ export class KeyService { if (data.api.ipWhitelist) { const ip = c.req.header("True-Client-IP") ?? c.req.header("CF-Connecting-IP"); + if (!ip) { return Ok({ key: data.key, @@ -461,7 +470,8 @@ export class KeyService { permissions: data.permissions, }); } - const ipWhitelist = JSON.parse(data.api.ipWhitelist) as string[]; + + const ipWhitelist = data.api.ipWhitelist.split(",").map((s) => s.trim()); if (!ipWhitelist.includes(ip)) { return Ok({ key: data.key, diff --git a/apps/api/src/routes/legacy_keys_verifyKey.test.ts b/apps/api/src/routes/legacy_keys_verifyKey.test.ts index eba5cfa15..6d549e55e 100644 --- a/apps/api/src/routes/legacy_keys_verifyKey.test.ts +++ b/apps/api/src/routes/legacy_keys_verifyKey.test.ts @@ -129,7 +129,7 @@ describe("with ip whitelist", () => { name: "test", authType: "key", keyAuthId: keyAuthId, - ipWhitelist: JSON.stringify(["100.100.100.100"]), + ipWhitelist: ["100.100.100.100"].join(","), createdAt: new Date(), deletedAt: null, }); @@ -177,7 +177,7 @@ describe("with ip whitelist", () => { name: "test", authType: "key", keyAuthId: keyAuthid, - ipWhitelist: JSON.stringify(["100.100.100.100"]), + ipWhitelist: ["100.100.100.100"].join(","), createdAt: new Date(), deletedAt: null, }); diff --git a/apps/api/src/routes/v1_apis_getApi.happy.test.ts b/apps/api/src/routes/v1_apis_getApi.happy.test.ts index 23600af73..60cdbc991 100644 --- a/apps/api/src/routes/v1_apis_getApi.happy.test.ts +++ b/apps/api/src/routes/v1_apis_getApi.happy.test.ts @@ -31,7 +31,7 @@ test("with ip whitelist", async (t) => { id: newId("api"), name: "with ip whitelist", workspaceId: h.resources.userWorkspace.id, - ipWhitelist: JSON.stringify(["127.0.0.1"]), + ipWhitelist: ["127.0.0.1"].join(","), createdAt: new Date(), deletedAt: null, }; diff --git a/apps/api/src/routes/v1_keys_verifyKey.test.ts b/apps/api/src/routes/v1_keys_verifyKey.test.ts index d97e9d195..a5534e16c 100644 --- a/apps/api/src/routes/v1_keys_verifyKey.test.ts +++ b/apps/api/src/routes/v1_keys_verifyKey.test.ts @@ -349,6 +349,7 @@ describe("when ratelimited", () => { expect(res.body.identity!.externalId).toEqual(externalId); }); }); + describe("with ratelimit override", () => { test("deducts the correct number of tokens", { timeout: 20000 }, async (t) => { const h = await IntegrationHarness.init(t); @@ -517,7 +518,7 @@ describe("with ip whitelist", () => { name: "test", authType: "key", keyAuthId: keyAuthId, - ipWhitelist: JSON.stringify(["100.100.100.100"]), + ipWhitelist: ["100.100.100.100"].join(","), createdAt: new Date(), }); @@ -565,7 +566,7 @@ describe("with ip whitelist", () => { name: "test", authType: "key", keyAuthId: keyAuthid, - ipWhitelist: JSON.stringify(["100.100.100.100"]), + ipWhitelist: ["100.100.100.100"].join(","), createdAt: new Date(), }); diff --git a/apps/dashboard/app/(app)/apis/[apiId]/settings/page.tsx b/apps/dashboard/app/(app)/apis/[apiId]/settings/page.tsx index fc16fc94b..0fb16523f 100644 --- a/apps/dashboard/app/(app)/apis/[apiId]/settings/page.tsx +++ b/apps/dashboard/app/(app)/apis/[apiId]/settings/page.tsx @@ -34,6 +34,7 @@ export default async function SettingsPage(props: Props) { if (!workspace || workspace.tenantId !== tenantId) { return redirect("/new"); } + const api = workspace.apis.find((api) => api.id === props.params.apiId); if (!api) { return notFound(); diff --git a/apps/dashboard/app/(app)/apis/[apiId]/settings/update-ip-whitelist.tsx b/apps/dashboard/app/(app)/apis/[apiId]/settings/update-ip-whitelist.tsx index 06fe1b3f7..df8224e19 100644 --- a/apps/dashboard/app/(app)/apis/[apiId]/settings/update-ip-whitelist.tsx +++ b/apps/dashboard/app/(app)/apis/[apiId]/settings/update-ip-whitelist.tsx @@ -30,7 +30,7 @@ const formSchema = z.object({ type Props = { workspace: { - plan: Workspace["plan"]; + features: Workspace["features"]; }; api: { id: string; @@ -42,7 +42,7 @@ type Props = { export const UpdateIpWhitelist: React.FC = ({ api, workspace }) => { const router = useRouter(); - const isEnabled = workspace.plan === "enterprise"; + const isEnabled = workspace.features.ipWhitelist; const form = useForm>({ resolver: zodResolver(formSchema), @@ -79,7 +79,7 @@ export const UpdateIpWhitelist: React.FC = ({ api, workspace }) => { - {workspace.plan === "enterprise" ? ( + {isEnabled ? (
diff --git a/apps/dashboard/lib/trpc/routers/api/updateIpWhitelist.ts b/apps/dashboard/lib/trpc/routers/api/updateIpWhitelist.ts index 67954331c..0984adee9 100644 --- a/apps/dashboard/lib/trpc/routers/api/updateIpWhitelist.ts +++ b/apps/dashboard/lib/trpc/routers/api/updateIpWhitelist.ts @@ -43,7 +43,12 @@ export const updateApiIpWhitelist = rateLimitedProcedure(ratelimit.update) "We are unable to update the API whitelist. Please try again or contact support@unkey.dev", }); }); - if (!api || api.workspace.tenantId !== ctx.tenant.id) { + + if ( + !api || + api.workspace.tenantId !== ctx.tenant.id || + input.workspaceId !== api.workspace.id + ) { throw new TRPCError({ code: "NOT_FOUND", message: @@ -51,6 +56,14 @@ export const updateApiIpWhitelist = rateLimitedProcedure(ratelimit.update) }); } + if (!api.workspace.features.ipWhitelist) { + throw new TRPCError({ + code: "FORBIDDEN", + message: + "IP Whitelisting is only available for enterprise plans. Please contact support@unkey.dev.", + }); + } + const newIpWhitelist = input.ipWhitelist === null ? null : input.ipWhitelist.join(","); await db @@ -68,6 +81,7 @@ export const updateApiIpWhitelist = rateLimitedProcedure(ratelimit.update) "We are unable to update the API whitelist. Please try again or contact support@unkey.dev", }); }); + await insertAuditLogs(tx, { workspaceId: api.workspace.id, actor: {