diff --git a/messages/en-US.json b/messages/en-US.json index 35e88375..44d980c5 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1031,6 +1031,7 @@ "pangolinSetup": "Setup - Pangolin", "orgNameRequired": "Organization name is required", "orgIdRequired": "Organization ID is required", + "orgIdMaxLength": "Organization ID must be at most 32 characters", "orgErrorCreate": "An error occurred while creating org", "pageNotFound": "Page Not Found", "pageNotFoundDescription": "Oops! The page you're looking for doesn't exist.", diff --git a/server/routers/org/createOrg.ts b/server/routers/org/createOrg.ts index e58f8207..59aa86d2 100644 --- a/server/routers/org/createOrg.ts +++ b/server/routers/org/createOrg.ts @@ -31,8 +31,17 @@ import { doCidrsOverlap } from "@server/lib/ip"; import { generateCA } from "@server/private/lib/sshCA"; import { encrypt } from "@server/lib/crypto"; +const validOrgIdRegex = /^[a-z0-9_]+(-[a-z0-9_]+)*$/; + const createOrgSchema = z.strictObject({ - orgId: z.string(), + orgId: z + .string() + .min(1, "Organization ID is required") + .max(32, "Organization ID must be at most 32 characters") + .refine((val) => validOrgIdRegex.test(val), { + message: + "Organization ID must contain only lowercase letters, numbers, underscores, and single hyphens (no leading, trailing, or consecutive hyphens)" + }), name: z.string().min(1).max(255), subnet: z // .union([z.cidrv4(), z.cidrv6()]) diff --git a/src/app/setup/page.tsx b/src/app/setup/page.tsx index dc505b67..c7e6de6a 100644 --- a/src/app/setup/page.tsx +++ b/src/app/setup/page.tsx @@ -50,7 +50,10 @@ export default function StepperForm() { const orgSchema = z.object({ orgName: z.string().min(1, { message: t("orgNameRequired") }), - orgId: z.string().min(1, { message: t("orgIdRequired") }), + orgId: z + .string() + .min(1, { message: t("orgIdRequired") }) + .max(32, { message: t("orgIdMaxLength") }), subnet: z.string().min(1, { message: t("subnetRequired") }), utilitySubnet: z.string().min(1, { message: t("subnetRequired") }) }); @@ -140,6 +143,16 @@ export default function StepperForm() { .replace(/^-+|-+$/g, ""); }; + const sanitizeOrgId = (value: string) => { + return value + .toLowerCase() + .replace(/\s+/g, "-") + .replace(/[^a-z0-9_-]/g, "") + .replace(/-+/g, "-") + .replace(/^-+|-+$/g, "") + .slice(0, 32); + }; + async function orgSubmit(values: z.infer) { if (orgIdTaken) { return; @@ -303,7 +316,22 @@ export default function StepperForm() { {t("orgId")} - + { + const value = sanitizeOrgId( + e.target.value + ); + field.onChange(value); + setOrgIdTaken(false); + if (value) { + debouncedCheckOrgIdAvailability( + value + ); + } + }} + /> diff --git a/src/components/OrgSelector.tsx b/src/components/OrgSelector.tsx index f5351362..cacaf553 100644 --- a/src/components/OrgSelector.tsx +++ b/src/components/OrgSelector.tsx @@ -154,22 +154,22 @@ export function OrgSelector({
-
- - {org.name} + + {org.name} + +
+ + {org.orgId} {org.isPrimaryOrg && ( {t("primary")} )}
- - {org.orgId} -