{"record":{"id":"f2a0d1aef69274f9","repo":"immich-app/immich","slug":"invalid-user-token","errorCode":null,"errorMessage":"Invalid user token","messagePattern":"Invalid user token","errorType":"http","errorClass":"UnauthorizedException","httpStatus":401,"severity":"error","filePath":"server/src/services/auth.service.ts","lineNumber":585,"sourceCode":"        hasElevatedPermission = pinExpiresAt > now;\n\n        if (hasElevatedPermission && now.plus({ minutes: 5 }) > pinExpiresAt) {\n          await this.sessionRepository.update(session.id, {\n            pinExpiresAt: DateTime.now().plus({ minutes: 5 }).toJSDate(),\n          });\n        }\n      }\n\n      return {\n        user: session.user,\n        session: {\n          id: session.id,\n          hasElevatedPermission,\n        },\n      };\n    }\n\n    throw new UnauthorizedException('Invalid user token');\n  }\n\n  async unlockSession(auth: AuthDto, dto: SessionUnlockDto): Promise<void> {\n    if (!auth.session) {\n      throw new BadRequestException('This endpoint can only be used with a session token');\n    }\n\n    const user = await this.userRepository.getForPinCode(auth.user.id);\n    this.validatePinCode(user, { pinCode: dto.pinCode });\n\n    await this.sessionRepository.update(auth.session.id, {\n      pinExpiresAt: DateTime.now().plus({ minutes: 15 }).toJSDate(),\n    });\n  }\n\n  async lockSession(auth: AuthDto): Promise<void> {\n    if (!auth.session) {\n      throw new BadRequestException('This endpoint can only be used with a session token');","sourceCodeStart":567,"sourceCodeEnd":603,"githubUrl":"https://github.com/immich-app/immich/blob/199723261c6ffa897fec8ccdaea6359e39c37cc3/server/src/services/auth.service.ts#L567-L603","documentation":"Thrown at the tail of AuthService.validateSession when no valid session could be built from the supplied token+headers. The token is SHA-256 hashed and matched to a session record; if the session/user cannot be resolved, all earlier return paths are skipped and execution reaches this catch-all.","triggerScenarios":"A request authenticated with a session cookie/bearer token that does not hash to an existing session: logged-out token, session invalidated server-side (e.g. password change / admin forced logout), token from another instance, or a malformed token.","commonSituations":"Stale browser cookie after the user changed their password or an admin invalidated sessions; token copied from a different deployment; cookie truncated by a proxy; session expired and was reaped.","solutions":["Have the client re-authenticate (log in again) to obtain a fresh session token.","Verify the token is transmitted intact (no truncation/encoding by a reverse proxy or cookie size limit).","Confirm the sessions table is intact on this instance (not wiped by a restore/migration).","Treat the 401 as a trigger to clear the local session and redirect to login."],"exampleFix":"// before\nconst auth = await authService.validateSession(token, headers);\n\n// after\nlet auth: AuthDto;\ntry {\n  auth = await authService.validateSession(token, headers);\n} catch (e) {\n  if (e instanceof UnauthorizedException) {\n    // session is gone — force a clean re-login rather than retrying the dead token\n    await sessionStore.clear();\n    throw new UnauthorizedException('Session expired, please log in again');\n  }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"if (!token || token.length < 32) {\n  throw new UnauthorizedException('Session token is missing');\n}","typeGuard":"function isSessionToken(v: unknown): v is string {\n  return typeof v === 'string' && /^[A-Za-z0-9+/=]{32,}$/.test(v);\n}","tryCatchPattern":"try {\n  auth = await authService.validateSession(token, headers);\n} catch (e) {\n  if (e instanceof UnauthorizedException) {\n    await sessionStore.clear(); // dead token — purge locally\n    throw new UnauthorizedException('Session expired; please log in again');\n  }\n  throw e;\n}","preventionTips":["Clear stored tokens immediately on a 401 rather than retrying the same dead token.","Invalidate sessions server-side on password change so stale tokens cannot linger.","Ensure reverse proxies do not truncate large cookies."],"tags":["auth","session","unauthorized","cookie"],"backgroundTag":null,"analyzedSha":"199723261c6ffa897fec8ccdaea6359e39c37cc3","analyzedAt":"2026-08-12T04:54:27.085Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}