diff --git a/apps/api/src/routes/v1_keys_createKey.happy.test.ts b/apps/api/src/routes/v1_keys_createKey.happy.test.ts index d8a8be632d..aac372967f 100644 --- a/apps/api/src/routes/v1_keys_createKey.happy.test.ts +++ b/apps/api/src/routes/v1_keys_createKey.happy.test.ts @@ -6,6 +6,7 @@ import { eq, schema } from "@unkey/db"; import { newId } from "@unkey/id"; import { IntegrationHarness } from "src/pkg/testutil/integration-harness"; +import { KeyV1 } from "@unkey/keys"; import type { V1KeysCreateKeyRequest, V1KeysCreateKeyResponse } from "./v1_keys_createKey"; test("creates key", async (t) => { @@ -34,6 +35,50 @@ test("creates key", async (t) => { expect(found!.hash).toEqual(await sha256(res.body.key)); }); +test("creates key with default prefix and bytes from keyAuth", async (t) => { + const expectedPrefix = "_prefix"; + const expectedBytes = 66; + + const h = await IntegrationHarness.init(t); + const root = await h.createRootKey([`api.${h.resources.userApi.id}.create_key`]); + + await h.db.primary + .update(schema.keyAuth) + .set({ + defaultPrefix: expectedPrefix, + defaultBytes: expectedBytes, + }) + .where(eq(schema.keyAuth.id, h.resources.userKeyAuth.id)); + + // Make the request without specifying prefix or byteLength + const res = await h.post({ + url: "/v1/keys.createKey", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${root.key}`, + }, + body: { + apiId: h.resources.userApi.id, + enabled: true, + }, + }); + + expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); + + const found = await h.db.primary.query.keys.findFirst({ + where: (table, { eq }) => eq(table.id, res.body.keyId), + }); + + const referenceKey = new KeyV1({ + byteLength: expectedBytes, + prefix: expectedPrefix, + }).toString(); + + expect(found).toBeDefined(); + expect(found!.start).toEqual(referenceKey.slice(0, 5)); + expect(found!.hash).toEqual(await sha256(res.body.key)); +}); + describe("with enabled flag", () => { describe("not set", () => { test("should still create an enabled key", async (t) => { diff --git a/apps/api/src/routes/v1_keys_createKey.ts b/apps/api/src/routes/v1_keys_createKey.ts index 709bc08ffd..db1209ed31 100644 --- a/apps/api/src/routes/v1_keys_createKey.ts +++ b/apps/api/src/routes/v1_keys_createKey.ts @@ -279,6 +279,7 @@ export const registerV1KeysCreateKey = (app: App) => })) ?? null ); }); + if (err) { throw new UnkeyApiError({ code: "INTERNAL_SERVER_ERROR", @@ -350,10 +351,12 @@ export const registerV1KeysCreateKey = (app: App) => apiId: api.id, }); } + const secret = new KeyV1({ - byteLength: req.byteLength ?? 16, - prefix: req.prefix, + byteLength: req.byteLength ?? api.keyAuth?.defaultBytes ?? 16, + prefix: req.prefix ?? (api.keyAuth?.defaultPrefix as string | undefined), }).toString(); + const start = secret.slice(0, (req.prefix?.length ?? 0) + 5); const kId = newId("key"); const hash = await sha256(secret.toString()); diff --git a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx index 1cf452c243..7393667793 100644 --- a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx +++ b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx @@ -85,13 +85,18 @@ export const CreateKey = ({ apiId, keyAuthId, defaultBytes, defaultPrefix }: Pro }); async function onSubmit(values: z.infer) { - if (values.limit?.refill?.interval !== "none" && !values.limit?.refill?.amount) { + if ( + values.limitEnabled && + values.limit?.refill?.interval !== "none" && + !values.limit?.refill?.amount + ) { form.setError("limit.refill.amount", { type: "manual", message: "Please enter a value if interval is selected", }); return; } + if (!values.expireEnabled) { delete values.expires; }