toeverything/AFFiNE · error · SignUpForbidden

sign_up_forbidden

sign_up_forbidden

Error message

You are not allowed to sign up.

What it means

SignUpForbidden thrown at packages/backend/server/src/plugins/oauth/service.ts:216 when an OAuth identity has no existing connected account and the server has auth.allowSignupForOauth disabled. AFFiNE first tries to match a connected account; only when none exists does it consult the signup flag before creating a user via fulfill(). The error code is sign_up_forbidden (HTTP class action_forbidden).

Source

Thrown at packages/backend/server/src/plugins/oauth/service.ts:216

      provider,
      externalAccount.id
    );

    if (connectedAccount) {
      await this.updateConnectedAccount(connectedAccount, tokens);

      if (
        !connectedAccount.user.emailVerifiedAt &&
        externalAccount.email.toLowerCase() ===
          connectedAccount.user.email.toLowerCase()
      ) {
        await this.auth.setEmailVerified(connectedAccount.userId);
      }
      return connectedAccount.user;
    }

    if (!this.config.auth.allowSignupForOauth) {
      throw new SignUpForbidden();
    }

    const user = await this.models.user.fulfill(externalAccount.email, {
      name: externalAccount.name,
      avatarUrl: externalAccount.avatarUrl,
    });

    await this.models.user.createConnectedAccount({
      userId: user.id,
      provider,
      providerAccountId: externalAccount.id,
      accessToken: tokens.accessToken,
      refreshToken: tokens.refreshToken,
      expiresAt: tokens.expiresAt,
    });

    return user;
  }

View on GitHub (pinned to b4c8548c09)

Solutions

  1. Enable OAuth signup in config (auth.allowSignupForOauth, default true) and restart the server.
  2. Otherwise the user should sign in with their original method (email/password or a previously linked provider).
  3. Existing users can link the provider in user settings so future OAuth sign-ins match a connected account.
  4. Admins can also pre-create accounts so the fulfill() path is never needed.

Example fix

# before
AFFINE_SERVER_SUB_PATH=... # auth config with signup closed
# auth.allowSignupForOauth = false

# after (config)
{"auth":{"allowSignupForOauth":true}}
# or via env/config file depending on your deployment, then restart
Defensive patterns

Strategy: validation

Validate before calling

// Client: only offer SSO 'sign in' when new-user signup is allowed
const serverConfig = await fetch('/api/server/info').then(r => r.json());
// if OAuth signup is disabled, route new users to an invite/existing-account flow first

Try / catch

try {
  await oauth.verifyCallback(input);
} catch (err) {
  if (err instanceof SignUpForbidden) {
    // show 'Sign-up is disabled on this server. Sign in with your existing account or ask an admin.'
  }
}

Prevention

When it happens

Trigger: A brand-new user signs in with Google/GitHub/OIDC on a server where allowSignupForOauth=false; or an existing email-based user tries an OAuth provider they never linked — no connected account exists, so the signup gate applies even though the email is known.

Common situations: Self-hosters lock the instance to invited/existing users (allowSignupForOauth=false) and users assume OAuth login is separate from signup; users who registered with email/password try 'Sign in with Google' before linking it in settings; admin disables OAuth signup after rollout.

Related errors


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