{"record":{"id":"3768b5a2ad8a04ee","repo":"immich-app/immich","slug":"unauthorized","errorCode":null,"errorMessage":"Unauthorized","messagePattern":"Unauthorized","errorType":"http","errorClass":"UnauthorizedException","httpStatus":401,"severity":"error","filePath":"server/src/services/auth.service.ts","lineNumber":151,"sourceCode":"    }\n\n    const hashedPassword = await this.cryptoRepository.hashBcrypt(newPassword, SALT_ROUNDS);\n\n    const updatedUser = await this.userRepository.update(user.id, { password: hashedPassword });\n\n    await this.eventRepository.emit('AuthChangePassword', {\n      userId: user.id,\n      currentSessionId: auth.session?.id,\n      invalidateSessions: dto.invalidateSessions,\n    });\n\n    return mapUserAdmin(updatedUser);\n  }\n\n  async setupPinCode(auth: AuthDto, { pinCode }: PinCodeSetupDto) {\n    const user = await this.userRepository.getForPinCode(auth.user.id);\n    if (!user) {\n      throw new UnauthorizedException();\n    }\n\n    if (user.pinCode) {\n      throw new BadRequestException('User already has a PIN code');\n    }\n\n    const hashed = await this.cryptoRepository.hashBcrypt(pinCode, SALT_ROUNDS);\n    await this.userRepository.update(auth.user.id, { pinCode: hashed });\n  }\n\n  async resetPinCode(auth: AuthDto, dto: PinCodeResetDto) {\n    const user = await this.userRepository.getForPinCode(auth.user.id);\n    this.validatePinCode(user, dto);\n\n    await this.userRepository.update(auth.user.id, { pinCode: null });\n    await this.sessionRepository.lockAll(auth.user.id);\n  }\n","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/immich-app/immich/blob/199723261c6ffa897fec8ccdaea6359e39c37cc3/server/src/services/auth.service.ts#L133-L169","documentation":"Thrown by AuthService.setupPinCode (POST /auth/pin-code) when userRepository.getForPinCode(auth.user.id) returns null. Because the caller is already authenticated (the route requires PinCodeCreate permission), a null user is unexpected and is treated as an authorization failure, raising a bare UnauthorizedException (401). It signals the authenticated subject does not map to a user row in the state required for PIN setup.","triggerScenarios":"POST /auth/pin-code by an authenticated session whose user cannot be loaded via getForPinCode (user deleted mid-session, or getForPinCode's query excludes the user); session token valid but user row gone.","commonSituations":"User account deleted while a session was still active; the authenticated identity is a non-user service account that has no PIN row; race between account deletion and a PIN-setup request.","solutions":["Re-authenticate to obtain a fresh session for a valid user, then retry PIN setup.","Confirm the account still exists before calling setupPinCode.","If the account was deleted, no PIN setup is possible; surface a re-login prompt to the user."],"exampleFix":"// before\nawait api.post('/auth/pin-code', { pinCode });\n\n// after\ntry {\n  await api.post('/auth/pin-code', { pinCode });\n} catch (e) {\n  if (e.response?.status === 401) {\n    await reAuthenticate(); // session/user no longer valid\n    await api.post('/auth/pin-code', { pinCode });\n  } else throw e;\n}","handlingStrategy":"try-catch","validationCode":"// PIN setup requires a live user; confirm the session is still valid first.\nconst { data } = await api.get('/auth/status').catch(() => ({ data: null }));\nif (!data?.user) {\n  throw new Error('Session no longer valid; re-authenticate before PIN setup.');\n}\nawait api.post('/auth/pin-code', { pinCode });","typeGuard":null,"tryCatchPattern":"try {\n  await api.post('/auth/pin-code', { pinCode });\n} catch (e) {\n  if (e.response?.status === 401) {\n    await reAuthenticate();\n    await api.post('/auth/pin-code', { pinCode });\n  } else throw e;\n}","preventionTips":["Re-authenticate when a 401 is returned from PIN setup rather than retrying blindly.","Confirm the account still exists before PIN operations.","Do not cache PIN-setup ability for deleted users."],"tags":["auth","pin-code","unauthorized","session"],"backgroundTag":null,"analyzedSha":"199723261c6ffa897fec8ccdaea6359e39c37cc3","analyzedAt":"2026-08-12T04:54:27.085Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}