diff --git a/e2e/src/specs/server/api/user.e2e-spec.ts b/e2e/src/specs/server/api/user.e2e-spec.ts
deleted file mode 100644
index fb357b30e..000000000
--- a/e2e/src/specs/server/api/user.e2e-spec.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { LoginResponseDto, getMyUser, login } from '@immich/sdk';
-import { createUserDto } from 'src/fixtures.js';
-import { app, asBearerAuth, utils } from 'src/utils.js';
-import request from 'supertest';
-import { beforeAll, describe, expect, it } from 'vitest';
-
-describe('/users', () => {
- let admin: LoginResponseDto;
- let nonAdmin: LoginResponseDto;
-
- beforeAll(async () => {
- await utils.resetDatabase();
- admin = await utils.adminSetup({ onboarding: false });
- nonAdmin = await utils.userSetup(admin.accessToken, createUserDto.user2);
- });
-
- describe('PUT /users/me', () => {
- /** @deprecated */
- it('should allow a user to change their password (deprecated)', async () => {
- const user = await getMyUser({ headers: asBearerAuth(nonAdmin.accessToken) });
-
- expect(user.shouldChangePassword).toBe(true);
-
- const { status, body } = await request(app)
- .put(`/users/me`)
- .send({ password: 'super-secret' })
- .set('Authorization', `Bearer ${nonAdmin.accessToken}`);
-
- expect(status).toBe(200);
- expect(body).toMatchObject({
- email: nonAdmin.userEmail,
- shouldChangePassword: false,
- });
-
- const token = await login({ loginCredentialDto: { email: nonAdmin.userEmail, password: 'super-secret' } });
-
- expect(token.accessToken).toBeDefined();
- });
- });
-});
diff --git a/e2e/src/specs/web/auth.e2e-spec.ts b/e2e/src/specs/web/auth.e2e-spec.ts
index 37ee638a7..e49e699e7 100644
--- a/e2e/src/specs/web/auth.e2e-spec.ts
+++ b/e2e/src/specs/web/auth.e2e-spec.ts
@@ -71,6 +71,7 @@ test.describe('Registration', () => {
// change password
await expect(page.getByRole('heading')).toHaveText('Change Password');
await expect(page).toHaveURL('/auth/change-password');
+ await page.getByLabel('Password', { exact: true }).fill('password');
await page.getByLabel('New Password').fill('new-password');
await page.getByLabel('Confirm Password').fill('new-password');
await page.getByRole('button', { name: 'Change password' }).click();
diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json
index a32926e84..b8fbb5483 100644
--- a/open-api/immich-openapi-specs.json
+++ b/open-api/immich-openapi-specs.json
@@ -34312,11 +34312,6 @@
"name": {
"description": "User name",
"type": "string"
- },
- "password": {
- "deprecated": true,
- "description": "User password (deprecated, use change password endpoint)",
- "type": "string"
}
},
"type": "object"
diff --git a/packages/sdk/src/fetch-client.ts b/packages/sdk/src/fetch-client.ts
index 2d06a14d9..e52f746c0 100644
--- a/packages/sdk/src/fetch-client.ts
+++ b/packages/sdk/src/fetch-client.ts
@@ -3266,8 +3266,6 @@ export type UserUpdateMeDto = {
email?: string;
/** User name */
name?: string;
- /** User password (deprecated, use change password endpoint) */
- password?: string;
};
export type OnboardingResponseDto = {
/** Is user onboarded */
diff --git a/server/src/dtos/user.dto.ts b/server/src/dtos/user.dto.ts
index fa237e8eb..5e33dc2d8 100644
--- a/server/src/dtos/user.dto.ts
+++ b/server/src/dtos/user.dto.ts
@@ -11,11 +11,6 @@ import { isoDatetimeToDate, sanitizeFilename, stringToBool, toEmail } from 'src/
export const UserUpdateMeSchema = z
.object({
email: toEmail.optional().describe('User email'),
- password: z
- .string()
- .optional()
- .describe('User password (deprecated, use change password endpoint)')
- .meta({ deprecated: true }),
name: z.string().optional().describe('User name'),
avatarColor: UserAvatarColorSchema.nullish(),
})
diff --git a/server/src/services/user.service.ts b/server/src/services/user.service.ts
index 29116e8b1..33bd923c3 100644
--- a/server/src/services/user.service.ts
+++ b/server/src/services/user.service.ts
@@ -3,7 +3,6 @@ import { Updateable } from 'kysely';
import { DateTime } from 'luxon';
import type { ArgOf } from 'src/repositories/event.repository.js';
import type { JobOf, UserMetadataItem } from 'src/types.js';
-import { SALT_ROUNDS } from 'src/constants.js';
import { StorageCore } from 'src/cores/storage.core.js';
import { OnEvent, OnJob } from 'src/decorators.js';
import { AuthDto } from 'src/dtos/auth.dto.js';
@@ -68,12 +67,6 @@ export class UserService extends BaseService {
avatarColor: dto.avatarColor,
};
- if (dto.password) {
- const hashedPassword = await this.cryptoRepository.hashBcrypt(dto.password, SALT_ROUNDS);
- update.password = hashedPassword;
- update.shouldChangePassword = false;
- }
-
const updatedUser = await this.userRepository.update(user.id, update);
return mapUserAdmin(updatedUser);
diff --git a/server/test/medium/specs/services/user.service.spec.ts b/server/test/medium/specs/services/user.service.spec.ts
index b2efa4bde..96d727f85 100644
--- a/server/test/medium/specs/services/user.service.spec.ts
+++ b/server/test/medium/specs/services/user.service.spec.ts
@@ -198,17 +198,6 @@ describe(UserService.name, () => {
await expect(sut.updateMe(auth, dto)).resolves.toMatchObject(dto);
await expect(sut.getMe(auth)).resolves.toMatchObject(dto);
});
-
- it('should clear shouldChangePassword when the password is updated', async () => {
- const { sut, ctx } = setup();
- const { user } = await ctx.newUser({ shouldChangePassword: true });
- const auth = factory.auth({ user: { id: user.id } });
-
- await expect(sut.updateMe(auth, { password: 'super-secret' })).resolves.toMatchObject({
- shouldChangePassword: false,
- });
- await expect(sut.getMe(auth)).resolves.toMatchObject({ shouldChangePassword: false });
- });
});
describe('updateMyPreferences', () => {
diff --git a/web/src/routes/auth/change-password/+page.svelte b/web/src/routes/auth/change-password/+page.svelte
index 49578ff80..b4e43e49f 100644
--- a/web/src/routes/auth/change-password/+page.svelte
+++ b/web/src/routes/auth/change-password/+page.svelte
@@ -3,7 +3,7 @@
import AuthPageLayout from '$lib/components/layouts/AuthPageLayout.svelte';
import { authManager } from '$lib/managers/auth-manager.svelte';
import { Route } from '$lib/route';
- import { updateMyUser } from '@immich/sdk';
+ import { handleChangePassword } from '$lib/services/user.service';
import { Alert, Button, Field, HelperText, PasswordInput, Stack, Text } from '@immich/ui';
import { t } from 'svelte-i18n';
import type { PageData } from './$types';
@@ -14,9 +14,10 @@
let { data }: Props = $props();
+ let currentPassword = $state('');
let password = $state('');
let passwordConfirm = $state('');
- const valid = $derived(password === passwordConfirm && passwordConfirm.length > 0);
+ const valid = $derived(currentPassword.length > 0 && password === passwordConfirm && passwordConfirm.length > 0);
const errorMessage = $derived(passwordConfirm.length === 0 || valid ? '' : $t('password_does_not_match'));
const onSubmit = async () => {
@@ -24,8 +25,10 @@
return;
}
- await updateMyUser({ userUpdateMeDto: { password } });
- await goto(Route.logout());
+ const success = await handleChangePassword({ password: currentPassword, newPassword: password });
+ if (success) {
+ await goto(Route.logout());
+ }
};
@@ -38,6 +41,10 @@
+
+
+
+