HeyPuter/puter · error · HttpError

signup_disabled

signup_disabled

Error message

User registration is disabled.

What it means

Thrown by POST /signup with HTTP 403 (legacyCode 'signup_disabled') when config.disable_user_signup is true AND the request is not 'claiming' an existing pseudo-user. A pseudo-user is an unconfirmed, password-null placeholder row (e.g. admin-pre-provisioned); claiming one is still allowed when signup is globally disabled. The check intentionally runs before duplicate checks so a closed endpoint does not leak which usernames/emails exist.

Source

Thrown at src/backend/controllers/auth/AuthController.ts:790

        // disabled endpoint doesn't reveal which usernames or emails
        // exist. Claiming a pre-existing placeholder row is still
        // allowed, so permanent signups look the email up first.
        if (this.config.disable_user_signup) {
            let claimable = false;
            if (!is_temp) {
                const existing =
                    (await this.stores.user.getByEmail(body.email)) ??
                    (await this.stores.user.getByCleanEmail(
                        cleanEmail(body.email),
                    ));
                claimable = Boolean(
                    existing &&
                    !existing.email_confirmed &&
                    existing.password === null,
                );
            }
            if (!claimable) {
                throw new HttpError(403, 'User registration is disabled.', {
                    legacyCode: 'signup_disabled',
                });
            }
        }

        // Duplicate username check
        if (await this.stores.user.getByUsername(body.username)) {
            throw new HttpError(
                400,
                'This username already exists in our database. Please use another one.',
                { legacyCode: 'bad_request' },
            );
        }

        // Duplicate confirmed-email check. A confirmed account (any
        // credential type — password OR OIDC) on this email → reject.
        //
        // A pseudo-user is an UNCONFIRMED placeholder row: email

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Confirm whether disable_user_signup is intended; if registration should be open, unset/flip it in the backend config.
  2. If invite-only is intended, have an admin pre-create the pseudo-user row (unconfirmed, password null) for the invitee's email, then have them sign up with that exact email to claim it.
  3. Surface this state to users with a clear 'registration is closed' message rather than retry loops.

Example fix

// before (operator): registration closed, users see signup_disabled
// config.json
{ "disable_user_signup": true }

// after: open registration
{ "disable_user_signup": false }
// — or, invite-only: admin pre-creates a pseudo-user row with the invitee's email,
//   password NULL and email_confirmed=0, then the invitee signs up with that email.
Defensive patterns

Strategy: try-catch

Validate before calling

// operator check: expose the flag to clients if you can
// GET /config/public -> { disable_user_signup: true }
if (serverConfig.disable_user_signup && !isClaimingPseudoUser(email)) {
  showNotice('Registration is closed on this server');
  return;
}

Try / catch

try {
  await signup(payload);
} catch (e) {
  if (e.statusCode === 403 && e.legacyCode === 'signup_disabled') {
    showRegistrationClosedNotice();
  } else throw e;
}

Prevention

When it happens

Trigger: Server configured with disable_user_signup:true, and the caller submits a fresh username/email pair that has no matching unconfirmed/password-null placeholder row. Claiming a pre-provisioned pseudo-user by submitting its email is the only path through.

Common situations: Self-hosted or locked-down deployments that set disable_user_signup to forbid open registration; an invite-only system where admins pre-create pseudo-user rows that invitees then claim; accidentally leaving the flag on after a migration.

Related errors


AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12). Data as JSON: /api/errors/e79802655163692e. Report an issue: GitHub.