From f577778667ec40242060ca29d86c5fbdfcabd31f Mon Sep 17 00:00:00 2001 From: tanaymishra Date: Sun, 12 Jul 2026 17:42:28 +0530 Subject: [PATCH] fix: validate API key name length to prevent opaque 500 API key names longer than 32 characters were rejected by better-auth with a 400 that surfaced as an opaque INTERNAL_SERVER_ERROR (the generic "Failed to generate API key" toast). Add a shared name schema (min 1, max 32, matching better-auth's default maximumNameLength) used by both the tRPC input and the client form, and surface the limit on the Name field so users see it before submitting. Fixes #4798 --- .../dokploy/__test__/api/api-key-name.test.ts | 26 +++++++++++++++++++ .../dashboard/settings/api/add-api-key.tsx | 12 +++++++-- apps/dokploy/lib/api-keys.ts | 23 ++++++++++++++++ apps/dokploy/server/api/routers/user.ts | 3 ++- 4 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 apps/dokploy/__test__/api/api-key-name.test.ts create mode 100644 apps/dokploy/lib/api-keys.ts diff --git a/apps/dokploy/__test__/api/api-key-name.test.ts b/apps/dokploy/__test__/api/api-key-name.test.ts new file mode 100644 index 000000000..677a52757 --- /dev/null +++ b/apps/dokploy/__test__/api/api-key-name.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, it } from "vitest"; +import { API_KEY_NAME_MAX_LENGTH, apiKeyNameSchema } from "@/lib/api-keys"; + +describe("apiKeyNameSchema", () => { + it("rejects an empty name", () => { + const result = apiKeyNameSchema.safeParse(""); + expect(result.success).toBe(false); + }); + + it("accepts a name at the maximum length", () => { + const name = "a".repeat(API_KEY_NAME_MAX_LENGTH); + const result = apiKeyNameSchema.safeParse(name); + expect(result.success).toBe(true); + }); + + it("rejects a name over the maximum length instead of passing it to better-auth", () => { + const name = "a".repeat(API_KEY_NAME_MAX_LENGTH + 1); + const result = apiKeyNameSchema.safeParse(name); + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.issues[0]?.message).toBe( + `Name must be at most ${API_KEY_NAME_MAX_LENGTH} characters`, + ); + } + }); +}); diff --git a/apps/dokploy/components/dashboard/settings/api/add-api-key.tsx b/apps/dokploy/components/dashboard/settings/api/add-api-key.tsx index c6db49b5d..ece17421a 100644 --- a/apps/dokploy/components/dashboard/settings/api/add-api-key.tsx +++ b/apps/dokploy/components/dashboard/settings/api/add-api-key.tsx @@ -32,10 +32,11 @@ import { SelectValue, } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; +import { API_KEY_NAME_MAX_LENGTH, apiKeyNameSchema } from "@/lib/api-keys"; import { api } from "@/utils/api"; const formSchema = z.object({ - name: z.string().min(1, "Name is required"), + name: apiKeyNameSchema, prefix: z.string().optional(), expiresIn: z.number().nullable(), organizationId: z.string().min(1, "Organization is required"), @@ -159,8 +160,15 @@ export const AddApiKey = () => { Name - + + + Maximum {API_KEY_NAME_MAX_LENGTH} characters + )} diff --git a/apps/dokploy/lib/api-keys.ts b/apps/dokploy/lib/api-keys.ts new file mode 100644 index 000000000..b64e3e0a9 --- /dev/null +++ b/apps/dokploy/lib/api-keys.ts @@ -0,0 +1,23 @@ +import { z } from "zod"; + +/** + * Maximum length allowed for an API key name. + * + * This mirrors the default `maximumNameLength` enforced by the + * `@better-auth/api-key` plugin. Names longer than this are rejected by + * better-auth with a 400, so we validate against it up front to surface a + * clear field-level error instead of an opaque 500. + */ +export const API_KEY_NAME_MAX_LENGTH = 32; + +/** + * Shared validation for an API key name, used by both the tRPC input schema + * and the client form so the two can't drift. + */ +export const apiKeyNameSchema = z + .string() + .min(1, "Name is required") + .max( + API_KEY_NAME_MAX_LENGTH, + `Name must be at most ${API_KEY_NAME_MAX_LENGTH} characters`, + ); diff --git a/apps/dokploy/server/api/routers/user.ts b/apps/dokploy/server/api/routers/user.ts index 583014ff6..b4782b1d2 100644 --- a/apps/dokploy/server/api/routers/user.ts +++ b/apps/dokploy/server/api/routers/user.ts @@ -35,6 +35,7 @@ import { TRPCError } from "@trpc/server"; import * as bcrypt from "bcrypt"; import { and, asc, eq, gt, ne } from "drizzle-orm"; import { z } from "zod"; +import { apiKeyNameSchema } from "@/lib/api-keys"; import { audit } from "@/server/api/utils/audit"; import { adminProcedure, @@ -45,7 +46,7 @@ import { } from "../trpc"; const apiCreateApiKey = z.object({ - name: z.string().min(1), + name: apiKeyNameSchema, prefix: z.string().optional(), expiresIn: z.number().optional(), metadata: z.object({