feat: improve user profile update and password change functionality

This commit adds enhanced password change validation and handling:

- Add password change validation in user update route
- Implement password verification before allowing changes
- Update user schema to support optional password fields
- Fix token display in generate token component
- Disable migration script temporarily
This commit is contained in:
Mauricio Siu
2025-02-22 22:37:57 -06:00
parent 0478419f7c
commit 47f7648cb3
5 changed files with 176 additions and 143 deletions

View File

@@ -51,7 +51,7 @@ export const GenerateToken = () => {
<Label>Token</Label>
<ToggleVisibilityInput
placeholder="Token"
value={data || ""}
value={data?.id || ""}
disabled
/>
</div>

View File

@@ -103,9 +103,9 @@ export const ProfileForm = () => {
const onSubmit = async (values: Profile) => {
await mutateAsync({
email: values.email.toLowerCase(),
password: values.password,
password: values.password || undefined,
image: values.image,
currentPassword: values.currentPassword,
currentPassword: values.currentPassword || undefined,
})
.then(async () => {
await refetch();

View File

@@ -1,149 +1,149 @@
import { drizzle } from "drizzle-orm/postgres-js";
import { nanoid } from "nanoid";
import postgres from "postgres";
import * as schema from "./server/db/schema";
// import { drizzle } from "drizzle-orm/postgres-js";
// import { nanoid } from "nanoid";
// import postgres from "postgres";
// import * as schema from "./server/db/schema";
const connectionString = process.env.DATABASE_URL!;
// const connectionString = process.env.DATABASE_URL!;
const sql = postgres(connectionString, { max: 1 });
const db = drizzle(sql, {
schema,
});
// const sql = postgres(connectionString, { max: 1 });
// const db = drizzle(sql, {
// schema,
// });
await db
.transaction(async (db) => {
const admins = await db.query.admins.findMany({
with: {
auth: true,
users: {
with: {
auth: true,
},
},
},
});
for (const admin of admins) {
const user = await db
.insert(schema.users_temp)
.values({
id: admin.adminId,
email: admin.auth.email,
token: admin.auth.token || "",
emailVerified: true,
updatedAt: new Date(),
role: "admin",
serverIp: admin.serverIp,
image: admin.auth.image,
certificateType: admin.certificateType,
host: admin.host,
letsEncryptEmail: admin.letsEncryptEmail,
sshPrivateKey: admin.sshPrivateKey,
enableDockerCleanup: admin.enableDockerCleanup,
enableLogRotation: admin.enableLogRotation,
enablePaidFeatures: admin.enablePaidFeatures,
metricsConfig: admin.metricsConfig,
cleanupCacheApplications: admin.cleanupCacheApplications,
cleanupCacheOnPreviews: admin.cleanupCacheOnPreviews,
cleanupCacheOnCompose: admin.cleanupCacheOnCompose,
stripeCustomerId: admin.stripeCustomerId,
stripeSubscriptionId: admin.stripeSubscriptionId,
serversQuantity: admin.serversQuantity,
})
.returning()
.then((user) => user[0]);
// await db
// .transaction(async (db) => {
// const admins = await db.query.admins.findMany({
// with: {
// auth: true,
// users: {
// with: {
// auth: true,
// },
// },
// },
// });
// for (const admin of admins) {
// const user = await db
// .insert(schema.users_temp)
// .values({
// id: admin.adminId,
// email: admin.auth.email,
// token: admin.auth.token || "",
// emailVerified: true,
// updatedAt: new Date(),
// role: "admin",
// serverIp: admin.serverIp,
// image: admin.auth.image,
// certificateType: admin.certificateType,
// host: admin.host,
// letsEncryptEmail: admin.letsEncryptEmail,
// sshPrivateKey: admin.sshPrivateKey,
// enableDockerCleanup: admin.enableDockerCleanup,
// enableLogRotation: admin.enableLogRotation,
// enablePaidFeatures: admin.enablePaidFeatures,
// metricsConfig: admin.metricsConfig,
// cleanupCacheApplications: admin.cleanupCacheApplications,
// cleanupCacheOnPreviews: admin.cleanupCacheOnPreviews,
// cleanupCacheOnCompose: admin.cleanupCacheOnCompose,
// stripeCustomerId: admin.stripeCustomerId,
// stripeSubscriptionId: admin.stripeSubscriptionId,
// serversQuantity: admin.serversQuantity,
// })
// .returning()
// .then((user) => user[0]);
await db.insert(schema.account).values({
providerId: "credential",
userId: user?.id || "",
password: admin.auth.password,
is2FAEnabled: admin.auth.is2FAEnabled || false,
createdAt: new Date(admin.auth.createdAt) || new Date(),
updatedAt: new Date(admin.auth.createdAt) || new Date(),
});
// await db.insert(schema.account).values({
// providerId: "credential",
// userId: user?.id || "",
// password: admin.auth.password,
// is2FAEnabled: admin.auth.is2FAEnabled || false,
// createdAt: new Date(admin.auth.createdAt) || new Date(),
// updatedAt: new Date(admin.auth.createdAt) || new Date(),
// });
const organization = await db
.insert(schema.organization)
.values({
name: "My Organization",
slug: nanoid(),
ownerId: user?.id || "",
createdAt: new Date(admin.createdAt) || new Date(),
})
.returning()
.then((organization) => organization[0]);
// const organization = await db
// .insert(schema.organization)
// .values({
// name: "My Organization",
// slug: nanoid(),
// ownerId: user?.id || "",
// createdAt: new Date(admin.createdAt) || new Date(),
// })
// .returning()
// .then((organization) => organization[0]);
for (const member of admin.users) {
const userTemp = await db
.insert(schema.users_temp)
.values({
id: member.userId,
email: member.auth.email,
token: member.token || "",
emailVerified: true,
updatedAt: new Date(admin.createdAt) || new Date(),
role: "user",
image: member.auth.image,
createdAt: admin.createdAt,
canAccessToAPI: member.canAccessToAPI || false,
canAccessToDocker: member.canAccessToDocker || false,
canAccessToGitProviders: member.canAccessToGitProviders || false,
canAccessToSSHKeys: member.canAccessToSSHKeys || false,
canAccessToTraefikFiles: member.canAccessToTraefikFiles || false,
canCreateProjects: member.canCreateProjects || false,
canCreateServices: member.canCreateServices || false,
canDeleteProjects: member.canDeleteProjects || false,
canDeleteServices: member.canDeleteServices || false,
accessedProjects: member.accessedProjects || [],
accessedServices: member.accessedServices || [],
})
.returning()
.then((userTemp) => userTemp[0]);
// for (const member of admin.users) {
// const userTemp = await db
// .insert(schema.users_temp)
// .values({
// id: member.userId,
// email: member.auth.email,
// token: member.token || "",
// emailVerified: true,
// updatedAt: new Date(admin.createdAt) || new Date(),
// role: "user",
// image: member.auth.image,
// createdAt: admin.createdAt,
// canAccessToAPI: member.canAccessToAPI || false,
// canAccessToDocker: member.canAccessToDocker || false,
// canAccessToGitProviders: member.canAccessToGitProviders || false,
// canAccessToSSHKeys: member.canAccessToSSHKeys || false,
// canAccessToTraefikFiles: member.canAccessToTraefikFiles || false,
// canCreateProjects: member.canCreateProjects || false,
// canCreateServices: member.canCreateServices || false,
// canDeleteProjects: member.canDeleteProjects || false,
// canDeleteServices: member.canDeleteServices || false,
// accessedProjects: member.accessedProjects || [],
// accessedServices: member.accessedServices || [],
// })
// .returning()
// .then((userTemp) => userTemp[0]);
await db.insert(schema.account).values({
providerId: "credential",
userId: member?.userId || "",
password: member.auth.password,
is2FAEnabled: member.auth.is2FAEnabled || false,
createdAt: new Date(member.auth.createdAt) || new Date(),
updatedAt: new Date(member.auth.createdAt) || new Date(),
});
// await db.insert(schema.account).values({
// providerId: "credential",
// userId: member?.userId || "",
// password: member.auth.password,
// is2FAEnabled: member.auth.is2FAEnabled || false,
// createdAt: new Date(member.auth.createdAt) || new Date(),
// updatedAt: new Date(member.auth.createdAt) || new Date(),
// });
await db.insert(schema.member).values({
organizationId: organization?.id || "",
userId: userTemp?.id || "",
role: "admin",
createdAt: new Date(member.createdAt) || new Date(),
});
}
}
})
.then(() => {
console.log("Migration finished");
})
.catch((error) => {
console.error(error);
});
// await db.insert(schema.member).values({
// organizationId: organization?.id || "",
// userId: userTemp?.id || "",
// role: "admin",
// createdAt: new Date(member.createdAt) || new Date(),
// });
// }
// }
// })
// .then(() => {
// console.log("Migration finished");
// })
// .catch((error) => {
// console.error(error);
// });
await db
.transaction(async (db) => {
const projects = await db.query.projects.findMany({
with: {
user: {
with: {
organizations: true,
},
},
},
});
for (const project of projects) {
const _user = await db.update(schema.projects).set({
organizationId: project.user.organizations[0]?.id || "",
});
}
})
.then(() => {
console.log("Migration finished");
})
.catch((error) => {
console.error(error);
});
// await db
// .transaction(async (db) => {
// const projects = await db.query.projects.findMany({
// with: {
// user: {
// with: {
// organizations: true,
// },
// },
// },
// });
// for (const project of projects) {
// const _user = await db.update(schema.projects).set({
// organizationId: project.user.organizations[0]?.id || "",
// });
// }
// })
// .then(() => {
// console.log("Migration finished");
// })
// .catch((error) => {
// console.error(error);
// });

View File

@@ -8,12 +8,14 @@ import {
} from "@dokploy/server";
import { db } from "@dokploy/server/db";
import {
account,
apiAssignPermissions,
apiFindOneToken,
apiUpdateUser,
invitation,
member,
} from "@dokploy/server/db/schema";
import * as bcrypt from "bcrypt";
import { TRPCError } from "@trpc/server";
import { and, asc, eq, gt } from "drizzle-orm";
import { z } from "zod";
@@ -81,6 +83,35 @@ export const userRouter = createTRPCRouter({
update: protectedProcedure
.input(apiUpdateUser)
.mutation(async ({ input, ctx }) => {
if (input.password || input.currentPassword) {
const currentAuth = await db.query.account.findFirst({
where: eq(account.userId, ctx.user.id),
});
const correctPassword = bcrypt.compareSync(
input.currentPassword || "",
currentAuth?.password || "",
);
if (!correctPassword) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Current password is incorrect",
});
}
if (!input.password) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "New password is required",
});
}
await db
.update(account)
.set({
password: bcrypt.hashSync(input.password, 10),
})
.where(eq(account.userId, ctx.user.id));
}
return await updateUser(ctx.user.id, input);
}),
getUserByToken: publicProcedure

View File

@@ -278,6 +278,8 @@ export const apiUpdateWebServerMonitoring = z.object({
});
export const apiUpdateUser = createSchema.partial().extend({
password: z.string().optional(),
currentPassword: z.string().optional(),
metricsConfig: z
.object({
server: z.object({