{"record":{"id":"a7d2ca0012586bf1","repo":"immich-app/immich","slug":"forbidden","errorCode":null,"errorMessage":"Forbidden","messagePattern":"Forbidden","errorType":"http","errorClass":"ForbiddenException","httpStatus":403,"severity":"error","filePath":"server/src/services/auth.service.ts","lineNumber":218,"sourceCode":"    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\n  async authenticate({ headers, queryParams, metadata }: ValidateRequest): Promise<AuthDto> {\n    const authDto = await this.validate({ headers, queryParams });\n    const { adminRoute, sharedLinkRoute, uri } = metadata;\n    const requestedPermission = metadata.permission ?? Permission.All;\n\n    if (!authDto.user.isAdmin && adminRoute) {\n      this.logger.warn(`Denied access to admin only route: ${uri}`);\n      throw new ForbiddenException('Forbidden');\n    }\n\n    if (authDto.sharedLink && !sharedLinkRoute) {\n      this.logger.warn(`Denied access to non-shared route: ${uri}`);\n      throw new ForbiddenException('Forbidden');\n    }\n\n    if (\n      authDto.apiKey &&\n      requestedPermission !== false &&\n      !isGranted({ requested: [requestedPermission], current: authDto.apiKey.permissions })\n    ) {\n      throw new ForbiddenException(`Missing required permission: ${requestedPermission}`);\n    }\n\n    return authDto;\n  }\n","sourceCodeStart":200,"sourceCodeEnd":236,"githubUrl":"https://github.com/immich-app/immich/blob/199723261c6ffa897fec8ccdaea6359e39c37cc3/server/src/services/auth.service.ts#L200-L236","documentation":"ForbiddenException (HTTP 403) thrown by AuthService.authenticate when metadata.adminRoute is true and the resolved user is not an admin. The auth guard attaches adminRoute metadata to admin-only endpoints; non-admins are rejected before the handler runs. A warn log records the denied URI for auditing.","triggerScenarios":"Any request to an admin-only route (e.g. DELETE /admin/users/:id, POST /system/preferences) carrying a valid non-admin session/API-key/shared-link token. The token authenticates successfully but authorization fails at the adminRoute check.","commonSituations":"Normal user token reused against admin endpoints after a role demotion; client uses the wrong account; shared link or API key with insufficient privileges hitting admin endpoints; JWT not refreshed after an admin role change.","solutions":["Use an admin user's session token/API key for admin endpoints.","If the user was recently promoted, log out and back in to refresh the cached isAdmin flag.","Gate the admin UI behind an isAdmin check before issuing the request.","Check server logs for the warn line 'Denied access to admin only route' to identify the offending token."],"exampleFix":"// before\nconst client = axios.create({ headers: { Authorization: `Bearer ${userToken}` } });\nawait client.delete('/admin/users/123');\n\n// after\nconst me = await api.userApi.getMyUserInfo();\nif (!me.isAdmin) throw new Error('admin required');\nconst client = axios.create({ headers: { Authorization: `Bearer ${adminToken}` } });\nawait client.delete('/admin/users/123');","handlingStrategy":"validation","validationCode":"async function isAdmin(token: string): Promise<boolean> {\n  const { data } = await axios.get('/users/me', { headers: { Authorization: `Bearer ${token}` } });\n  return data.isAdmin === true;\n}","typeGuard":"function isAdminUser(user: { isAdmin?: boolean }): user is { isAdmin: true } {\n  return user.isAdmin === true;\n}","tryCatchPattern":"try {\n  await axios.delete(`/admin/users/${id}`, { headers: auth() });\n} catch (e) {\n  if (e.response?.status === 403) {\n    showInsufficientPrivileges();\n  } else throw e;\n}","preventionTips":["Gate admin UI on a fresh /users/me.isAdmin check.","Re-login after role changes to refresh cached claims.","Log 403s and surface them as permission errors, not generic failures."],"tags":["auth","authorization","admin","nestjs","immich"],"backgroundTag":null,"analyzedSha":"199723261c6ffa897fec8ccdaea6359e39c37cc3","analyzedAt":"2026-08-12T04:54:27.085Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}