Skip to content

Commit 72ba9c4

Browse files
committed
feat: added safeguard for website creation
1 parent a98b4ea commit 72ba9c4

3 files changed

Lines changed: 50 additions & 10 deletions

File tree

apps/api/src/schemas/website.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ export const createWebsiteRequestSchema = z.object({
66
description: "The website's name.",
77
example: "Dub",
88
}),
9-
domain: z.string().url().openapi({
10-
description: "The website's domain.",
11-
example: "https://dub.co",
12-
}),
9+
domain: z
10+
.string()
11+
.regex(/^[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*$/)
12+
.openapi({
13+
description: "The website's domain.",
14+
example: "dub.co",
15+
}),
1316
organizationId: z.string().ulid().openapi({
1417
description: "The organization's unique identifier.",
1518
example: "01JG000000000000000000000",

apps/api/src/trpc/routers/website.ts

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import {
55
createWebsiteResponseSchema,
66
} from "@api/schemas/website";
77
import { createDefaultWebsiteKeys } from "@api/db/queries/api-keys";
8-
import { eq } from "drizzle-orm";
8+
import { and, eq } from "drizzle-orm";
99
import { nanoid } from "nanoid";
10+
import { TRPCError } from "@trpc/server";
1011

1112
export const websiteRouter = createTRPCRouter({
1213
create: protectedProcedure
@@ -16,22 +17,49 @@ export const websiteRouter = createTRPCRouter({
1617
let slug = input.name.trim().toLowerCase().replace(/ /g, "-");
1718

1819
// Check if website with same slug already exists
19-
const existingWebsite = await db.query.website.findFirst({
20-
where: eq(website.slug, slug),
21-
});
20+
const [existingSlugWebsite, existingDomainWebsite] = await Promise.all([
21+
db.query.website.findFirst({
22+
where: eq(website.slug, slug),
23+
}),
24+
db.query.website.findFirst({
25+
where: and(
26+
eq(website.domain, input.domain),
27+
eq(website.isDomainOwnershipVerified, true)
28+
),
29+
}),
30+
]);
2231

2332
// If website with same slug already exists, add a random suffix to the slug
24-
if (existingWebsite) {
33+
if (existingSlugWebsite) {
2534
slug = `${slug}-${nanoid(4)}`;
2635
}
2736

37+
// If website with same verified domain already exists, error, the user cannot use that domain
38+
if (existingDomainWebsite) {
39+
throw new TRPCError({
40+
code: "BAD_REQUEST",
41+
message: "Domain already in use by another website",
42+
});
43+
}
44+
45+
const userEmailDomain = user.email.split("@")[1];
46+
47+
// TODO: Add a better verification process for domain ownership
48+
// If the user's email domain is the same as the website domain, we can assume that the user owns the domain for now
49+
const isDomainOwnershipVerified = userEmailDomain === input.domain;
50+
2851
const [createdWebsite] = await db
2952
.insert(website)
3053
.values({
3154
name: input.name,
3255
organizationId: input.organizationId,
3356
installationTarget: input.installationTarget,
34-
whitelistedDomains: [input.domain, "http://localhost:3000"],
57+
domain: input.domain,
58+
isDomainOwnershipVerified,
59+
whitelistedDomains: [
60+
`https://${input.domain}`,
61+
"http://localhost:3000",
62+
],
3563
slug,
3664
})
3765
.returning();

packages/database/src/schema/chat.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ export const website = pgTable(
5252
id: text("id").primaryKey().$defaultFn(generatePrimaryId),
5353
name: text("name").notNull(),
5454
slug: text("slug").notNull().unique(),
55+
domain: text("domain").notNull(),
56+
isDomainOwnershipVerified: boolean("is_domain_ownership_verified")
57+
.default(false)
58+
.notNull(),
5559
description: text("description"),
5660
logoUrl: text("logo_url"),
5761
whitelistedDomains: text("whitelisted_domains").array().notNull(),
@@ -76,6 +80,11 @@ export const website = pgTable(
7680
// Index for soft delete queries
7781
index("website_deleted_at_idx").on(table.deletedAt),
7882
index("website_slug_idx").on(table.slug),
83+
index("website_domain_idx").on(table.domain),
84+
index("website_org_domain_idx").on(
85+
table.isDomainOwnershipVerified,
86+
table.domain
87+
),
7988
]
8089
);
8190

0 commit comments

Comments
 (0)