immich-app/immich · error · BadRequestException

Wrong password

Error message

Wrong password

What it means

Thrown by AuthService.changePassword (POST /auth/change-password) when validateSecret(password, user.password) fails, i.e. the supplied current password does not match the stored bcrypt hash. The user is fetched via getForChangePassword(auth.user.id) so it always exists; only the current-password check can fail, yielding 400 BadRequest 'Wrong password'.

Source

Thrown at server/src/services/auth.service.ts:132

      throw new BadRequestException('Invalid logout token: it must contain either a sub or a sid claim');
    }

    const deletedSessionIds = await this.sessionRepository.invalidateOAuth({
      oauthSid: claims.sid,
      oauthId: claims.sub,
    });

    for (const sessionId of deletedSessionIds) {
      await this.eventRepository.emit('SessionDelete', { sessionId });
    }
  }

  async changePassword(auth: AuthDto, dto: ChangePasswordDto): Promise<UserAdminResponseDto> {
    const { password, newPassword } = dto;
    const user = await this.userRepository.getForChangePassword(auth.user.id);
    const isValid = this.validateSecret(password, user.password);
    if (!isValid) {
      throw new BadRequestException('Wrong password');
    }

    const hashedPassword = await this.cryptoRepository.hashBcrypt(newPassword, SALT_ROUNDS);

    const updatedUser = await this.userRepository.update(user.id, { password: hashedPassword });

    await this.eventRepository.emit('AuthChangePassword', {
      userId: user.id,
      currentSessionId: auth.session?.id,
      invalidateSessions: dto.invalidateSessions,
    });

    return mapUserAdmin(updatedUser);
  }

  async setupPinCode(auth: AuthDto, { pinCode }: PinCodeSetupDto) {
    const user = await this.userRepository.getForPinCode(auth.user.id);
    if (!user) {

View on GitHub (pinned to 199723261c)

Solutions

  1. Confirm the current password is correct (re-enter, check caps).
  2. Ensure the request body maps password -> current and newPassword -> new exactly as the DTO expects.
  3. If forgotten, use the admin reset / forgot-password flow instead of change-password.

Example fix

// before
await api.post('/auth/change-password', { password: newPassword, newPassword: currentPassword });

// after
await api.post('/auth/change-password', { password: currentPassword, newPassword });
Defensive patterns

Strategy: try-catch

Validate before calling

// Map fields exactly as the DTO expects; validate non-empty.
if (!dto.password || !dto.newPassword) {
  throw new Error('Both current and new password are required.');
}
if (dto.password === dto.newPassword) {
  throw new Error('New password must differ from the current password.');
}
await api.post('/auth/change-password', { password: dto.password, newPassword: dto.newPassword });

Try / catch

try {
  await api.post('/auth/change-password', { password, newPassword });
} catch (e) {
  if (e.response?.status === 400 && /wrong password/i.test(e.response?.data?.message)) {
    showCurrentPasswordError();
  } else throw e;
}

Prevention

When it happens

Trigger: POST /auth/change-password where the 'password' (current) field is incorrect; user changed password elsewhere and the cached credential is stale; password field order swapped with newPassword in the request body.

Common situations: User mistypes the current password; client sends fields in the wrong order (newPassword in the password slot); concurrent password change invalidating the old password.

Related errors


AI-assisted analysis of immich-app/immich@199723261c (2026-08-12). Data as JSON: /api/errors/13168828d9a8cbf8. Report an issue: GitHub.