mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-17 20:05:24 +02:00
Compare commits
3 Commits
v0.29.12
...
fix/watchp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f9d5242d8 | ||
|
|
7ba9818894 | ||
|
|
f577778667 |
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,
|
||||
} 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>
|
||||
)}
|
||||
|
||||
@@ -5,7 +5,7 @@ import type * as React from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const badgeVariants = cva(
|
||||
"group/badge inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2.5 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
|
||||
"group/badge inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2.5 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg:not(.cursor-pointer)]:pointer-events-none [&>svg]:size-3!",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
|
||||
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 { 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({
|
||||
|
||||
Reference in New Issue
Block a user