immich-app/immich · error · BadRequestException

User already has a PIN code

Error message

User already has a PIN code

What it means

setupPinCode only allows a user to set a PIN once. If the loaded user already has a pinCode stored, it refuses to overwrite it with a 400 'User already has a PIN code'; changing an existing PIN must go through changePinCode, and removal through resetPinCode.

Solutions

  1. Use the change PIN endpoint (changePinCode) instead of setup to update an existing PIN.
  2. If the PIN is unknown, use the reset PIN flow (resetPinCode) which authenticates with the account password.
  3. Check GET /api/auth/pin-code/status before calling setup to see whether a PIN already exists.

Example fix

// before
await api.authenticationApi.setupPinCode({ pinCode }); // PIN already set
// after
const { hasPin } = await api.authenticationApi.getPinCodeStatus();
if (hasPin) {
  await api.authenticationApi.changePinCode({ pinCode: currentPin, newPinCode });
} else {
  await api.authenticationApi.setupPinCode({ pinCode });
}
Defensive patterns

Strategy: validation

Validate before calling

const status = await api.authenticationApi.getPinCodeStatus();
if (status.hasPin) {
  throw new Error('PIN already set; use changePinCode or resetPinCode instead of setup');
}

Try / catch

try {
  await api.authenticationApi.setupPinCode({ pinCode });
} catch (e) {
  if (e.status === 400 && e.message === 'User already has a PIN code') {
    // fall back to change/reset flow
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling POST /api/auth/pin-code/setup for a user whose record already has a non-null pinCode hash.

Common situations: Double-submitting the setup request (e.g. retry after timeout when the first call actually succeeded); calling setup instead of change when rotating the PIN; client state out of sync after the PIN was set on another device.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


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

Appendix: source

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

    });

    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) {
      throw new UnauthorizedException();
    }

    if (user.pinCode) {
      throw new BadRequestException('User already has a PIN code');
    }

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

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

    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);

View on GitHub (pinned to f48d4b3321)