refactor: change password repository lookup (#19584)

This commit is contained in:
Jason Rasmussen 2025-06-27 16:52:04 -04:00 committed by GitHub
parent a2a9797fab
commit 09cbc5d3f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 29 additions and 26 deletions

View File

@ -97,6 +97,16 @@ where
"users"."id" = $1
and "users"."deletedAt" is null
-- UserRepository.getForChangePassword
select
"users"."id",
"users"."password"
from
"users"
where
"users"."id" = $1
and "users"."deletedAt" is null
-- UserRepository.getByEmail
select
"id",

View File

@ -100,6 +100,16 @@ export class UserRepository {
.executeTakeFirstOrThrow();
}
@GenerateSql({ params: [DummyValue.UUID] })
getForChangePassword(id: string) {
return this.db
.selectFrom('users')
.select(['users.id', 'users.password'])
.where('users.id', '=', id)
.where('users.deletedAt', 'is', null)
.executeTakeFirstOrThrow();
}
@GenerateSql({ params: [DummyValue.EMAIL] })
getByEmail(email: string, options?: { withPassword?: boolean }) {
return this.db

View File

@ -116,46 +116,33 @@ describe(AuthService.name, () => {
const auth = factory.auth({ user });
const dto = { password: 'old-password', newPassword: 'new-password' };
mocks.user.getByEmail.mockResolvedValue({ ...user, password: 'hash-password' });
mocks.user.getForChangePassword.mockResolvedValue({ id: user.id, password: 'hash-password' });
mocks.user.update.mockResolvedValue(user);
await sut.changePassword(auth, dto);
expect(mocks.user.getByEmail).toHaveBeenCalledWith(auth.user.email, { withPassword: true });
expect(mocks.user.getForChangePassword).toHaveBeenCalledWith(user.id);
expect(mocks.crypto.compareBcrypt).toHaveBeenCalledWith('old-password', 'hash-password');
});
it('should throw when auth user email is not found', async () => {
const auth = { user: { email: 'test@imimch.com' } } as AuthDto;
const dto = { password: 'old-password', newPassword: 'new-password' };
mocks.user.getByEmail.mockResolvedValue(void 0);
await expect(sut.changePassword(auth, dto)).rejects.toBeInstanceOf(UnauthorizedException);
});
it('should throw when password does not match existing password', async () => {
const auth = { user: { email: 'test@imimch.com' } as UserAdmin };
const user = factory.user();
const auth = factory.auth({ user });
const dto = { password: 'old-password', newPassword: 'new-password' };
mocks.crypto.compareBcrypt.mockReturnValue(false);
mocks.user.getByEmail.mockResolvedValue({
email: 'test@immich.com',
password: 'hash-password',
} as UserAdmin & { password: string });
mocks.user.getForChangePassword.mockResolvedValue({ id: user.id, password: 'hash-password' });
await expect(sut.changePassword(auth, dto)).rejects.toBeInstanceOf(BadRequestException);
});
it('should throw when user does not have a password', async () => {
const auth = { user: { email: 'test@imimch.com' } } as AuthDto;
const user = factory.user();
const auth = factory.auth({ user });
const dto = { password: 'old-password', newPassword: 'new-password' };
mocks.user.getByEmail.mockResolvedValue({
email: 'test@immich.com',
password: '',
} as UserAdmin & { password: string });
mocks.user.getForChangePassword.mockResolvedValue({ id: user.id, password: '' });
await expect(sut.changePassword(auth, dto)).rejects.toBeInstanceOf(BadRequestException);
});

View File

@ -91,11 +91,7 @@ export class AuthService extends BaseService {
async changePassword(auth: AuthDto, dto: ChangePasswordDto): Promise<UserAdminResponseDto> {
const { password, newPassword } = dto;
const user = await this.userRepository.getByEmail(auth.user.email, { withPassword: true });
if (!user) {
throw new UnauthorizedException();
}
const user = await this.userRepository.getForChangePassword(auth.user.id);
const valid = this.validateSecret(password, user.password);
if (!valid) {
throw new BadRequestException('Wrong password');