From 20f659db8955475ab278bc5c0aafe45c0a7da014 Mon Sep 17 00:00:00 2001 From: Milo Schwartz Date: Wed, 29 Jan 2025 00:03:10 -0500 Subject: [PATCH] fix zod schemas --- config/config.example.yml | 2 +- config/traefik/traefik_config.example.yml | 2 +- server/routers/resource/createResource.ts | 53 ++++---- server/routers/traefik/getTraefikConfig.ts | 4 +- .../settings/resources/CreateResourceForm.tsx | 39 +++--- .../[resourceId]/connectivity/page.tsx | 123 +++++++++--------- .../resources/[resourceId]/general/page.tsx | 57 +++++--- 7 files changed, 158 insertions(+), 122 deletions(-) diff --git a/config/config.example.yml b/config/config.example.yml index 788b5943..e62af16d 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -41,4 +41,4 @@ flags: require_email_verification: false disable_signup_without_invite: true disable_user_create_org: true - allow_raw_resources: true \ No newline at end of file + allow_raw_resources: true diff --git a/config/traefik/traefik_config.example.yml b/config/traefik/traefik_config.example.yml index 8a2263ad..01d05903 100644 --- a/config/traefik/traefik_config.example.yml +++ b/config/traefik/traefik_config.example.yml @@ -13,7 +13,7 @@ experimental: plugins: badger: moduleName: "github.com/fosrl/badger" - version: "v1.0.0-beta.2" + version: "v1.0.0-beta.3" log: level: "INFO" diff --git a/server/routers/resource/createResource.ts b/server/routers/resource/createResource.ts index ee392e5f..e687cc02 100644 --- a/server/routers/resource/createResource.ts +++ b/server/routers/resource/createResource.ts @@ -17,6 +17,7 @@ import { eq, and } from "drizzle-orm"; import stoi from "@server/lib/stoi"; import { fromError } from "zod-validation-error"; import logger from "@server/logger"; +import { subdomainSchema } from "@server/schemas/subdomainSchema"; const createResourceParamsSchema = z .object({ @@ -27,36 +28,43 @@ const createResourceParamsSchema = z const createResourceSchema = z .object({ - subdomain: z - .union([ - z - .string() - .regex( - /^(?!:\/\/)([a-zA-Z0-9-_]+\.)*[a-zA-Z0-9-_]+$/, - "Invalid subdomain format" - ) - .min(1, "Subdomain must be at least 1 character long") - .transform((val) => val.toLowerCase()), - z.string().optional() - ]) - .optional(), + subdomain: z.string().optional(), name: z.string().min(1).max(255), + siteId: z.number(), http: z.boolean(), protocol: z.string(), - proxyPort: z.number().int().min(1).max(65535).optional(), - }).refine( + proxyPort: z.number().optional() + }) + .refine( (data) => { - if (data.http === true) { - return true; + if (!data.http) { + return z + .number() + .int() + .min(1) + .max(65535) + .safeParse(data.proxyPort).success; } - return !!data.proxyPort; + return true; }, { - message: "Port number is required for non-HTTP resources", + message: "Invalid port number", path: ["proxyPort"] } + ) + .refine( + (data) => { + if (data.http) { + return subdomainSchema.safeParse(data.subdomain).success; + } + return true; + }, + { + message: "Invalid subdomain", + path: ["subdomain"] + } ); - + export type CreateResourceResponse = Resource; export async function createResource( @@ -134,7 +142,6 @@ export async function createResource( ); } } else { - if (proxyPort === 443 || proxyPort === 80) { return next( createHttpError( @@ -149,7 +156,7 @@ export async function createResource( .select() .from(resources) .where(eq(resources.fullDomain, fullDomain)); - + if (existingResource.length > 0) { return next( createHttpError( @@ -165,7 +172,7 @@ export async function createResource( .insert(resources) .values({ siteId, - fullDomain: http? fullDomain : null, + fullDomain: http ? fullDomain : null, orgId, name, subdomain, diff --git a/server/routers/traefik/getTraefikConfig.ts b/server/routers/traefik/getTraefikConfig.ts index 4b760274..93eddde4 100644 --- a/server/routers/traefik/getTraefikConfig.ts +++ b/server/routers/traefik/getTraefikConfig.ts @@ -156,13 +156,15 @@ export async function traefikConfigProvider( : {}) }; + const additionalMiddlewares = config.getRawConfig().traefik.additional_middlewares || []; + config_output.http.routers![routerName] = { entryPoints: [ resource.ssl ? config.getRawConfig().traefik.https_entrypoint : config.getRawConfig().traefik.http_entrypoint ], - middlewares: [badgerMiddlewareName], + middlewares: [badgerMiddlewareName, ...additionalMiddlewares], service: serviceName, rule: `Host(\`${fullDomain}\`)`, ...(resource.ssl ? { tls } : {}) diff --git a/src/app/[orgId]/settings/resources/CreateResourceForm.tsx b/src/app/[orgId]/settings/resources/CreateResourceForm.tsx index 7a111c06..d92a3792 100644 --- a/src/app/[orgId]/settings/resources/CreateResourceForm.tsx +++ b/src/app/[orgId]/settings/resources/CreateResourceForm.tsx @@ -59,38 +59,39 @@ import { SelectTrigger, SelectValue } from "@app/components/ui/select"; +import { subdomainSchema } from "@server/schemas/subdomainSchema"; const createResourceFormSchema = z .object({ - subdomain: z - .union([ - z - .string() - .regex( - /^(?!:\/\/)([a-zA-Z0-9-_]+\.)*[a-zA-Z0-9-_]+$/, - "Invalid subdomain format" - ) - .min(1, "Subdomain must be at least 1 character long") - .transform((val) => val.toLowerCase()), - z.string().optional() - ]) - .optional(), + subdomain: z.string().optional(), name: z.string().min(1).max(255), siteId: z.number(), http: z.boolean(), protocol: z.string(), - proxyPort: z.number().int().min(1).max(65535).optional() + proxyPort: z.number().optional(), }) .refine( (data) => { - if (data.http === true) { - return true; + if (!data.http) { + return z.number().int().min(1).max(65535).safeParse(data.proxyPort).success; } - return !!data.proxyPort; + return true; }, { - message: "Port number is required for non-HTTP resources", - path: ["proxyPort"] + message: "Invalid port number", + path: ["proxyPort"], + } + ) + .refine( + (data) => { + if (data.http) { + return subdomainSchema.safeParse(data.subdomain).success; + } + return true; + }, + { + message: "Invalid subdomain", + path: ["subdomain"], } ); diff --git a/src/app/[orgId]/settings/resources/[resourceId]/connectivity/page.tsx b/src/app/[orgId]/settings/resources/[resourceId]/connectivity/page.tsx index 2e38b193..c40102e3 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/connectivity/page.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/connectivity/page.tsx @@ -63,6 +63,7 @@ import { } from "@app/components/Settings"; import { SwitchInput } from "@app/components/SwitchInput"; import { useSiteContext } from "@app/hooks/useSiteContext"; +import { InfoPopup } from "@app/components/ui/info-popup"; // Regular expressions for validation const DOMAIN_REGEX = @@ -458,28 +459,28 @@ export default function ReverseProxyTargets(props: { return ( {resource.http && ( - - - - SSL Configuration - - - Setup SSL to secure your connections with LetsEncrypt - certificates - - - - { - await saveSsl(val); - }} - /> - - -)} + + + + SSL Configuration + + + Setup SSL to secure your connections with + LetsEncrypt certificates + + + + { + await saveSsl(val); + }} + /> + + + )} {/* Targets Section */} @@ -498,41 +499,45 @@ export default function ReverseProxyTargets(props: { >
{resource.http && ( - - ( - - Method - - - - - - - - http - - - https - - - - - - - )} - /> - )} + ) => { + addTargetForm.setValue( + "method", + value + ); + }} + > + + + + + + http + + + https + + + + + + + )} + /> + )} - - Multiple targets will get load balanced by Traefik. You can use this for high availability. - +

+ Adding more than one target above will enable load balancing. +