mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-15 10:55:23 +02:00
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
This commit is contained in:
26
apps/dokploy/__test__/api/api-key-name.test.ts
Normal file
26
apps/dokploy/__test__/api/api-key-name.test.ts
Normal 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`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -32,10 +32,11 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import { API_KEY_NAME_MAX_LENGTH, apiKeyNameSchema } from "@/lib/api-keys";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
name: z.string().min(1, "Name is required"),
|
name: apiKeyNameSchema,
|
||||||
prefix: z.string().optional(),
|
prefix: z.string().optional(),
|
||||||
expiresIn: z.number().nullable(),
|
expiresIn: z.number().nullable(),
|
||||||
organizationId: z.string().min(1, "Organization is required"),
|
organizationId: z.string().min(1, "Organization is required"),
|
||||||
@@ -159,8 +160,15 @@ export const AddApiKey = () => {
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Name</FormLabel>
|
<FormLabel>Name</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="My API Key" {...field} />
|
<Input
|
||||||
|
placeholder="My API Key"
|
||||||
|
maxLength={API_KEY_NAME_MAX_LENGTH}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
Maximum {API_KEY_NAME_MAX_LENGTH} characters
|
||||||
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
|
|||||||
23
apps/dokploy/lib/api-keys.ts
Normal file
23
apps/dokploy/lib/api-keys.ts
Normal 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`,
|
||||||
|
);
|
||||||
@@ -35,6 +35,7 @@ import { TRPCError } from "@trpc/server";
|
|||||||
import * as bcrypt from "bcrypt";
|
import * as bcrypt from "bcrypt";
|
||||||
import { and, asc, eq, gt, ne } from "drizzle-orm";
|
import { and, asc, eq, gt, ne } from "drizzle-orm";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { apiKeyNameSchema } from "@/lib/api-keys";
|
||||||
import { audit } from "@/server/api/utils/audit";
|
import { audit } from "@/server/api/utils/audit";
|
||||||
import {
|
import {
|
||||||
adminProcedure,
|
adminProcedure,
|
||||||
@@ -45,7 +46,7 @@ import {
|
|||||||
} from "../trpc";
|
} from "../trpc";
|
||||||
|
|
||||||
const apiCreateApiKey = z.object({
|
const apiCreateApiKey = z.object({
|
||||||
name: z.string().min(1),
|
name: apiKeyNameSchema,
|
||||||
prefix: z.string().optional(),
|
prefix: z.string().optional(),
|
||||||
expiresIn: z.number().optional(),
|
expiresIn: z.number().optional(),
|
||||||
metadata: z.object({
|
metadata: z.object({
|
||||||
|
|||||||
Reference in New Issue
Block a user