Merge pull request #4806 from tanaymishra/fix/validate-api-key-name-length

fix: validate API key name length to prevent opaque 500
This commit is contained in:
Mauricio Siu
2026-07-14 00:08:04 -06:00
committed by GitHub
4 changed files with 61 additions and 3 deletions

View File

@@ -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`,
);
}
});
});

View File

@@ -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 = () => {
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input placeholder="My API Key" {...field} />
<Input
placeholder="My API Key"
maxLength={API_KEY_NAME_MAX_LENGTH}
{...field}
/>
</FormControl>
<FormDescription>
Maximum {API_KEY_NAME_MAX_LENGTH} characters
</FormDescription>
<FormMessage />
</FormItem>
)}

View File

@@ -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`,
);

View File

@@ -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({