mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-11 17:05:25 +02:00
Merge pull request #2498 from Dokploy/2456-cannot-back-up-mariadb-database-access-denied-error
feat(database): enhance password validation for database schemas and …
This commit is contained in:
11
.github/pull_request_template.md
vendored
11
.github/pull_request_template.md
vendored
@@ -6,16 +6,13 @@ Please describe in a short paragraph what this PR is about.
|
|||||||
|
|
||||||
Before submitting this PR, please make sure that:
|
Before submitting this PR, please make sure that:
|
||||||
|
|
||||||
- [ ] You created a dedicated branch based on the `canary` branch.
|
- [] You created a dedicated branch based on the `canary` branch.
|
||||||
- [ ] You have read the suggestions in the CONTRIBUTING.md file https://github.com/Dokploy/dokploy/blob/canary/CONTRIBUTING.md#pull-request
|
- [] You have read the suggestions in the CONTRIBUTING.md file https://github.com/Dokploy/dokploy/blob/canary/CONTRIBUTING.md#pull-request
|
||||||
- [ ] You have tested this PR in your local instance.
|
- [] You have tested this PR in your local instance.
|
||||||
|
|
||||||
## Issues related (if applicable)
|
## Issues related (if applicable)
|
||||||
|
|
||||||
Close automatically the related issues using the keywords: `closes #ISSUE_NUMBER`, `fixes #ISSUE_NUMBER`, `resolves #ISSUE_NUMBER`
|
closes #123
|
||||||
|
|
||||||
Example: `closes #123`
|
|
||||||
|
|
||||||
## Screenshots (if applicable)
|
## Screenshots (if applicable)
|
||||||
|
|
||||||
If you include a video or screenshot, would be awesome so we can see the changes in action.
|
|
||||||
@@ -83,7 +83,13 @@ const baseDatabaseSchema = z.object({
|
|||||||
message:
|
message:
|
||||||
"App name supports lowercase letters, numbers, '-' and can only start and end letters, and does not support continuous '-'",
|
"App name supports lowercase letters, numbers, '-' and can only start and end letters, and does not support continuous '-'",
|
||||||
}),
|
}),
|
||||||
databasePassword: z.string(),
|
databasePassword: z
|
||||||
|
.string()
|
||||||
|
.min(1, "Password is required")
|
||||||
|
.regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
|
||||||
|
message:
|
||||||
|
"Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility",
|
||||||
|
}),
|
||||||
dockerImage: z.string(),
|
dockerImage: z.string(),
|
||||||
description: z.string().nullable(),
|
description: z.string().nullable(),
|
||||||
serverId: z.string().nullable(),
|
serverId: z.string().nullable(),
|
||||||
@@ -112,7 +118,12 @@ const mySchema = z.discriminatedUnion("type", [
|
|||||||
z
|
z
|
||||||
.object({
|
.object({
|
||||||
type: z.literal("mysql"),
|
type: z.literal("mysql"),
|
||||||
databaseRootPassword: z.string().default(""),
|
databaseRootPassword: z
|
||||||
|
.string()
|
||||||
|
.regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
|
||||||
|
message:
|
||||||
|
"Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility",
|
||||||
|
}),
|
||||||
databaseUser: z.string().default("mysql"),
|
databaseUser: z.string().default("mysql"),
|
||||||
databaseName: z.string().default("mysql"),
|
databaseName: z.string().default("mysql"),
|
||||||
})
|
})
|
||||||
@@ -121,7 +132,12 @@ const mySchema = z.discriminatedUnion("type", [
|
|||||||
.object({
|
.object({
|
||||||
type: z.literal("mariadb"),
|
type: z.literal("mariadb"),
|
||||||
dockerImage: z.string().default("mariadb:4"),
|
dockerImage: z.string().default("mariadb:4"),
|
||||||
databaseRootPassword: z.string().default(""),
|
databaseRootPassword: z
|
||||||
|
.string()
|
||||||
|
.regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
|
||||||
|
message:
|
||||||
|
"Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility",
|
||||||
|
}),
|
||||||
databaseUser: z.string().default("mariadb"),
|
databaseUser: z.string().default("mariadb"),
|
||||||
databaseName: z.string().default("mariadb"),
|
databaseName: z.string().default("mariadb"),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,25 +1,16 @@
|
|||||||
import copy from "copy-to-clipboard";
|
import copy from "copy-to-clipboard";
|
||||||
import { Clipboard, EyeIcon, EyeOffIcon } from "lucide-react";
|
import { Clipboard } from "lucide-react";
|
||||||
import { useRef, useState } from "react";
|
import { useRef } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { Input, type InputProps } from "../ui/input";
|
import { Input, type InputProps } from "../ui/input";
|
||||||
|
|
||||||
export const ToggleVisibilityInput = ({ ...props }: InputProps) => {
|
export const ToggleVisibilityInput = ({ ...props }: InputProps) => {
|
||||||
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const togglePasswordVisibility = () => {
|
|
||||||
setIsPasswordVisible((prevVisibility) => !prevVisibility);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full items-center space-x-2">
|
<div className="flex w-full items-center space-x-2">
|
||||||
<Input
|
<Input ref={inputRef} type={"password"} {...props} />
|
||||||
ref={inputRef}
|
|
||||||
type={isPasswordVisible ? "text" : "password"}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
<Button
|
<Button
|
||||||
variant={"secondary"}
|
variant={"secondary"}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -29,13 +20,13 @@ export const ToggleVisibilityInput = ({ ...props }: InputProps) => {
|
|||||||
>
|
>
|
||||||
<Clipboard className="size-4 text-muted-foreground" />
|
<Clipboard className="size-4 text-muted-foreground" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={togglePasswordVisibility} variant={"secondary"}>
|
{/* <Button onClick={togglePasswordVisibility} variant={"secondary"}>
|
||||||
{isPasswordVisible ? (
|
{isPasswordVisible ? (
|
||||||
<EyeOffIcon className="size-4 text-muted-foreground" />
|
<EyeOffIcon className="size-4 text-muted-foreground" />
|
||||||
) : (
|
) : (
|
||||||
<EyeIcon className="size-4 text-muted-foreground" />
|
<EyeIcon className="size-4 text-muted-foreground" />
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button> */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { EyeIcon, EyeOffIcon } from "lucide-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
@@ -8,18 +9,39 @@ export interface InputProps
|
|||||||
|
|
||||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||||
({ className, errorMessage, type, ...props }, ref) => {
|
({ className, errorMessage, type, ...props }, ref) => {
|
||||||
|
const [showPassword, setShowPassword] = React.useState(false);
|
||||||
|
const isPassword = type === "password";
|
||||||
|
const inputType = isPassword ? (showPassword ? "text" : "password") : type;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<input
|
<div className="relative w-full">
|
||||||
type={type}
|
<input
|
||||||
className={cn(
|
type={inputType}
|
||||||
// bg-gray
|
className={cn(
|
||||||
"flex h-10 w-full rounded-md bg-input px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border disabled:cursor-not-allowed disabled:opacity-50",
|
// bg-gray
|
||||||
className,
|
"flex h-10 w-full rounded-md bg-input px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
isPassword && "pr-10", // Add padding for the eye icon
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
{isPassword && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="absolute inset-y-0 right-0 flex items-center pr-3 text-muted-foreground hover:text-foreground focus:outline-none"
|
||||||
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
|
tabIndex={-1}
|
||||||
|
>
|
||||||
|
{showPassword ? (
|
||||||
|
<EyeOffIcon className="h-4 w-4" />
|
||||||
|
) : (
|
||||||
|
<EyeIcon className="h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
</div>
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
{errorMessage && (
|
{errorMessage && (
|
||||||
<span className="text-sm text-red-600 text-secondary-foreground">
|
<span className="text-sm text-red-600 text-secondary-foreground">
|
||||||
{errorMessage}
|
{errorMessage}
|
||||||
|
|||||||
@@ -94,8 +94,20 @@ const createSchema = createInsertSchema(mariadb, {
|
|||||||
createdAt: z.string(),
|
createdAt: z.string(),
|
||||||
databaseName: z.string().min(1),
|
databaseName: z.string().min(1),
|
||||||
databaseUser: z.string().min(1),
|
databaseUser: z.string().min(1),
|
||||||
databasePassword: z.string(),
|
databasePassword: z
|
||||||
databaseRootPassword: z.string().optional(),
|
.string()
|
||||||
|
.min(1, "Password is required")
|
||||||
|
.regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
|
||||||
|
message:
|
||||||
|
"Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility",
|
||||||
|
}),
|
||||||
|
databaseRootPassword: z
|
||||||
|
.string()
|
||||||
|
.regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
|
||||||
|
message:
|
||||||
|
"Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility",
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
dockerImage: z.string().default("mariadb:6"),
|
dockerImage: z.string().default("mariadb:6"),
|
||||||
command: z.string().optional(),
|
command: z.string().optional(),
|
||||||
env: z.string().optional(),
|
env: z.string().optional(),
|
||||||
|
|||||||
@@ -89,7 +89,13 @@ const createSchema = createInsertSchema(mongo, {
|
|||||||
createdAt: z.string(),
|
createdAt: z.string(),
|
||||||
mongoId: z.string(),
|
mongoId: z.string(),
|
||||||
name: z.string().min(1),
|
name: z.string().min(1),
|
||||||
databasePassword: z.string(),
|
databasePassword: z
|
||||||
|
.string()
|
||||||
|
.min(1, "Password is required")
|
||||||
|
.regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
|
||||||
|
message:
|
||||||
|
"Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility",
|
||||||
|
}),
|
||||||
databaseUser: z.string().min(1),
|
databaseUser: z.string().min(1),
|
||||||
dockerImage: z.string().default("mongo:15"),
|
dockerImage: z.string().default("mongo:15"),
|
||||||
command: z.string().optional(),
|
command: z.string().optional(),
|
||||||
|
|||||||
@@ -92,8 +92,20 @@ const createSchema = createInsertSchema(mysql, {
|
|||||||
name: z.string().min(1),
|
name: z.string().min(1),
|
||||||
databaseName: z.string().min(1),
|
databaseName: z.string().min(1),
|
||||||
databaseUser: z.string().min(1),
|
databaseUser: z.string().min(1),
|
||||||
databasePassword: z.string(),
|
databasePassword: z
|
||||||
databaseRootPassword: z.string().optional(),
|
.string()
|
||||||
|
.min(1, "Password is required")
|
||||||
|
.regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
|
||||||
|
message:
|
||||||
|
"Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility",
|
||||||
|
}),
|
||||||
|
databaseRootPassword: z
|
||||||
|
.string()
|
||||||
|
.regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
|
||||||
|
message:
|
||||||
|
"Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility",
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
dockerImage: z.string().default("mysql:8"),
|
dockerImage: z.string().default("mysql:8"),
|
||||||
command: z.string().optional(),
|
command: z.string().optional(),
|
||||||
env: z.string().optional(),
|
env: z.string().optional(),
|
||||||
|
|||||||
@@ -88,7 +88,13 @@ export const postgresRelations = relations(postgres, ({ one, many }) => ({
|
|||||||
const createSchema = createInsertSchema(postgres, {
|
const createSchema = createInsertSchema(postgres, {
|
||||||
postgresId: z.string(),
|
postgresId: z.string(),
|
||||||
name: z.string().min(1),
|
name: z.string().min(1),
|
||||||
databasePassword: z.string(),
|
databasePassword: z
|
||||||
|
.string()
|
||||||
|
.min(1, "Password is required")
|
||||||
|
.regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
|
||||||
|
message:
|
||||||
|
"Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility",
|
||||||
|
}),
|
||||||
databaseName: z.string().min(1),
|
databaseName: z.string().min(1),
|
||||||
databaseUser: z.string().min(1),
|
databaseUser: z.string().min(1),
|
||||||
dockerImage: z.string().default("postgres:15"),
|
dockerImage: z.string().default("postgres:15"),
|
||||||
|
|||||||
Reference in New Issue
Block a user