toeverything/AFFiNE · error · SignUpForbidden

sign_up_forbidden

sign_up_forbidden

Error message

You are not allowed to sign up.

What it means

MagicLinkService.assertSignupAllowed runs when the email has no existing user. If config.auth.allowSignup is false (self-hosted instances after the first user, or signup disabled by policy), it throws SignUpForbidden (sign_up_forbidden) - passwordless sign-in cannot silently create accounts when signup is closed.

Solutions

  1. An administrator enables signup in auth config, or invites the user from workspace member management
  2. Sign in with an existing account instead
  3. Double-check the email address - a typo makes an existing user look like a new signup
  4. Self-hosters: verify the allowSignup setting and licensing/user-limit state
Defensive patterns

Strategy: try-catch

Type guard

function isSignUpForbidden(e: unknown): boolean {
  return typeof e === 'object' && e !== null && (e as { code?: string }).code === 'sign_up_forbidden';
}

Try / catch

try {
  await sendMagicLink(email);
} catch (e) {
  if (isSignUpForbidden(e)) {
    show('Sign-up is disabled on this instance. Ask an admin to invite you.');
  } else throw e;
}

Prevention

When it happens

Trigger: A brand-new email requests a magic link on an instance where signup is disabled; the workspace is at its licensed user cap with signup turned off; admins closed signup after onboarding and a new contractor tries the flow.

Common situations: Self-hosted AFFiNE where the first account already exists and allowSignup defaults off; enterprise deployments with invite-only policy; users mistyping their email so they look 'new' to the system.

Related errors


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

Appendix: source

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

      TokenType.SignIn,
      consumed.token,
      {
        credential: email,
      }
    );

    if (!tokenRecord) {
      throw new InvalidEmailToken();
    }

    const user = await this.models.user.fulfill(email);

    return { userId: user.id, method: 'magic_link' };
  }

  private async assertSignupAllowed(email: string) {
    if (!this.config.auth.allowSignup) {
      throw new SignUpForbidden();
    }

    if (!this.config.auth.requireEmailDomainVerification) {
      return;
    }

    if (!(await verifyEmailDomainRecords(email))) {
      throw new InvalidEmail({ email });
    }
  }
}

View on GitHub (pinned to b4c8548c09)