{"record":{"id":"e79802655163692e","repo":"HeyPuter/puter","slug":"signup-disabled","errorCode":"signup_disabled","errorMessage":"User registration is disabled.","messagePattern":"User registration is disabled\\.","errorType":"http","errorClass":"HttpError","httpStatus":403,"severity":"error","filePath":"src/backend/controllers/auth/AuthController.ts","lineNumber":790,"sourceCode":"        // disabled endpoint doesn't reveal which usernames or emails\n        // exist. Claiming a pre-existing placeholder row is still\n        // allowed, so permanent signups look the email up first.\n        if (this.config.disable_user_signup) {\n            let claimable = false;\n            if (!is_temp) {\n                const existing =\n                    (await this.stores.user.getByEmail(body.email)) ??\n                    (await this.stores.user.getByCleanEmail(\n                        cleanEmail(body.email),\n                    ));\n                claimable = Boolean(\n                    existing &&\n                    !existing.email_confirmed &&\n                    existing.password === null,\n                );\n            }\n            if (!claimable) {\n                throw new HttpError(403, 'User registration is disabled.', {\n                    legacyCode: 'signup_disabled',\n                });\n            }\n        }\n\n        // Duplicate username check\n        if (await this.stores.user.getByUsername(body.username)) {\n            throw new HttpError(\n                400,\n                'This username already exists in our database. Please use another one.',\n                { legacyCode: 'bad_request' },\n            );\n        }\n\n        // Duplicate confirmed-email check. A confirmed account (any\n        // credential type — password OR OIDC) on this email → reject.\n        //\n        // A pseudo-user is an UNCONFIRMED placeholder row: email","sourceCodeStart":772,"sourceCodeEnd":808,"githubUrl":"https://github.com/HeyPuter/puter/blob/908ec23eda38526170322c3edf71ba45ecb1ca95/src/backend/controllers/auth/AuthController.ts#L772-L808","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Confirm whether disable_user_signup is intended; if registration should be open, unset/flip it in the backend config.","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.","Surface this state to users with a clear 'registration is closed' message rather than retry loops."],"exampleFix":"// before (operator): registration closed, users see signup_disabled\n// config.json\n{ \"disable_user_signup\": true }\n\n// after: open registration\n{ \"disable_user_signup\": false }\n// — or, invite-only: admin pre-creates a pseudo-user row with the invitee's email,\n//   password NULL and email_confirmed=0, then the invitee signs up with that email.","handlingStrategy":"try-catch","validationCode":"// operator check: expose the flag to clients if you can\n// GET /config/public -> { disable_user_signup: true }\nif (serverConfig.disable_user_signup && !isClaimingPseudoUser(email)) {\n  showNotice('Registration is closed on this server');\n  return;\n}","typeGuard":null,"tryCatchPattern":"try {\n  await signup(payload);\n} catch (e) {\n  if (e.statusCode === 403 && e.legacyCode === 'signup_disabled') {\n    showRegistrationClosedNotice();\n  } else throw e;\n}","preventionTips":["Surface disable_user_signup to clients so the UI can hide/disable the signup form.","For invite-only, pre-create pseudo-user rows (password NULL, email_confirmed 0) and have invitees claim by email.","Communicate 'registration closed' clearly instead of letting users retry."],"tags":["auth","signup","config","operator","forbidden"],"backgroundTag":null,"analyzedSha":"908ec23eda38526170322c3edf71ba45ecb1ca95","analyzedAt":"2026-08-12T20:53:15.911Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}