Skip to content

Commit 1b60270

Browse files
bchapuisclaude
andcommitted
Add identifier validation to database names
Apply the same IDENTIFIER_PATTERN validation to database names in both the backend routes and the frontend create dialog, with red border and inline error message for invalid input. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 043a073 commit 1b60270

4 files changed

Lines changed: 71 additions & 24 deletions

File tree

apps/api/src/routes/databases.ts

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1-
import type {
2-
CreateDatabaseRequest,
3-
CreateDatabaseResponse,
4-
DatabaseQueryRequest,
5-
DatabaseQueryResponse,
6-
DatabaseSchemaColumn,
7-
DatabaseSchemaForeignKey,
8-
DatabaseSchemaResponse,
9-
DatabaseSchemaTable,
10-
DeleteDatabaseResponse,
11-
GetDatabaseResponse,
12-
ListDatabasesResponse,
13-
UpdateDatabaseRequest,
14-
UpdateDatabaseResponse,
1+
import {
2+
type CreateDatabaseRequest,
3+
type CreateDatabaseResponse,
4+
type DatabaseQueryRequest,
5+
type DatabaseQueryResponse,
6+
type DatabaseSchemaColumn,
7+
type DatabaseSchemaForeignKey,
8+
type DatabaseSchemaResponse,
9+
type DatabaseSchemaTable,
10+
type DeleteDatabaseResponse,
11+
type GetDatabaseResponse,
12+
IDENTIFIER_PATTERN,
13+
type ListDatabasesResponse,
14+
type UpdateDatabaseRequest,
15+
type UpdateDatabaseResponse,
1516
} from "@dafthunk/types";
1617
import { zValidator } from "@hono/zod-validator";
1718
import { Hono } from "hono";
@@ -64,7 +65,13 @@ databaseRoutes.post(
6465
zValidator(
6566
"json",
6667
z.object({
67-
name: z.string().min(1, "Database name is required"),
68+
name: z
69+
.string()
70+
.min(1, "Database name is required")
71+
.regex(
72+
IDENTIFIER_PATTERN,
73+
"Must start with a letter or underscore, and contain only letters, digits, or underscores"
74+
),
6875
}) as z.ZodType<CreateDatabaseRequest>
6976
),
7077
async (c) => {
@@ -126,7 +133,13 @@ databaseRoutes.put(
126133
zValidator(
127134
"json",
128135
z.object({
129-
name: z.string().min(1, "Database name is required"),
136+
name: z
137+
.string()
138+
.min(1, "Database name is required")
139+
.regex(
140+
IDENTIFIER_PATTERN,
141+
"Must start with a letter or underscore, and contain only letters, digits, or underscores"
142+
),
130143
}) as z.ZodType<UpdateDatabaseRequest>
131144
),
132145
async (c) => {

apps/api/src/routes/schemas.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import {
2-
IDENTIFIER_PATTERN,
32
type CreateSchemaResponse,
43
type DeleteSchemaResponse,
54
type GetSchemaResponse,
5+
IDENTIFIER_PATTERN,
66
type ListSchemasResponse,
77
type SchemaEntity,
88
type UpdateSchemaResponse,

apps/app/src/components/schema-dialog.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { IDENTIFIER_PATTERN, type Field, type FieldType, type SchemaEntity } from "@dafthunk/types";
1+
import {
2+
type Field,
3+
type FieldType,
4+
IDENTIFIER_PATTERN,
5+
type SchemaEntity,
6+
} from "@dafthunk/types";
27
import Asterisk from "lucide-react/icons/asterisk";
38
import Hash from "lucide-react/icons/hash";
49
import KeyRound from "lucide-react/icons/key-round";

apps/app/src/pages/databases-page.tsx

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { IDENTIFIER_PATTERN } from "@dafthunk/types";
12
import { ColumnDef } from "@tanstack/react-table";
23
import MoreHorizontal from "lucide-react/icons/more-horizontal";
34
import PlusCircle from "lucide-react/icons/plus-circle";
@@ -34,6 +35,7 @@ import {
3435
deleteDatabase,
3536
useDatabases,
3637
} from "@/services/database-service";
38+
import { cn } from "@/utils/utils";
3739

3840
function useDatabaseActions() {
3941
const { mutateDatabases } = useDatabases();
@@ -151,6 +153,7 @@ function createColumns(
151153

152154
export function DatabasesPage() {
153155
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
156+
const [newDatabaseName, setNewDatabaseName] = useState("");
154157
const { setBreadcrumbs } = usePageBreadcrumbs([]);
155158
const { organization } = useAuth();
156159
const orgId = organization?.id || "";
@@ -206,17 +209,22 @@ export function DatabasesPage() {
206209
description: "Create a new database to get started.",
207210
}}
208211
/>
209-
<Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
212+
<Dialog
213+
open={isCreateDialogOpen}
214+
onOpenChange={(open) => {
215+
setIsCreateDialogOpen(open);
216+
if (!open) setNewDatabaseName("");
217+
}}
218+
>
210219
<DialogContent>
211220
<DialogHeader>
212221
<DialogTitle>Create New Database</DialogTitle>
213222
</DialogHeader>
214223
<form
215224
onSubmit={async (e) => {
216225
e.preventDefault();
217-
const formData = new FormData(e.currentTarget);
218-
const name = formData.get("name") as string;
219-
await handleCreateDatabase(name);
226+
await handleCreateDatabase(newDatabaseName.trim());
227+
setNewDatabaseName("");
220228
}}
221229
className="space-y-4"
222230
>
@@ -225,9 +233,22 @@ export function DatabasesPage() {
225233
<Input
226234
id="name"
227235
name="name"
236+
value={newDatabaseName}
237+
onChange={(e) => setNewDatabaseName(e.target.value)}
228238
placeholder="Enter database name"
229-
className="mt-2"
239+
className={cn(
240+
"mt-2",
241+
newDatabaseName.trim().length > 0 &&
242+
!IDENTIFIER_PATTERN.test(newDatabaseName.trim()) &&
243+
"border-destructive"
244+
)}
230245
/>
246+
{newDatabaseName.trim().length > 0 &&
247+
!IDENTIFIER_PATTERN.test(newDatabaseName.trim()) && (
248+
<p className="text-xs text-destructive mt-1">
249+
Letters, digits, and underscores only (e.g. my_database)
250+
</p>
251+
)}
231252
</div>
232253
<DialogFooter>
233254
<Button
@@ -237,7 +258,15 @@ export function DatabasesPage() {
237258
>
238259
Cancel
239260
</Button>
240-
<Button type="submit">Create Database</Button>
261+
<Button
262+
type="submit"
263+
disabled={
264+
newDatabaseName.trim().length === 0 ||
265+
!IDENTIFIER_PATTERN.test(newDatabaseName.trim())
266+
}
267+
>
268+
Create Database
269+
</Button>
241270
</DialogFooter>
242271
</form>
243272
</DialogContent>

0 commit comments

Comments
 (0)