{"record":{"id":"e95baad57277a627","repo":"toeverything/AFFiNE","slug":"action-forbidden-e95baa","errorCode":"action_forbidden","errorMessage":"This feature is temporarily unavailable for you.","messagePattern":"This feature is temporarily unavailable for you\\.","errorType":"exception","errorClass":"ActionForbidden","httpStatus":403,"severity":"error","filePath":"packages/backend/server/src/core/workspaces/resolvers/member.ts","lineNumber":119,"sourceCode":"      throw new ActionForbidden(\n        'Workspace names containing links or domains cannot be used to invite members.'\n      );\n    }\n  }\n\n  @ResolveField(() => UserType, {\n    description: 'Owner of workspace',\n    complexity: 2,\n  })\n  async owner(@Parent() workspace: WorkspaceType) {\n    return this.models.workspaceUser.getOwner(workspace.id);\n  }\n\n  @ResolveField(() => Int, {\n    description: 'member count of workspace',\n    complexity: 2,\n  })\n  memberCount(@Parent() workspace: WorkspaceType) {\n    return this.models.workspaceUser.count(workspace.id);\n  }\n\n  @ResolveField(() => [InviteUserType], {\n    description: 'Members of workspace',\n    complexity: 2,\n  })\n  async members(\n    @CurrentUser() user: CurrentUser,\n    @Parent() workspace: WorkspaceType,\n    @Args('skip', { type: () => Int, nullable: true }) skip?: number,\n    @Args('take', { type: () => Int, nullable: true }) take?: number,\n    @Args('query', { type: () => String, nullable: true }) query?: string\n  ) {\n    await this.ac\n      .user(user.id)\n      .workspace(workspace.id)\n      .assert('Workspace.Users.Read');","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/2af30773aecd567f09b346e7b72fc69143144057/packages/backend/server/src/core/workspaces/resolvers/member.ts#L101-L137","documentation":"Thrown by WorkspaceMemberResolver.assertCanInviteOrShare when the acting user has been quarantined or banned by AFFiNE's invite-abuse detection system (runtime.isInviteAbuseUserQuarantinedOrBanned). It guards invite-link creation and share actions against accounts flagged for abusive invite behavior. The generic 'temporarily unavailable' message is intentional: it does not disclose that an abuse filter fired. Code action_forbidden, HTTP 403.","triggerScenarios":"A user flagged by the invite-abuse system calls createInviteLink (or any share/invite action routed through assertCanInviteOrShare). The user-level check runs before the workspace-level one, so the flagged actor is blocked on every workspace they touch.","commonSituations":"A user sent many invites in a short window and tripped the abuse detector; a shared/test account was previously quarantined; spam-like invite patterns from automated scripts reusing one account.","solutions":["Stop retrying — the block is server-side state tied to the account, not a transient failure.","Contact the workspace/server administrator or AFFiNE support to review and lift the quarantine on the account.","Use a different, non-flagged account for legitimate invite needs while the review is pending.","If you operate the server, inspect the invite-abuse quarantine state for the user via the runtime abuse controls and clear it if the flag was a false positive."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isActionForbiddenTemporarilyUnavailable(e: unknown): boolean {\n  const ext = (e as { extensions?: Record<string, unknown> })?.extensions;\n  return ext?.type === 'ACTION_FORBIDDEN' &&\n    String(ext?.message ?? '').includes('temporarily unavailable');\n}","tryCatchPattern":"try {\n  const link = await createInviteLink(workspaceId, expireTime);\n} catch (e) {\n  if (isActionForbiddenTemporarilyUnavailable(e)) {\n    // account quarantined/banned by invite-abuse system — surface 'contact support', do NOT retry\n    return showContactSupportState();\n  }\n  throw e;\n}","preventionTips":["Do not auto-retry on 403 action_forbidden — quarantine is server-side state.","Rate-limit your own invite/send actions well below abuse thresholds.","Distinguish this from the new-account cooldown by checking account age first."],"tags":["affine","graphql","invite","abuse-prevention","forbidden","rate-limit"],"backgroundTag":"account-suspended-or-banned","analyzedSha":"2af30773aecd567f09b346e7b72fc69143144057","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}