{"record":{"id":"8ed44426914f4d84","repo":"immich-app/immich","slug":"not-found-or-no-request-permission-access","errorCode":null,"errorMessage":"Not found or no ${request.permission} access","messagePattern":"Not found or no (.+?) access","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"server/src/utils/access.ts","lineNumber":40,"sourceCode":"  auth: AuthDto;\n  permission: Permission;\n  ids: Set<string> | string[];\n};\n\ntype SharedLinkAccessRequest = { sharedLink: AuthSharedLink; permission: Permission; ids: Set<string> };\ntype OtherAccessRequest = { auth: AuthDto; permission: Permission; ids: Set<string> };\n\nexport const requireUploadAccess = (auth: AuthDto | null): AuthDto => {\n  if (!auth || (auth.sharedLink && !auth.sharedLink.allowUpload)) {\n    throw new UnauthorizedException();\n  }\n  return auth;\n};\n\nexport const requireAccess = async (access: AccessRepository, request: AccessRequest) => {\n  const allowedIds = await checkAccess(access, request);\n  if (!areSetsEqual(new Set(request.ids), allowedIds)) {\n    throw new BadRequestException(`Not found or no ${request.permission} access`);\n  }\n};\n\nexport const checkAccess = async (\n  access: AccessRepository,\n  { ids, auth, permission }: AccessRequest,\n): Promise<Set<string>> => {\n  const idSet = Array.isArray(ids) ? new Set(ids) : ids;\n  if (idSet.size === 0) {\n    return new Set<string>();\n  }\n\n  return auth.sharedLink\n    ? checkSharedLinkAccess(access, { sharedLink: auth.sharedLink, permission, ids: idSet })\n    : checkOtherAccess(access, { auth, permission, ids: idSet });\n};\n\nconst checkSharedLinkAccess = async (","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/immich-app/immich/blob/199723261c6ffa897fec8ccdaea6359e39c37cc3/server/src/utils/access.ts#L22-L58","documentation":"A BadRequestException (HTTP 400) thrown by requireAccess when the set of resource ids the user is allowed to act on does not exactly equal the set of ids requested. checkAccess returns the allowed id subset; if any requested id is missing (either it does not exist or the user lacks the permission), the whole request is rejected.","triggerScenarios":"Calling any access-gated operation (album update, asset delete, person merge, workflow delete, etc.) with one or more ids the caller does not own or that do not exist. The message includes the permission name, e.g., 'Not found or no AssetUpdate access'.","commonSituations":"Operating on a resource id from another user; stale client id after a resource was deleted; mixed batch where some ids are valid and others are not; permission not granted (e.g., non-owner trying an owner-only action).","solutions":["Verify each id belongs to the calling user (or is shared with them) before the request.","Refresh client-side id lists after deletions to avoid referencing removed resources.","For batch operations, split ids and operate only on those the user owns, or request access to the others.","Check the exact permission in the error message to know which access path failed."],"exampleFix":"// before\nawait api.deleteAssets(['a','b','c']); // b owned by someone else -> 400\n\n// after\nconst owned = await api.getMyAssets();\nconst deletable = ['a','b','c'].filter((id) => owned.some((a) => a.id === id));\nawait api.deleteAssets(deletable);","handlingStrategy":"validation","validationCode":"async function filterAccessibleIds(auth, permission, ids) {\n  // use a bulk access check / ownership filter before the mutating call\n  const owned = await api.checkAccess(permission, ids);\n  return ids.filter((id) => owned.has(id));\n}\nconst safeIds = await filterAccessibleIds(auth, permission, ids);\nif (safeIds.length !== ids.length) {\n  return badRequest(`Missing access for ${ids.length - safeIds.length} id(s)`);\n}","typeGuard":"const isAccessDeniedError = (e: unknown): boolean =>\n  typeof e === 'object' && e !== null && (e as any).status === 400 &&\n  typeof (e as any).message === 'string' && (e as any).message.startsWith('Not found or no');","tryCatchPattern":"try {\n  await api.deleteAssets(ids);\n} catch (e) {\n  if (isAccessDeniedError(e)) {\n    // retry with only owned ids discovered via search\n    const owned = await getOwnedIds();\n    await api.deleteAssets(ids.filter((id) => owned.includes(id)));\n    return;\n  }\n  throw e;\n}","preventionTips":["Operate only on ids returned by the user's own list/search endpoints.","Refresh id lists after deletions to avoid stale references.","For batch ops, split by ownership rather than sending mixed id sets."],"tags":["authorization","access-control","permission","nestjs"],"backgroundTag":null,"analyzedSha":"199723261c6ffa897fec8ccdaea6359e39c37cc3","analyzedAt":"2026-08-12T04:54:27.085Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}