toeverything/AFFiNE · error · ActionForbidden

action_forbidden

action_forbidden

Error message

You are not allowed to perform this action.

What it means

MagicLinkService.send validates the callbackUrl argument against UrlService.isAllowedCallbackUrl before sending a sign-in email; URLs not on the server-configured allowlist throw ActionForbidden (action_forbidden). The callback URL is where the user lands after clicking the magic link, and the allowlist exists to stop open-redirect / phishing abuse of the mail flow.

Solutions

  1. Use the default relative callback '/magic-link' unless you specifically configured another
  2. Add your exact frontend origin/path to the server's allowed callback URL configuration and restart
  3. Verify the value you send matches the configured entry character-for-character (scheme, host, no trailing slash)

Example fix

// before
await post('/auth/magic-link/send', { email, callbackUrl: 'https://myapp.example/cb' });

// after
await post('/auth/magic-link/send', { email, callbackUrl: '/magic-link' }); // or an origin present in the server allowlist
Defensive patterns

Strategy: validation

Validate before calling

const ALLOWED_CALLBACK_URLS = ['/magic-link']; // mirror the server allowlist
function isAllowedCallbackUrl(url: string): boolean {
  return ALLOWED_CALLBACK_URLS.includes(url);
}

Prevention

When it happens

Trigger: POST to the magic-link send endpoint with callbackUrl pointing at a foreign origin (https://evil.example); a self-hosted frontend origin that was never added to the server's allowed callback list; passing an absolute URL where only specific relative paths are allowed; trailing-slash or case differences that miss the allowlist match.

Common situations: Self-hosting on a new domain without updating allowed callback URL config; renaming/relocating the web app; staging environment pointing at production API; third-party portals trying to relay AFFiNE sign-in.

Related errors


AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18). Data as JSON: /api/errors/46dc0982afc20960. Report an issue: GitHub.

Appendix: source

Thrown at packages/backend/server/src/core/auth/magic-link.ts:42

  constructor(
    private readonly url: URLHelper,
    private readonly auth: AuthService,
    private readonly models: Models,
    private readonly config: Config,
    private readonly crypto: CryptoHelper
  ) {}

  async send(
    email: string,
    callbackUrl = '/magic-link',
    clientNonce?: string,
    metadata?: Pick<MailDeliveryMetadata, 'source'>
  ) {
    validators.assertValidEmail(email);

    if (!this.url.isAllowedCallbackUrl(callbackUrl)) {
      throw new ActionForbidden();
    }

    const callbackUrlObj = this.url.url(callbackUrl);
    const redirectUriInCallback =
      callbackUrlObj.searchParams.get('redirect_uri');
    if (
      redirectUriInCallback &&
      !this.url.isAllowedRedirectUri(redirectUriInCallback)
    ) {
      throw new ActionForbidden();
    }

    const user = await this.models.user.getUserByEmail(email, {
      withDisabled: true,
    });

    if (!user) {
      await this.assertSignupAllowed(email);

View on GitHub (pinned to b4c8548c09)