{"record":{"id":"f29dd38f2e6e5855","repo":"immich-app/immich","slug":"wrong-pin-code","errorCode":null,"errorMessage":"Wrong PIN code","messagePattern":"Wrong PIN code","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"warning","filePath":"server/src/services/auth.service.ts","lineNumber":192,"sourceCode":"    const hashed = await this.cryptoRepository.hashBcrypt(dto.newPinCode, SALT_ROUNDS);\n    await this.userRepository.update(auth.user.id, { pinCode: hashed });\n  }\n\n  private validatePinCode(\n    user: { pinCode: string | null; password: string | null },\n    dto: { pinCode?: string; password?: string },\n  ) {\n    if (!user.pinCode) {\n      throw new BadRequestException('User does not have a PIN code');\n    }\n\n    if (dto.password) {\n      if (!this.validateSecret(dto.password, user.password)) {\n        throw new BadRequestException('Wrong password');\n      }\n    } else if (dto.pinCode) {\n      if (!this.validateSecret(dto.pinCode, user.pinCode)) {\n        throw new BadRequestException('Wrong PIN code');\n      }\n    } else {\n      throw new BadRequestException('Either password or pinCode is required');\n    }\n  }\n\n  async adminSignUp(dto: SignUpDto): Promise<UserAdminResponseDto> {\n    const admin = await this.createUser({\n      isAdmin: true,\n      email: dto.email,\n      name: dto.name,\n      password: dto.password,\n      storageLabel: 'admin',\n    });\n\n    return mapUserAdmin(admin);\n  }\n","sourceCodeStart":174,"sourceCodeEnd":210,"githubUrl":"https://github.com/immich-app/immich/blob/199723261c6ffa897fec8ccdaea6359e39c37cc3/server/src/services/auth.service.ts#L174-L210","documentation":"BadRequestException (HTTP 400) raised inside validatePinCode when dto.pinCode was supplied (and dto.password was not) but it does not bcrypt-match the stored user.pinCode hash. Guards the PIN-protected reset/change/unlock endpoints. Failure does not lock the session; the caller may retry.","triggerScenarios":"PUT /auth/pin-code, DELETE /auth/pin-code, or POST /auth/session/unlock with body {pinCode: '<wrong>'} and no password field. Reached only after the !user.pinCode check passes.","commonSituations":"User mistyped the 6-digit PIN; PIN was changed on another device; PIN hash corrupted or migrated incorrectly; client sent the new PIN instead of the current one.","solutions":["Re-prompt the user for the current PIN (not the new one in a change flow).","If the PIN is forgotten, fall back to the password branch: send {password} instead of {pinCode}.","If both PIN and password are lost, an admin must clear the pinCode column out of band so the user can re-setup.","Confirm the PIN matches /^\\d{6}$/ before sending (validation runs client-side too)."],"exampleFix":"// before\nawait api.authApi.unlockSession({ pinCode: userInput });\n\n// after\nif (!/^\\d{6}$/.test(userInput)) {\n  showPinFormatError();\n  return;\n}\ntry {\n  await api.authApi.unlockSession({ pinCode: userInput });\n} catch (e) {\n  // fall back to password auth for unlock\n  await api.authApi.unlockSession({ password: await askPassword() });\n}","handlingStrategy":"try-catch","validationCode":"function isValidPinFormat(pin: string): boolean {\n  return /^\\d{6}$/.test(pin);\n}\nif (!isValidPinFormat(pin)) throw new Error('PIN must be 6 digits');","typeGuard":"function isSixDigitPin(value: unknown): value is string {\n  return typeof value === 'string' && /^\\d{6}$/.test(value);\n}","tryCatchPattern":"try {\n  await api.authApi.unlockSession({ pinCode });\n} catch (e) {\n  if (e.response?.data?.message === 'Wrong PIN code') {\n    retryOrFailToFallback();\n  } else throw e;\n}","preventionTips":["Validate /^\\d{6}$/ client-side before sending.","Distinguish current vs. new PIN in change flows.","Provide a password-based fallback for users who forgot the PIN."],"tags":["auth","pin-code","nestjs","immich"],"backgroundTag":null,"analyzedSha":"199723261c6ffa897fec8ccdaea6359e39c37cc3","analyzedAt":"2026-08-12T04:54:27.085Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}