diff --git a/src/app/pages/me.vue b/src/app/pages/me.vue
index b0bb54a7..69c2640d 100644
--- a/src/app/pages/me.vue
+++ b/src/app/pages/me.vue
@@ -27,6 +27,7 @@
{{ $t('general.password') }}
@@ -125,6 +130,7 @@ const authStore = useAuthStore();
const name = ref(authStore.userData?.name);
const email = ref(authStore.userData?.email);
+const hasPassword = computed(() => authStore.userData?.hasPassword);
const _submit = useSubmit(
(data) =>
@@ -158,13 +164,14 @@ const _updatePassword = useSubmit(
currentPassword.value = '';
newPassword.value = '';
confirmPassword.value = '';
+ return authStore.update();
},
}
);
function updatePassword() {
return _updatePassword({
- currentPassword: currentPassword.value,
+ currentPassword: hasPassword.value ? currentPassword.value : null,
newPassword: newPassword.value,
confirmPassword: confirmPassword.value,
});
diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json
index b8f2bc6e..48d631a9 100644
--- a/src/i18n/locales/en.json
+++ b/src/i18n/locales/en.json
@@ -28,6 +28,7 @@
"password": "Password",
"newPassword": "New Password",
"updatePassword": "Update Password",
+ "addPassword": "Add Password",
"mtu": "MTU",
"allowedIps": "Allowed IPs",
"dns": "DNS",
diff --git a/src/server/api/session.get.ts b/src/server/api/session.get.ts
index 5d975ee5..32771f79 100644
--- a/src/server/api/session.get.ts
+++ b/src/server/api/session.get.ts
@@ -26,5 +26,6 @@ export default defineEventHandler(async (event) => {
name: user.name,
email: user.email,
totpVerified: user.totpVerified,
+ hasPassword: user.password !== null,
} satisfies SharedPublicUser;
});
diff --git a/src/server/database/repositories/user/service.ts b/src/server/database/repositories/user/service.ts
index ea4b4877..555f5ffd 100644
--- a/src/server/database/repositories/user/service.ts
+++ b/src/server/database/repositories/user/service.ts
@@ -193,7 +193,11 @@ export class UserService {
return this.#statements.update.execute({ id, name, email });
}
- async updatePassword(id: ID, currentPassword: string, newPassword: string) {
+ async updatePassword(
+ id: ID,
+ currentPassword: string | null,
+ newPassword: string
+ ) {
const hash = await hashPassword(newPassword);
return this.#db.transaction(async (tx) => {
@@ -206,13 +210,20 @@ export class UserService {
throw new Error('User not found');
}
- const passwordValid = await isPasswordValid(
- currentPassword,
- txUser.password
- );
+ // only check password if already set
+ if (txUser.password !== null) {
+ if (!currentPassword) {
+ throw new Error('Invalid password');
+ }
- if (!passwordValid) {
- throw new Error('Invalid password');
+ const passwordValid = await isPasswordValid(
+ currentPassword,
+ txUser.password
+ );
+
+ if (!passwordValid) {
+ throw new Error('Invalid password');
+ }
}
await tx
diff --git a/src/server/database/repositories/user/types.ts b/src/server/database/repositories/user/types.ts
index 4743be73..a5a31d04 100644
--- a/src/server/database/repositories/user/types.ts
+++ b/src/server/database/repositories/user/types.ts
@@ -57,7 +57,7 @@ export const UserUpdateSchema = z.object({
export const UserUpdatePasswordSchema = z
.object({
- currentPassword: password,
+ currentPassword: password.nullable(),
newPassword: password,
confirmPassword: password,
})
diff --git a/src/shared/utils/permissions.ts b/src/shared/utils/permissions.ts
index 12fd5570..7a14855c 100644
--- a/src/shared/utils/permissions.ts
+++ b/src/shared/utils/permissions.ts
@@ -48,7 +48,7 @@ type SharedUserType =
export type SharedPublicUser = Pick<
UserType,
'id' | 'username' | 'name' | 'email' | 'totpVerified'
-> & { role: BrandedNumber };
+> & { role: BrandedNumber; hasPassword: boolean };
type PermissionCheck =
| boolean