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
- Enable OAuth signup in config (auth.allowSignupForOauth, default true) and restart the server.
- Otherwise the user should sign in with their original method (email/password or a previously linked provider).
- Existing users can link the provider in user settings so future OAuth sign-ins match a connected account.
- 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
- Set auth.allowSignupForOauth deliberately (it defaults to true).
- Tell users to link providers in account settings before relying on SSO login when signup is closed.
- Surface a clear 'signup disabled' message in the client instead of a generic OAuth failure.
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
- sign_up_forbidden
- invalid_email
- unknown_oauth_provider
- missing_oauth_query_parameter
- unknown_oauth_provider
AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18).
Data as JSON: /api/errors/398f91cd37190802.
Report an issue: GitHub.