toeverything/AFFiNE · error · ActionForbidden
action_forbidden
action_forbidden
Error message
You are not allowed to perform this action.
What it means
During POST /oauth/preflight, if the `client` field fails the OAuthClientSchema enum ('web', 'affine', 'affine-canary', 'affine-beta', 'affine-dev'), the server deliberately throws a generic ActionForbidden instead of revealing the valid client list. So 'not allowed to perform this action' here almost always means 'unrecognized OAuth client identifier'.
Source
Thrown at packages/backend/server/src/plugins/oauth/controller.ts:50
private readonly sessionIssuer: SessionIssuer,
private readonly oauth: OAuthService,
private readonly providerFactory: OAuthProviderFactory,
private readonly url: URLHelper
) {}
@Public()
@UseNamedGuard('version')
@Post('/preflight')
@HttpCode(HttpStatus.OK)
async preflight(@Req() req: Request, @Body() body?: unknown) {
const input = OAuthPreflightBodySchema.safeParse(body);
if (!input.success) {
const fields = new Set(input.error.issues.map(issue => issue.path[0]));
if (fields.has('client_nonce')) {
throw new MissingOauthQueryParameter({ name: 'client_nonce' });
}
if (fields.has('client')) {
throw new ActionForbidden();
}
if (fields.has('provider')) {
const provider =
body && typeof body === 'object' && 'provider' in body
? String(body.provider)
: '';
throw new UnknownOauthProvider({ name: provider });
}
throw new MissingOauthQueryParameter({ name: 'provider' });
}
const {
provider: unknownProviderName,
redirect_uri: redirectUri,
client,
client_nonce: clientNonce,
} = input.data;
View on GitHub (pinned to 591f874dad)
Solutions
- Set client to one of the allowed values: 'web' (browser), 'affine' (stable desktop), 'affine-canary', 'affine-beta', or 'affine-dev'
- If you are building a custom integration, use 'web' and handle the flow in a browser context
- Upgrade the desktop/electron client to a version whose identifier the server still accepts
Example fix
// before
{ provider: 'Google', client: 'desktop', client_nonce: nonce }
// after
{ provider: 'Google', client: 'affine', client_nonce: nonce } Defensive patterns
Strategy: validation
Validate before calling
const ALLOWED_CLIENTS = ['web', 'affine', 'affine-canary', 'affine-beta', 'affine-dev'] as const;
if (!ALLOWED_CLIENTS.includes(client)) {
throw new Error(`client must be one of ${ALLOWED_CLIENTS.join(', ')} — got '${client}'`);
} Type guard
type AllowedClient = 'web' | 'affine' | 'affine-canary' | 'affine-beta' | 'affine-dev';
function isAllowedClient(c: unknown): c is AllowedClient {
return typeof c === 'string' && ['web', 'affine', 'affine-canary', 'affine-beta', 'affine-dev'].includes(c);
} Try / catch
try { await post('/oauth/preflight', body); } catch (e) {
if ((e as any).code === 'action_forbidden' && !isAllowedClient(body.client)) {
throw new Error(`Server rejected client '${body.client}' — use an allowed client identifier`);
}
throw e;
} Prevention
- Hard-code the allowed client enum in your integration and type it, so invalid values can't be sent
- Remember ActionForbidden on preflight deliberately hides the reason — first suspect is always the client field
- Track server release notes: client identifiers have been added/removed between versions
When it happens
Trigger: Preflight body with client missing or set to anything outside the enum — e.g. client: 'mobile', 'my-app', 'ios', or a typo like 'Affine'. A missing client also routes here because the zod issue path is 'client'.
Common situations: Building a third-party client against the AFFiNE OAuth endpoints with a custom client name; older desktop build whose client identifier was removed from the enum; frontend refactor passing the wrong variable.
Related errors
- missing_oauth_query_parameter
- unknown_oauth_provider
- action_forbidden
- action_forbidden
- wrong_sign_in_method
AI-assisted analysis of toeverything/AFFiNE@591f874dad (2026-08-18).
Data as JSON: /api/errors/9adbef7f73af2c17.
Report an issue: GitHub.