immich-app/immich · error · BadRequestException

Wrong PIN code

Error message

Wrong PIN code

What it means

When validatePinCode receives a `pinCode` in the dto, it compares it against the stored hashed PIN via validateSecret. A mismatch throws 400 'Wrong PIN code', meaning the supplied plaintext PIN does not match the user's configured PIN.

Solutions

  1. Re-enter the current PIN carefully and retry; note the PIN is a fixed-digit code, not the account password.
  2. If the PIN is forgotten, use resetPinCode with the account password instead.
  3. Verify the PIN still matches by checking status or unlocking from the device where it was last set.

Example fix

// before
await api.authenticationApi.unlockSession({ pinCode: oldPin }); // PIN was rotated
// after
const { hasPin } = await api.authenticationApi.getPinCodeStatus();
if (hasPin) await api.authenticationApi.resetPinCode({ password }); // PIN forgotten
else await api.authenticationApi.unlockSession({ pinCode: currentPin });
Defensive patterns

Strategy: validation

Validate before calling

if (mode === 'pin' && !/^[0-9]+$/.test(pinCode ?? '')) {
  throw new Error('Enter the current numeric PIN');
}

Try / catch

try {
  await api.authenticationApi.unlockSession({ pinCode });
} catch (e) {
  if (e.status === 400 && e.message === 'Wrong PIN code') {
    // clear input, re-prompt; offer password-based reset after failures
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling changePinCode, resetPinCode, or unlockSession with dto.pinCode that fails bcrypt comparison against the stored pinCode hash.

Common situations: Typo in PIN entry; PIN was changed on another device so the client's remembered PIN is stale; PIN was reset by an admin; autocorrect/keyboard injecting characters into the PIN field.

Related errors


AI-assisted analysis of immich-app/immich@f48d4b3321 (2026-09-15). Data as JSON: /api/errors/f29dd38f2e6e5855. Report an issue: GitHub.

Appendix: source

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

    const hashed = await this.cryptoRepository.hashBcrypt(dto.newPinCode, SALT_ROUNDS);
    await this.userRepository.update(auth.user.id, { pinCode: hashed });
  }

  private validatePinCode(
    user: { pinCode: string | null; password: string | null },
    dto: { pinCode?: string; password?: string },
  ) {
    if (!user.pinCode) {
      throw new BadRequestException('User does not have a PIN code');
    }

    if (dto.password) {
      if (!this.validateSecret(dto.password, user.password)) {
        throw new BadRequestException('Wrong password');
      }
    } else if (dto.pinCode) {
      if (!this.validateSecret(dto.pinCode, user.pinCode)) {
        throw new BadRequestException('Wrong PIN code');
      }
    } else {
      throw new BadRequestException('Either password or pinCode is required');
    }
  }

  async adminSignUp(dto: SignUpDto): Promise<UserAdminResponseDto> {
    const admin = await this.createUser({
      isAdmin: true,
      email: dto.email,
      name: dto.name,
      password: dto.password,
      storageLabel: 'admin',
    });

    return mapUserAdmin(admin);
  }

View on GitHub (pinned to f48d4b3321)