{"record":{"id":"455ab47be60d4e40","repo":"toeverything/AFFiNE","slug":"space-access-denied-455ab4","errorCode":"space_access_denied","errorMessage":"You do not have permission to access Space ${spaceId}.","messagePattern":"You do not have permission to access Space (.+?)\\.","errorType":"exception","errorClass":"SpaceAccessDenied","httpStatus":403,"severity":"error","filePath":"packages/backend/server/src/core/workspaces/controller.ts","lineNumber":117,"sourceCode":"  // NOTE: because graphql can't represent a File, so we have to use REST API to get blob\n  @Public()\n  @Get('/:id/blobs/:name')\n  @CallMetric('controllers', 'workspace_get_blob')\n  async blob(\n    @CurrentUser() user: CurrentUser | undefined,\n    @Param('id') workspaceId: string,\n    @Param('name') name: string,\n    @Query('redirect') redirect: string | undefined,\n    @Res() res: Response\n  ) {\n    const canReadWorkspace = await this.ac\n      .user(user?.id ?? 'anonymous')\n      .workspace(workspaceId)\n      .can('Workspace.Read');\n    const canReadSharedWorkspaceBlobs =\n      await this.canReadSharedWorkspaceBlobs(workspaceId);\n    if (!canReadWorkspace && !canReadSharedWorkspaceBlobs) {\n      throw new SpaceAccessDenied({ spaceId: workspaceId });\n    }\n    const { body, metadata, redirectUrl } = await this.storage.get(\n      workspaceId,\n      name,\n      true\n    );\n\n    if (redirectUrl) {\n      // redirect to signed url\n      if (redirect === 'manual') {\n        return res.send({\n          url: redirectUrl,\n        });\n      } else {\n        return res.redirect(redirectUrl);\n      }\n    }\n","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/backend/server/src/core/workspaces/controller.ts#L99-L135","documentation":"Thrown by WorkspacesController.blob (GET /api/workspaces/:id/blobs/:name) when the caller has neither Workspace.Read on the space nor shared-workspace blob access — canReadSharedWorkspaceBlobs requires models.workspace.allowSharing(workspaceId) AND docAccessPolicy.hasPublicExternal(workspaceId). The error deliberately hides blob existence from non-members.","triggerScenarios":"Fetching a blob URL while logged out or as a non-member on a workspace that has not enabled blob sharing alongside publicly shared docs; expired session so the user resolves to 'anonymous'.","commonSituations":"Hotlinked/embedded blob images on external sites after sharing was disabled; users reopening old image links after removal from the workspace; frontend requests sent without refreshed auth cookies.","solutions":["Sign in as a workspace member with Workspace.Read (owner/admin/member)","In workspace settings, enable blob sharing and keep at least one doc publicly shared so canReadSharedWorkspaceBlobs passes","Refresh expired auth tokens/cookies before fetching blob URLs","Fetch blobs through the authenticated client instead of raw public links"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isSpaceAccessDenied(e: unknown): e is { code: 'space_access_denied'; spaceId: string } {\n  return typeof e === 'object' && e !== null && (e as any).code === 'space_access_denied';\n}","tryCatchPattern":"try {\n  return await fetchBlob(wsId, name);\n} catch (e) {\n  if (isSpaceAccessDenied(e)) {\n    if (!isAuthenticated()) return reauthenticateAndRetry(); // anonymous may be the cause\n    return renderPrivateBlobPlaceholder();\n  }\n  throw e;\n}","preventionTips":["Fetch blob URLs through the authenticated client, not raw hotlinks","Refresh sessions before rendering embedded blob content","If embedding externally, ensure workspace blob sharing + a public doc exist","On membership loss, stop reusing previously working blob URLs"],"tags":["authorization","blob","sharing","workspace"],"backgroundTag":"permission-denied","analyzedSha":"b4c8548c09da21b2898443559a5b846f0ccf5dd8","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}