{"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":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"warning","filePath":"server/src/services/auth.service.ts","lineNumber":183,"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":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/immich-app/immich/blob/199723261c6ffa897fec8ccdaea6359e39c37cc3/server/src/services/auth.service.ts#L165-L201","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Call GET /auth/status and check `pinCode` before invoking reset/change/unlock; if false, hide those controls.","If the user wants a PIN, POST /auth/pin-code to set one first.","If the UI expected a PIN to exist, force a re-fetch of auth status on each app foreground.","Verify no other admin/tool cleared the pinCode column out of band."],"exampleFix":"// before\nawait api.authApi.unlockSession({ pinCode: '123456' });\n\n// after\nconst status = await api.authApi.getAuthStatus();\nif (!status.pinCode) {\n  throw new Error('No PIN configured; call setup first');\n}\nawait api.authApi.unlockSession({ pinCode: '123456' });","handlingStrategy":"validation","validationCode":"async function ensurePinExists() {\n  const { pinCode } = await api.authApi.getAuthStatus();\n  if (!pinCode) throw new Error('No PIN configured');\n}","typeGuard":"function hasPinConfigured(status: AuthStatusResponseDto): status is AuthStatusResponseDto & { pinCode: true } {\n  return status.pinCode === true;\n}","tryCatchPattern":"try {\n  await api.authApi.resetPinCode({ password });\n} catch (e) {\n  if (e.response?.data?.message === 'User does not have a PIN code') {\n    hidePinControls(); // no PIN to reset\n  } else throw e;\n}","preventionTips":["Hide reset/change/unlock controls unless auth status reports pinCode=true.","Refetch auth status after every PIN setup/reset.","For OAuth-only users (no password), expect a PIN-less state by default."],"tags":["auth","pin-code","nestjs","immich","validation"],"backgroundTag":null,"analyzedSha":"199723261c6ffa897fec8ccdaea6359e39c37cc3","analyzedAt":"2026-08-12T04:54:27.085Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}