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

  1. Set client to one of the allowed values: 'web' (browser), 'affine' (stable desktop), 'affine-canary', 'affine-beta', or 'affine-dev'
  2. If you are building a custom integration, use 'web' and handle the flow in a browser context
  3. 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

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


AI-assisted analysis of toeverything/AFFiNE@591f874dad (2026-08-18). Data as JSON: /api/errors/9adbef7f73af2c17. Report an issue: GitHub.