{"record":{"id":"5c703bc3221e70f2","repo":"immich-app/immich","slug":"user-does-not-have-a-pin-code","errorCode":null,"errorMessage":"User does not have a PIN code","messagePattern":"User does not have a PIN code","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"server/src/services/auth.service.ts","lineNumber":187,"sourceCode":"\n    await this.userRepository.update(auth.user.id, { pinCode: null });\n    await this.sessionRepository.lockAll(auth.user.id);\n  }\n\n  async changePinCode(auth: AuthDto, dto: PinCodeChangeDto) {\n    const user = await this.userRepository.getForPinCode(auth.user.id);\n    this.validatePinCode(user, dto);\n\n    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,","sourceCodeStart":169,"sourceCodeEnd":205,"githubUrl":"https://github.com/immich-app/immich/blob/f48d4b332127ad365ba256108799ca8f571d2dd5/server/src/services/auth.service.ts#L169-L205","documentation":"validatePinCode is the shared gate for resetPinCode, changePinCode, and unlockSession. It first requires that the user actually has a PIN configured; if the stored pinCode is null, all PIN operations are invalid and it throws a 400 'User does not have a PIN code', since there is nothing to verify against.","triggerScenarios":"Calling resetPinCode, changePinCode, or unlockSession with pinCode/password credentials for a user account that has never set up a PIN (stored pinCode is null).","commonSituations":"Client UI showing PIN entry before the user completed PIN setup; calling unlockSession after another device reset the PIN; race where the PIN was cleared between listing and calling.","solutions":["Call the PIN setup endpoint first (setupPinCode) to create a PIN, then retry the operation.","Check PIN status via GET /api/auth/pin-code/status before invoking any PIN endpoint.","Use the password-based flow (e.g. password login) instead of PIN unlock when no PIN exists."],"exampleFix":"// before\nawait api.authenticationApi.unlockSession({ pinCode }); // no PIN set\n// after\nconst { hasPin } = await api.authenticationApi.getPinCodeStatus();\nif (!hasPin) await api.authenticationApi.setupPinCode({ pinCode });\nawait api.authenticationApi.unlockSession({ pinCode });","handlingStrategy":"validation","validationCode":"const status = await api.authenticationApi.getPinCodeStatus();\nif (!status.hasPin) {\n  throw new Error('No PIN configured; set one up first or use password auth');\n}","typeGuard":null,"tryCatchPattern":"try {\n  await api.authenticationApi.unlockSession({ pinCode });\n} catch (e) {\n  if (e.status === 400 && e.message === 'User does not have a PIN code') {\n    // route user to PIN setup\n  }\n  throw e;\n}","preventionTips":["Gate PIN-based UI behind a hasPin status check.","Refresh PIN status after reset/change operations.","Offer a password fallback when the PIN is not configured."],"tags":["pin-code","precondition","validation"],"backgroundTag":"invalid-state-transition","analyzedSha":"f48d4b332127ad365ba256108799ca8f571d2dd5","analyzedAt":"2026-09-15T07:20:19.675Z","contentChangedAt":"2026-09-15T07:20:19.675Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}