{"record":{"id":"d67179dc858f1353","repo":"coleam00/Archon","slug":"signup-is-disabled","errorCode":null,"errorMessage":"Signup is disabled.","messagePattern":"Signup is disabled\\.","errorType":"http","errorClass":"APIError","httpStatus":403,"severity":"warning","filePath":"packages/server/src/auth/instance.ts","lineNumber":115,"sourceCode":"    // `disabled` (no allowlist + no ARCHON_AUTH_OPEN_SIGNUP=true). The allowlist\n    // hook below is the belt-and-suspenders for `allowlist` mode.\n    emailAndPassword: { enabled: true, disableSignUp: signupDisabled },\n    user: { modelName: 'remote_agent_auth_user' },\n    session: { modelName: 'remote_agent_auth_session' },\n    account: { modelName: 'remote_agent_auth_account' },\n    verification: { modelName: 'remote_agent_auth_verification' },\n    databaseHooks: {\n      user: {\n        create: {\n          before: async (\n            user: User & Record<string, unknown>\n          ): Promise<{ data: User & Record<string, unknown> }> => {\n            // Defense in depth: `disableSignUp` (set above from getSignupMode)\n            // already blocks registration in `disabled` mode before this hook\n            // runs — re-check here so the hook stays correct on its own if that\n            // upstream enforcement ever changes.\n            if (signupDisabled) {\n              throw new APIError('FORBIDDEN', { message: 'Signup is disabled.' });\n            }\n            // Invite gate (`allowlist` mode): reject signups whose email is not on\n            // the allowlist. Throwing APIError surfaces a clean 403 instead of a\n            // generic 500. An empty allowlist makes isEmailAllowed() return true,\n            // so this hook is a no-op in `open` mode — `disableSignUp` and the\n            // posture above are what actually govern whether signup is permitted.\n            if (!user.email) {\n              throw new APIError('BAD_REQUEST', { message: 'Email is required.' });\n            }\n            if (!isEmailAllowed(user.email, allowedEmails)) {\n              throw new APIError('FORBIDDEN', {\n                message: 'This email is not on the invite allowlist.',\n              });\n            }\n            return { data: user };\n          },\n        },\n      },","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/server/src/auth/instance.ts#L97-L133","documentation":"Archon's auth instance (better-auth style) throws this 403 APIError from the signup hook when new-user registration is disabled. The server supports signup modes (open / allowlist / disabled); in 'disabled' mode the primary gate is the `disableSignUp` flag passed to the auth library, and this hook re-checks `signupDisabled` as defense in depth so the hook remains correct even if upstream enforcement changes. It fires after the auth library has already accepted a registration attempt but before the user record is returned.","triggerScenarios":"A client calls the signup/registration endpoint (e.g. better-auth signUp.email) while the instance's signup mode is 'disabled' (SIGNUP_MODE=disabled or equivalent), so `signupDisabled` is true inside buildAuth's createUser hook.","commonSituations":"Self-hosted operators running a private instance where signups are closed; a user trying to register with 'Sign up with email' on an invite-only deployment; misconfigured SIGNUP_MODE left at 'disabled' from initial setup; the upstream `disableSignUp` flag failing to block the request, leaving this hook as the last line of defense.","solutions":["If registration should be allowed, change the signup mode to 'open' (or 'allowlist') in the server config / SIGNUP_MODE env var and restart.","If the instance is intentionally closed, direct the user to be invited or to use an existing account instead of signing up.","Check for a version mismatch where the auth library's `disableSignUp` pre-gate is no longer blocking registration, making users hit this defensive hook; update or re-verify the getSignupMode wiring.","If the user is already supposed to exist, have an admin create the user manually rather than registering."],"exampleFix":"// before: server config\nSIGNUP_MODE=disabled\n// after: allow registration\nSIGNUP_MODE=open","handlingStrategy":"validation","validationCode":"const res = await fetch('/api/get-signup-mode');\nconst { mode } = await res.json();\nif (mode === 'disabled') {\n  // hide the signup UI / skip the signUp call entirely\n  return;\n}\nawait authClient.signUp.email({ email, password, name });","typeGuard":null,"tryCatchPattern":"try {\n  await authClient.signUp.email({ email, password, name });\n} catch (e) {\n  if (e?.status === 403 && /signup is disabled/i.test(e?.message ?? '')) {\n    showNotice('Registration is closed on this instance.');\n  } else throw e;\n}","preventionTips":["Query the signup-mode endpoint before rendering any signup form.","For self-hosted instances, document SIGNUP_MODE in the deployment env file.","Treat 403-with-'Signup is disabled.' as expected UX, not a bug."],"tags":["auth","signup-disabled","forbidden","configuration"],"backgroundTag":"signup-disabled","analyzedSha":"0773b9745896ef0612e709c80845a0f7db315b19","analyzedAt":"2026-09-01T02:28:07.064Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}