immich-app/immich · warning · BadRequestException

User does not have a PIN code

Error message

User does not have a PIN code

What it means

BadRequestException (HTTP 400) thrown by the private validatePinCode helper when user.pinCode is null. It guards resetPinCode, changePinCode, and unlockSession; none of these operations make sense without an existing PIN. The check runs before any credential verification, so callers cannot use reset/change/unlock to bootstrap a PIN.

Source

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

    await this.userRepository.update(auth.user.id, { pinCode: null });
    await this.sessionRepository.lockAll(auth.user.id);
  }

  async changePinCode(auth: AuthDto, dto: PinCodeChangeDto) {
    const user = await this.userRepository.getForPinCode(auth.user.id);
    this.validatePinCode(user, dto);

    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,

View on GitHub (pinned to 199723261c)

Solutions

  1. Call GET /auth/status and check `pinCode` before invoking reset/change/unlock; if false, hide those controls.
  2. If the user wants a PIN, POST /auth/pin-code to set one first.
  3. If the UI expected a PIN to exist, force a re-fetch of auth status on each app foreground.
  4. Verify no other admin/tool cleared the pinCode column out of band.

Example fix

// before
await api.authApi.unlockSession({ pinCode: '123456' });

// after
const status = await api.authApi.getAuthStatus();
if (!status.pinCode) {
  throw new Error('No PIN configured; call setup first');
}
await api.authApi.unlockSession({ pinCode: '123456' });
Defensive patterns

Strategy: validation

Validate before calling

async function ensurePinExists() {
  const { pinCode } = await api.authApi.getAuthStatus();
  if (!pinCode) throw new Error('No PIN configured');
}

Type guard

function hasPinConfigured(status: AuthStatusResponseDto): status is AuthStatusResponseDto & { pinCode: true } {
  return status.pinCode === true;
}

Try / catch

try {
  await api.authApi.resetPinCode({ password });
} catch (e) {
  if (e.response?.data?.message === 'User does not have a PIN code') {
    hidePinControls(); // no PIN to reset
  } else throw e;
}

Prevention

When it happens

Trigger: DELETE /auth/pin-code, PUT /auth/pin-code, or POST /auth/session/unlock against a user whose pinCode column is null. Common after a fresh install, after an admin reset the user's PIN, or when the user authenticates only via OAuth and never set a PIN.

Common situations: User authenticates by OAuth/password only and the client assumes a PIN exists because a 'locked' UI was shown; PIN was reset server-side but the client cached stale auth-status state; wrong endpoint used in client flow.

Related errors


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