toeverything/AFFiNE · error · ActionForbidden
action_forbidden
action_forbidden
Error message
BYOK settings require workspace owner or admin.
What it means
Authorization guard in the BYOK (bring-your-own-key) policy: mutating BYOK settings requires the caller to hold the Owner or Admin role in the target workspace, verified via workspaceUser.getActive. Members/visitors/collaborators — or unauthenticated or removed/suspended users (no active role record) — get ActionForbidden.
Source
Thrown at packages/backend/server/src/plugins/copilot/byok/policy.ts:34
const state = await this.quotaState.reconcileUserQuotaState(userId);
const flags = state.flags as { unlimitedCopilot?: boolean };
return (
flags.unlimitedCopilot ||
['pro', 'lifetime_pro', 'ai'].includes(state.plan)
);
}
async hasManagementAccess(workspaceId: string, userId?: string) {
if (!userId) return false;
const role = await this.models.workspaceUser.getActive(workspaceId, userId);
return (
role?.type === WorkspaceRole.Owner || role?.type === WorkspaceRole.Admin
);
}
async assertManagementAccess(workspaceId: string, userId?: string) {
if (!(await this.hasManagementAccess(workspaceId, userId))) {
throw new ActionForbidden(
'BYOK settings require workspace owner or admin.'
);
}
}
private async getWorkspaceOwnerId(workspaceId: string) {
const workspace = await this.models.workspace.get(workspaceId);
if (!workspace) {
return null;
}
try {
return (await this.models.workspaceUser.getOwner(workspaceId)).id;
} catch (error) {
if (
error instanceof Error &&
error.message === 'Workspace owner not found'
) {View on GitHub (pinned to b4c8548c09)
Solutions
- Have a workspace Owner or Admin perform BYOK settings changes, or promote the user first.
- Re-fetch the caller's workspace role before rendering the BYOK settings UI and hide it for non-admins.
- Check the request carries the right session/user for the workspace in the URL/mutation input.
- If membership looks wrong, confirm the workspaceUser record is active (not deleted/suspended).
Example fix
null
Defensive patterns
Strategy: validation
Validate before calling
const role = await client.getMyWorkspaceRole(workspaceId);
if (role !== 'Owner' && role !== 'Admin') {
hideByokSettings(); // only Owner/Admin may manage BYOK
} Type guard
const canManageByok = (roleType?: string): boolean => roleType === 'Owner' || roleType === 'Admin';
Try / catch
try {
await updateByokProfile(input);
} catch (e) {
if (e?.code === 'action_forbidden' && /owner or admin/i.test(e.message)) {
showToast('Ask a workspace owner or admin to change BYOK settings');
} else throw e;
} Prevention
- Re-fetch the active workspace role before rendering BYOK settings, not just at app load.
- Keep mutations scoped to the workspace the session user actually administers.
- Disable the save button for non-admins instead of letting the request fail.
When it happens
Trigger: A workspace Member calls update/create/delete BYOK profile endpoints or the BYOK settings GQL mutations; userId is undefined (session missing) so hasManagementAccess returns false; the caller's workspace membership was deactivated (getActive misses) between page load and save; cross-workspace request where the user is admin of a different workspace.
Common situations: Frontend shows the BYOK settings tab to non-admins based on stale role data; API scripts using a personal token from a member account; users demoted from admin while the settings page was open.
Understand the failure class
Background: "You do not have permission" / 403 Forbidden errors: authenticated but not allowed — causes and fixes across open-source libraries — this error's family across 31 libraries.
Related errors
AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18).
Data as JSON: /api/errors/cf93cb4f535a216a.
Report an issue: GitHub.