diff --git a/src/app/pages/me.vue b/src/app/pages/me.vue index 1a367611..bbe2989e 100644 --- a/src/app/pages/me.vue +++ b/src/app/pages/me.vue @@ -79,7 +79,7 @@ async function submit() { message: 'Saved', }); if (!res.success) { - throw new Error('Failed to save'); + throw new Error('Failed to update general'); } await refreshNuxtData(); } catch (e) { @@ -98,13 +98,33 @@ const currentPassword = ref(''); const newPassword = ref(''); const confirmPassword = ref(''); -function updatePassword() { - if (newPassword.value !== confirmPassword.value) { +async function updatePassword() { + try { + const res = await $fetch(`/api/me/password`, { + method: 'post', + body: { + currentPassword: currentPassword.value, + newPassword: newPassword.value, + confirmPassword: confirmPassword.value, + }, + }); toast.showToast({ - type: 'error', - title: 'Error', - message: 'Passwords do not match', + type: 'success', + title: 'Success', + message: 'Saved', }); + if (!res.success) { + throw new Error('Failed to update password'); + } + await refreshNuxtData(); + } catch (e) { + if (e instanceof FetchError) { + toast.showToast({ + type: 'error', + title: 'Error', + message: e.data.message, + }); + } } } diff --git a/src/server/api/me/password.post.ts b/src/server/api/me/password.post.ts new file mode 100644 index 00000000..50a593a4 --- /dev/null +++ b/src/server/api/me/password.post.ts @@ -0,0 +1,13 @@ +import { UserUpdatePasswordSchema } from '#db/repositories/user/types'; + +export default definePermissionEventHandler( + actions.CLIENT, + async ({ event, user }) => { + const { newPassword, currentPassword } = await readValidatedBody( + event, + validateZod(UserUpdatePasswordSchema) + ); + await Database.users.updatePassword(user.id, currentPassword, newPassword); + return { success: true }; + } +); diff --git a/src/server/database/repositories/user/service.ts b/src/server/database/repositories/user/service.ts index 34cffff1..be3779be 100644 --- a/src/server/database/repositories/user/service.ts +++ b/src/server/database/repositories/user/service.ts @@ -72,4 +72,34 @@ export class UserService { async update(id: ID, name: string, email: string | null) { return this.#statements.update.execute({ id, name, email }); } + + async updatePassword(id: ID, currentPassword: string, newPassword: string) { + const hash = await hashPassword(newPassword); + + return this.#db.transaction(async (tx) => { + // get user again to avoid password changing while request + const txUser = await tx.query.user + .findFirst({ where: eq(user.id, id) }) + .execute(); + + if (!txUser) { + throw new Error('User not found'); + } + + const passwordValid = await isPasswordValid( + currentPassword, + txUser.password + ); + + if (!passwordValid) { + throw new Error('Invalid password'); + } + + await tx + .update(user) + .set({ password: hash }) + .where(eq(user.id, id)) + .execute(); + }); + } } diff --git a/src/server/database/repositories/user/types.ts b/src/server/database/repositories/user/types.ts index 9c519d7d..46c23a1b 100644 --- a/src/server/database/repositories/user/types.ts +++ b/src/server/database/repositories/user/types.ts @@ -61,3 +61,16 @@ export const UserUpdateSchema = z.object( }, { message: objectMessage } ); + +export const UserUpdatePasswordSchema = z + .object( + { + currentPassword: password, + newPassword: password, + confirmPassword: password, + }, + { message: objectMessage } + ) + .refine((val) => val.newPassword === val.confirmPassword, { + message: 'zod.user.passwordMatch', + });