From 0cb5ee49e074a0782545f21bdcf1d5161ccb9b1c Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Sat, 4 Apr 2026 09:27:06 -0600 Subject: [PATCH] feat(password-validation): enhance password validation across database routers - Updated password validation in MariaDB, MongoDB, MySQL, Postgres, and Redis routers to enforce a regex pattern that restricts invalid characters. - Introduced a consistent error message for invalid passwords to improve user guidance and ensure database compatibility. - Refactored password validation logic in the schema files to utilize shared constants for regex and messages, promoting code reuse and maintainability. --- .../shared/update-database-password.tsx | 19 ++++++++++++------- apps/dokploy/server/api/routers/mariadb.ts | 9 ++++++++- apps/dokploy/server/api/routers/mongo.ts | 9 ++++++++- apps/dokploy/server/api/routers/mysql.ts | 9 ++++++++- apps/dokploy/server/api/routers/postgres.ts | 9 ++++++++- apps/dokploy/server/api/routers/redis.ts | 9 ++++++++- packages/server/src/db/schema/libsql.ts | 11 +++++++---- packages/server/src/db/schema/mariadb.ts | 18 +++++++++++------- packages/server/src/db/schema/mongo.ts | 13 +++++++++---- packages/server/src/db/schema/mysql.ts | 18 +++++++++++------- packages/server/src/db/schema/postgres.ts | 13 +++++++++---- packages/server/src/db/schema/utils.ts | 7 +++++++ 12 files changed, 106 insertions(+), 38 deletions(-) diff --git a/apps/dokploy/components/shared/update-database-password.tsx b/apps/dokploy/components/shared/update-database-password.tsx index 978375fd0..b22dbae7c 100644 --- a/apps/dokploy/components/shared/update-database-password.tsx +++ b/apps/dokploy/components/shared/update-database-password.tsx @@ -24,9 +24,17 @@ import { } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; +const DATABASE_PASSWORD_REGEX = /^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/; + const updatePasswordSchema = z .object({ - password: z.string().min(1, "Password is required"), + password: z + .string() + .min(1, "Password is required") + .regex(DATABASE_PASSWORD_REGEX, { + message: + "Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters", + }), confirmPassword: z.string().min(1, "Please confirm the password"), }) .refine((data) => data.password === data.confirmPassword, { @@ -63,15 +71,12 @@ export const UpdateDatabasePassword = ({ setIsOpen(false); } catch (e) { const raw = e instanceof Error ? e.message : "Error updating password"; - const noContainer = raw.match(/No running container found for \S+/); - if (noContainer) { + if (/No running container found/i.test(raw)) { setError( "The database container is not running. Please start the service before changing the password.", ); } else { - setError( - "Error updating password. Please check that the container is running and try again.", - ); + setError(raw); } } finally { setIsPending(false); @@ -101,7 +106,7 @@ export const UpdateDatabasePassword = ({ {error && {error}} - + This will change the {label.toLowerCase()} both in the running database container and in Dokploy. The container must be running for this operation to succeed. diff --git a/apps/dokploy/server/api/routers/mariadb.ts b/apps/dokploy/server/api/routers/mariadb.ts index 8a9f726e5..c049cffcb 100644 --- a/apps/dokploy/server/api/routers/mariadb.ts +++ b/apps/dokploy/server/api/routers/mariadb.ts @@ -43,6 +43,8 @@ import { apiSaveEnvironmentVariablesMariaDB, apiSaveExternalPortMariaDB, apiUpdateMariaDB, + DATABASE_PASSWORD_MESSAGE, + DATABASE_PASSWORD_REGEX, environments, mariadb as mariadbTable, projects, @@ -375,7 +377,12 @@ export const mariadbRouter = createTRPCRouter({ .input( z.object({ mariadbId: z.string().min(1), - password: z.string().min(1), + password: z + .string() + .min(1) + .regex(DATABASE_PASSWORD_REGEX, { + message: DATABASE_PASSWORD_MESSAGE, + }), type: z.enum(["user", "root"]).default("user"), }), ) diff --git a/apps/dokploy/server/api/routers/mongo.ts b/apps/dokploy/server/api/routers/mongo.ts index df91ec769..fd2165445 100644 --- a/apps/dokploy/server/api/routers/mongo.ts +++ b/apps/dokploy/server/api/routers/mongo.ts @@ -42,6 +42,8 @@ import { apiSaveEnvironmentVariablesMongo, apiSaveExternalPortMongo, apiUpdateMongo, + DATABASE_PASSWORD_MESSAGE, + DATABASE_PASSWORD_REGEX, environments, mongo as mongoTable, projects, @@ -397,7 +399,12 @@ export const mongoRouter = createTRPCRouter({ .input( z.object({ mongoId: z.string().min(1), - password: z.string().min(1), + password: z + .string() + .min(1) + .regex(DATABASE_PASSWORD_REGEX, { + message: DATABASE_PASSWORD_MESSAGE, + }), }), ) .mutation(async ({ input, ctx }) => { diff --git a/apps/dokploy/server/api/routers/mysql.ts b/apps/dokploy/server/api/routers/mysql.ts index b53af7521..286705612 100644 --- a/apps/dokploy/server/api/routers/mysql.ts +++ b/apps/dokploy/server/api/routers/mysql.ts @@ -42,6 +42,8 @@ import { apiSaveEnvironmentVariablesMySql, apiSaveExternalPortMySql, apiUpdateMySql, + DATABASE_PASSWORD_MESSAGE, + DATABASE_PASSWORD_REGEX, environments, mysql as mysqlTable, projects, @@ -394,7 +396,12 @@ export const mysqlRouter = createTRPCRouter({ .input( z.object({ mysqlId: z.string().min(1), - password: z.string().min(1), + password: z + .string() + .min(1) + .regex(DATABASE_PASSWORD_REGEX, { + message: DATABASE_PASSWORD_MESSAGE, + }), type: z.enum(["user", "root"]).default("user"), }), ) diff --git a/apps/dokploy/server/api/routers/postgres.ts b/apps/dokploy/server/api/routers/postgres.ts index 438363915..0cb7a3812 100644 --- a/apps/dokploy/server/api/routers/postgres.ts +++ b/apps/dokploy/server/api/routers/postgres.ts @@ -43,6 +43,8 @@ import { apiSaveEnvironmentVariablesPostgres, apiSaveExternalPortPostgres, apiUpdatePostgres, + DATABASE_PASSWORD_MESSAGE, + DATABASE_PASSWORD_REGEX, environments, postgres as postgresTable, projects, @@ -403,7 +405,12 @@ export const postgresRouter = createTRPCRouter({ .input( z.object({ postgresId: z.string().min(1), - password: z.string().min(1), + password: z + .string() + .min(1) + .regex(DATABASE_PASSWORD_REGEX, { + message: DATABASE_PASSWORD_MESSAGE, + }), }), ) .mutation(async ({ input, ctx }) => { diff --git a/apps/dokploy/server/api/routers/redis.ts b/apps/dokploy/server/api/routers/redis.ts index 19c1ab277..3d8ecf674 100644 --- a/apps/dokploy/server/api/routers/redis.ts +++ b/apps/dokploy/server/api/routers/redis.ts @@ -41,6 +41,8 @@ import { apiSaveEnvironmentVariablesRedis, apiSaveExternalPortRedis, apiUpdateRedis, + DATABASE_PASSWORD_MESSAGE, + DATABASE_PASSWORD_REGEX, environments, projects, redis as redisTable, @@ -384,7 +386,12 @@ export const redisRouter = createTRPCRouter({ .input( z.object({ redisId: z.string().min(1), - password: z.string().min(1), + password: z + .string() + .min(1) + .regex(DATABASE_PASSWORD_REGEX, { + message: DATABASE_PASSWORD_MESSAGE, + }), }), ) .mutation(async ({ input, ctx }) => { diff --git a/packages/server/src/db/schema/libsql.ts b/packages/server/src/db/schema/libsql.ts index 770ed2355..8245b95f4 100644 --- a/packages/server/src/db/schema/libsql.ts +++ b/packages/server/src/db/schema/libsql.ts @@ -34,7 +34,11 @@ import { type UpdateConfigSwarm, UpdateConfigSwarmSchema, } from "./shared"; -import { generateAppName } from "./utils"; +import { + DATABASE_PASSWORD_MESSAGE, + DATABASE_PASSWORD_REGEX, + generateAppName, +} from "./utils"; export const libsql = pgTable("libsql", { libsqlId: text("libsqlId") @@ -111,9 +115,8 @@ const createSchema = createInsertSchema(libsql, { databaseUser: z.string().min(1), databasePassword: z .string() - .regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, { - message: - "Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility", + .regex(DATABASE_PASSWORD_REGEX, { + message: DATABASE_PASSWORD_MESSAGE, }), sqldNode: z.enum(sqldNode.enumValues), sqldPrimaryUrl: z.string().nullable(), diff --git a/packages/server/src/db/schema/mariadb.ts b/packages/server/src/db/schema/mariadb.ts index 2659c2978..b3ff29ada 100644 --- a/packages/server/src/db/schema/mariadb.ts +++ b/packages/server/src/db/schema/mariadb.ts @@ -28,7 +28,13 @@ import { type UpdateConfigSwarm, UpdateConfigSwarmSchema, } from "./shared"; -import { APP_NAME_MESSAGE, APP_NAME_REGEX, generateAppName } from "./utils"; +import { + APP_NAME_MESSAGE, + APP_NAME_REGEX, + DATABASE_PASSWORD_MESSAGE, + DATABASE_PASSWORD_REGEX, + generateAppName, +} from "./utils"; export const mariadb = pgTable("mariadb", { mariadbId: text("mariadbId") @@ -110,15 +116,13 @@ const createSchema = createInsertSchema(mariadb, { databaseUser: z.string().min(1), databasePassword: z .string() - .regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, { - message: - "Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility", + .regex(DATABASE_PASSWORD_REGEX, { + message: DATABASE_PASSWORD_MESSAGE, }), databaseRootPassword: z .string() - .regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, { - message: - "Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility", + .regex(DATABASE_PASSWORD_REGEX, { + message: DATABASE_PASSWORD_MESSAGE, }) .optional(), dockerImage: z.string().default("mariadb:6"), diff --git a/packages/server/src/db/schema/mongo.ts b/packages/server/src/db/schema/mongo.ts index 4599cedb2..af9d5b12f 100644 --- a/packages/server/src/db/schema/mongo.ts +++ b/packages/server/src/db/schema/mongo.ts @@ -35,7 +35,13 @@ import { type UpdateConfigSwarm, UpdateConfigSwarmSchema, } from "./shared"; -import { APP_NAME_MESSAGE, APP_NAME_REGEX, generateAppName } from "./utils"; +import { + APP_NAME_MESSAGE, + APP_NAME_REGEX, + DATABASE_PASSWORD_MESSAGE, + DATABASE_PASSWORD_REGEX, + generateAppName, +} from "./utils"; export const mongo = pgTable("mongo", { mongoId: text("mongoId") @@ -112,9 +118,8 @@ const createSchema = createInsertSchema(mongo, { name: z.string().min(1), databasePassword: z .string() - .regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, { - message: - "Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility", + .regex(DATABASE_PASSWORD_REGEX, { + message: DATABASE_PASSWORD_MESSAGE, }), databaseUser: z.string().min(1), dockerImage: z.string().default("mongo:15"), diff --git a/packages/server/src/db/schema/mysql.ts b/packages/server/src/db/schema/mysql.ts index 90b38e6fa..618a78b4f 100644 --- a/packages/server/src/db/schema/mysql.ts +++ b/packages/server/src/db/schema/mysql.ts @@ -28,7 +28,13 @@ import { type UpdateConfigSwarm, UpdateConfigSwarmSchema, } from "./shared"; -import { APP_NAME_MESSAGE, APP_NAME_REGEX, generateAppName } from "./utils"; +import { + APP_NAME_MESSAGE, + APP_NAME_REGEX, + DATABASE_PASSWORD_MESSAGE, + DATABASE_PASSWORD_REGEX, + generateAppName, +} from "./utils"; export const mysql = pgTable("mysql", { mysqlId: text("mysqlId") @@ -108,15 +114,13 @@ const createSchema = createInsertSchema(mysql, { databaseUser: z.string().min(1), databasePassword: z .string() - .regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, { - message: - "Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility", + .regex(DATABASE_PASSWORD_REGEX, { + message: DATABASE_PASSWORD_MESSAGE, }), databaseRootPassword: z .string() - .regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, { - message: - "Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility", + .regex(DATABASE_PASSWORD_REGEX, { + message: DATABASE_PASSWORD_MESSAGE, }) .optional(), dockerImage: z.string().default("mysql:8"), diff --git a/packages/server/src/db/schema/postgres.ts b/packages/server/src/db/schema/postgres.ts index 5cb3015ce..13d35ddef 100644 --- a/packages/server/src/db/schema/postgres.ts +++ b/packages/server/src/db/schema/postgres.ts @@ -28,7 +28,13 @@ import { type UpdateConfigSwarm, UpdateConfigSwarmSchema, } from "./shared"; -import { APP_NAME_MESSAGE, APP_NAME_REGEX, generateAppName } from "./utils"; +import { + APP_NAME_MESSAGE, + APP_NAME_REGEX, + DATABASE_PASSWORD_MESSAGE, + DATABASE_PASSWORD_REGEX, + generateAppName, +} from "./utils"; export const postgres = pgTable("postgres", { postgresId: text("postgresId") @@ -105,9 +111,8 @@ const createSchema = createInsertSchema(postgres, { .optional(), databasePassword: z .string() - .regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, { - message: - "Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility", + .regex(DATABASE_PASSWORD_REGEX, { + message: DATABASE_PASSWORD_MESSAGE, }), databaseName: z.string().min(1), databaseUser: z.string().min(1), diff --git a/packages/server/src/db/schema/utils.ts b/packages/server/src/db/schema/utils.ts index 811d3f767..30babea6d 100644 --- a/packages/server/src/db/schema/utils.ts +++ b/packages/server/src/db/schema/utils.ts @@ -12,6 +12,13 @@ export const APP_NAME_REGEX = /^[a-zA-Z0-9._-]+$/; export const APP_NAME_MESSAGE = "App name can only contain letters, numbers, dots, underscores and hyphens"; +/** Database password: blocks shell-dangerous characters like $ ! ' " \ / and spaces. */ +export const DATABASE_PASSWORD_REGEX = + /^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/; + +export const DATABASE_PASSWORD_MESSAGE = + "Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility"; + export const generateAppName = (type: string) => { const verb = faker.hacker.verb().replace(/ /g, "-"); const adjective = faker.hacker.adjective().replace(/ /g, "-");