feat(server): remove deprecated password field from update-me (#32623)

* feat(server): remove deprecated password field from update-me

* fix: tests for removed password field

(cherry picked from commit 3f6a071203187681b6c06b977665dc624c431336)
This commit is contained in:
bo0tzz
2026-10-22 05:25:45 +03:00
committed by Codex
parent ad9b368cb8
commit b3bd5d1fd1
8 changed files with 12 additions and 74 deletions
-40
View File
@@ -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();
});
});
});
+1
View File
@@ -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();
-5
View File
@@ -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"
-2
View File
@@ -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 */
-5
View File
@@ -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(),
})
-7
View File
@@ -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);
@@ -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', () => {
@@ -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());
}
};
</script>
@@ -38,6 +41,10 @@
</Stack>
</Alert>
<Field label={$t('password')} required>
<PasswordInput bind:value={currentPassword} autocomplete="current-password" />
</Field>
<Field label={$t('new_password')} required>
<PasswordInput bind:value={password} autocomplete="new-password" />
</Field>