-
-
+
+
+ {{ $t('me.sectionGeneral') }}
+
+
+
+
+
+
+
+ {{ $t('me.sectionPassword') }}
+
+
+
+
+
+
diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json
index 62083d46..3d40afe5 100644
--- a/src/i18n/locales/en.json
+++ b/src/i18n/locales/en.json
@@ -61,7 +61,13 @@
"passwordNumber": "Password must have at least 1 number",
"passwordSpecial": "Password must have at least 1 special character",
"remember": "Remember must be a valid boolean",
- "accept": "Please accept the condition"
+ "accept": "Please accept the condition",
+ "name": "Name must be a valid string",
+ "nameMin": "Name must be at least 1 Character",
+ "email": "Email must be a valid string",
+ "emailMin": "Email must be at least 1 Character",
+ "emailInvalid": "Email must be a valid email",
+ "passwordMatch": "Passwords must match"
},
"userConfig": {
"host": "Host must be a valid string",
diff --git a/src/server/api/me/index.post.ts b/src/server/api/me/index.post.ts
new file mode 100644
index 00000000..324b4552
--- /dev/null
+++ b/src/server/api/me/index.post.ts
@@ -0,0 +1,13 @@
+import { UserUpdateSchema } from '#db/repositories/user/types';
+
+export default definePermissionEventHandler(
+ actions.CLIENT,
+ async ({ event, user }) => {
+ const { name, email } = await readValidatedBody(
+ event,
+ validateZod(UserUpdateSchema)
+ );
+ await Database.users.update(user.id, name, email);
+ return { success: true };
+ }
+);
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/api/setup/4.post.ts b/src/server/api/setup/4.post.ts
index 0dd73670..4c8eb82a 100644
--- a/src/server/api/setup/4.post.ts
+++ b/src/server/api/setup/4.post.ts
@@ -1,9 +1,9 @@
-import { UserSetupType } from '#db/repositories/user/types';
+import { UserSetupSchema } from '#db/repositories/user/types';
export default defineSetupEventHandler(async ({ event }) => {
const { username, password } = await readValidatedBody(
event,
- validateZod(UserSetupType, event)
+ validateZod(UserSetupSchema, event)
);
await Database.users.create(username, password);
diff --git a/src/server/api/setup/5.post.ts b/src/server/api/setup/5.post.ts
index 1e0b36d6..b62d42d9 100644
--- a/src/server/api/setup/5.post.ts
+++ b/src/server/api/setup/5.post.ts
@@ -1,9 +1,9 @@
-import { UserConfigSetupType } from '#db/repositories/userConfig/types';
+import { UserConfigSetupSchema } from '#db/repositories/userConfig/types';
export default defineSetupEventHandler(async ({ event }) => {
const { host, port } = await readValidatedBody(
event,
- validateZod(UserConfigSetupType, event)
+ validateZod(UserConfigSetupSchema, event)
);
await Database.userConfigs.updateHostPort('wg0', host, port);
await Database.general.setSetupStep(0);
diff --git a/src/server/database/repositories/user/service.ts b/src/server/database/repositories/user/service.ts
index 209fe99d..dd92287c 100644
--- a/src/server/database/repositories/user/service.ts
+++ b/src/server/database/repositories/user/service.ts
@@ -14,6 +14,14 @@ function createPreparedStatement(db: DBType) {
where: eq(user.username, sql.placeholder('username')),
})
.prepare(),
+ update: db
+ .update(user)
+ .set({
+ name: sql.placeholder('name') as never as string,
+ email: sql.placeholder('email') as never as string,
+ })
+ .where(eq(user.id, sql.placeholder('id')))
+ .prepare(),
};
}
@@ -42,7 +50,11 @@ export class UserService {
const hash = await hashPassword(password);
return this.#db.transaction(async (tx) => {
- const oldUser = await this.getByUsername(username);
+ const oldUser = await tx.query.user
+ .findFirst({
+ where: eq(user.username, username),
+ })
+ .execute();
if (oldUser) {
throw new Error('User already exists');
@@ -60,4 +72,38 @@ 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 7b629365..5837b19e 100644
--- a/src/server/database/repositories/user/types.ts
+++ b/src/server/database/repositories/user/types.ts
@@ -20,6 +20,18 @@ const password = z
const remember = z.boolean({ message: 'zod.user.remember' });
+const name = z
+ .string({ message: 'zod.user.name' })
+ .min(1, 'zod.user.nameMin')
+ .pipe(safeStringRefine);
+
+const email = z
+ .string({ message: 'zod.user.email' })
+ .min(5, 'zod.user.emailMin')
+ .email({ message: 'zod.user.emailInvalid' })
+ .pipe(safeStringRefine)
+ .nullable();
+
export const UserLoginSchema = z.object(
{
username: username,
@@ -33,7 +45,7 @@ const accept = z.boolean().refine((val) => val === true, {
message: 'zod.user.accept',
});
-export const UserSetupType = z.object(
+export const UserSetupSchema = z.object(
{
username: username,
password: password,
@@ -41,3 +53,24 @@ export const UserSetupType = z.object(
},
{ message: objectMessage }
);
+
+export const UserUpdateSchema = z.object(
+ {
+ name: name,
+ email: email,
+ },
+ { 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',
+ });
diff --git a/src/server/database/repositories/userConfig/types.ts b/src/server/database/repositories/userConfig/types.ts
index 165f3f27..89e59ef2 100644
--- a/src/server/database/repositories/userConfig/types.ts
+++ b/src/server/database/repositories/userConfig/types.ts
@@ -9,7 +9,7 @@ const host = z
.min(1, 'zod.userConfig.hostMin')
.pipe(safeStringRefine);
-export const UserConfigSetupType = z.object({
+export const UserConfigSetupSchema = z.object({
host: host,
port: PortSchema,
});