feat(password-update): enhance password update functionality across database routers

- Added confirmation password field and validation to the `UpdateDatabasePassword` component.
- Refactored password update logic in MariaDB, MongoDB, MySQL, Postgres, and Redis routers to utilize database transactions for improved reliability.
- Ensured consistent handling of password updates across all database types, enhancing user experience and security.
This commit is contained in:
Mauricio Siu
2026-04-04 09:19:29 -06:00
parent 1506d8f21e
commit 3d838aa074
6 changed files with 92 additions and 45 deletions

View File

@@ -24,9 +24,15 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
const updatePasswordSchema = z.object({
password: z.string().min(1, "Password is required"),
});
const updatePasswordSchema = z
.object({
password: z.string().min(1, "Password is required"),
confirmPassword: z.string().min(1, "Please confirm the password"),
})
.refine((data) => data.password === data.confirmPassword, {
message: "Passwords do not match",
path: ["confirmPassword"],
});
type UpdatePassword = z.infer<typeof updatePasswordSchema>;
@@ -44,7 +50,7 @@ export const UpdateDatabasePassword = ({
const [isPending, setIsPending] = useState(false);
const form = useForm<UpdatePassword>({
defaultValues: { password: "" },
defaultValues: { password: "", confirmPassword: "" },
resolver: zodResolver(updatePasswordSchema),
});
@@ -122,6 +128,23 @@ export const UpdateDatabasePassword = ({
</FormItem>
)}
/>
<FormField
control={form.control}
name="confirmPassword"
render={({ field }) => (
<FormItem>
<FormLabel>Confirm {label}</FormLabel>
<FormControl>
<Input
type="password"
placeholder={`Confirm new ${label.toLowerCase()}`}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<DialogFooter>
<Button isLoading={isPending} type="submit">
Update

View File

@@ -400,17 +400,22 @@ export const mariadbRouter = createTRPCRouter({
docker exec "$CONTAINER_ID" mariadb -u root -p'${databaseRootPassword}' -e "ALTER USER '${targetUser}'@'%' IDENTIFIED BY '${password}'; FLUSH PRIVILEGES;"
`;
if (serverId) {
await execAsyncRemote(serverId, command);
} else {
await execAsync(command, { shell: "/bin/bash" });
}
await db.transaction(async (tx) => {
const setData =
type === "root"
? { databaseRootPassword: password }
: { databasePassword: password };
await tx
.update(mariadbTable)
.set(setData)
.where(eq(mariadbTable.mariadbId, mariadbId));
if (type === "root") {
await updateMariadbById(mariadbId, { databaseRootPassword: password });
} else {
await updateMariadbById(mariadbId, { databasePassword: password });
}
if (serverId) {
await execAsyncRemote(serverId, command);
} else {
await execAsync(command, { shell: "/bin/bash" });
}
});
await audit(ctx, {
action: "update",

View File

@@ -419,13 +419,18 @@ export const mongoRouter = createTRPCRouter({
docker exec "$CONTAINER_ID" mongosh -u '${databaseUser}' -p '${databasePassword}' --authenticationDatabase admin --eval "db.getSiblingDB('admin').changeUserPassword('${databaseUser}', '${password}')"
`;
if (serverId) {
await execAsyncRemote(serverId, command);
} else {
await execAsync(command, { shell: "/bin/bash" });
}
await db.transaction(async (tx) => {
await tx
.update(mongoTable)
.set({ databasePassword: password })
.where(eq(mongoTable.mongoId, mongoId));
await updateMongoById(mongoId, { databasePassword: password });
if (serverId) {
await execAsyncRemote(serverId, command);
} else {
await execAsync(command, { shell: "/bin/bash" });
}
});
await audit(ctx, {
action: "update",

View File

@@ -408,8 +408,6 @@ export const mysqlRouter = createTRPCRouter({
const { appName, serverId, databaseUser, databaseRootPassword } = my;
const containerCmd = getServiceContainerCommand(appName);
const authPassword =
type === "root" ? databaseRootPassword : databaseRootPassword;
const targetUser = type === "root" ? "root" : databaseUser;
const command = `
@@ -418,20 +416,25 @@ export const mysqlRouter = createTRPCRouter({
echo "No running container found for ${appName}" >&2
exit 1
fi
docker exec "$CONTAINER_ID" mysql -u root -p'${authPassword}' -e "ALTER USER '${targetUser}'@'%' IDENTIFIED BY '${password}'; FLUSH PRIVILEGES;"
docker exec "$CONTAINER_ID" mysql -u root -p'${databaseRootPassword}' -e "ALTER USER '${targetUser}'@'%' IDENTIFIED BY '${password}'; FLUSH PRIVILEGES;"
`;
if (serverId) {
await execAsyncRemote(serverId, command);
} else {
await execAsync(command, { shell: "/bin/bash" });
}
await db.transaction(async (tx) => {
const setData =
type === "root"
? { databaseRootPassword: password }
: { databasePassword: password };
await tx
.update(mysqlTable)
.set(setData)
.where(eq(mysqlTable.mysqlId, mysqlId));
if (type === "root") {
await updateMySqlById(mysqlId, { databaseRootPassword: password });
} else {
await updateMySqlById(mysqlId, { databasePassword: password });
}
if (serverId) {
await execAsyncRemote(serverId, command);
} else {
await execAsync(command, { shell: "/bin/bash" });
}
});
await audit(ctx, {
action: "update",

View File

@@ -424,13 +424,19 @@ export const postgresRouter = createTRPCRouter({
fi
docker exec "$CONTAINER_ID" psql -U ${databaseUser} -c "ALTER USER \\"${databaseUser}\\" WITH PASSWORD '${password}';"
`;
if (serverId) {
await execAsyncRemote(serverId, command);
} else {
await execAsync(command, { shell: "/bin/bash" });
}
await updatePostgresById(postgresId, { databasePassword: password });
await db.transaction(async (tx) => {
await tx
.update(postgresTable)
.set({ databasePassword: password })
.where(eq(postgresTable.postgresId, postgresId));
if (serverId) {
await execAsyncRemote(serverId, command);
} else {
await execAsync(command, { shell: "/bin/bash" });
}
});
await audit(ctx, {
action: "update",

View File

@@ -406,13 +406,18 @@ export const redisRouter = createTRPCRouter({
docker exec "$CONTAINER_ID" redis-cli -a '${databasePassword}' CONFIG SET requirepass '${password}'
`;
if (serverId) {
await execAsyncRemote(serverId, command);
} else {
await execAsync(command, { shell: "/bin/bash" });
}
await db.transaction(async (tx) => {
await tx
.update(redisTable)
.set({ databasePassword: password })
.where(eq(redisTable.redisId, redisId));
await updateRedisById(redisId, { databasePassword: password });
if (serverId) {
await execAsyncRemote(serverId, command);
} else {
await execAsync(command, { shell: "/bin/bash" });
}
});
await audit(ctx, {
action: "update",