{"record":{"id":"afc178fa545ff1de","repo":"coleam00/Archon","slug":"this-email-is-not-on-the-invite-allowlist","errorCode":null,"errorMessage":"This email is not on the invite allowlist.","messagePattern":"This email is not on the invite allowlist\\.","errorType":"http","errorClass":"APIError","httpStatus":403,"severity":"warning","filePath":"packages/server/src/auth/instance.ts","lineNumber":126,"sourceCode":"            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      },\n    },\n  });\n}\n\n/**\n * Release the Better Auth pg.Pool on graceful shutdown. No-op when web auth is\n * disabled (no pool was ever created).\n */\nexport async function closeAuth(): Promise<void> {\n  if (authPool) {\n    await authPool.end();","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/server/src/auth/instance.ts#L108-L144","documentation":"Thrown from the signup createUser hook when the instance is in 'allowlist' (invite-only) signup mode and the registering user's email is not in the configured allowed-emails list. `isEmailAllowed()` returns true for an empty allowlist, so this only fires when an allowlist is actually configured. The APIError('FORBIDDEN') produces a clean 403 for the client instead of a generic 500.","triggerScenarios":"Signup mode is 'allowlist', ALLOWED_EMAILS (or equivalent) is non-empty, and a user attempts registration with an email that does not match any allowlist entry (exact or domain match per isEmailAllowed).","commonSituations":"A user with the wrong personal email tries to join a company instance; an admin typo'd the address in the allowlist; the allowlist uses full addresses while the user signs up with an alias; emails differ in case or plus-addressing and the matcher doesn't normalize them.","solutions":["Ask the operator to add the user's email (or their email domain) to the allowlist configuration and restart/reload.","Verify the allowlist value matches exactly what the identity provider returns (case, plus-tags, alias vs primary address).","If the instance should be open to everyone, switch signup mode from 'allowlist' to 'open'.","Check server logs to confirm which email was evaluated against the allowlist to spot mismatches."],"exampleFix":"// before\nALLOWED_EMAILS=alice@example.com\n// after: include the user or their domain\nALLOWED_EMAILS=alice@example.com,bob@example.com,@example.com","handlingStrategy":"validation","validationCode":"// client-side pre-check when the instance exposes its mode\nconst { mode, allowedEmails } = await (await fetch('/api/get-signup-mode')).json();\nif (mode === 'allowlist' && allowedEmails?.length && !allowedEmails.includes(email)) {\n  showNotice('This email is not on the invite list.');\n  return;\n}","typeGuard":null,"tryCatchPattern":"try {\n  await authClient.signUp.email({ email, password, name });\n} catch (e) {\n  if (e?.status === 403 && /invite allowlist/i.test(e?.message ?? '')) {\n    showNotice('Your email is not invited. Ask an admin to add it.');\n  } else throw e;\n}","preventionTips":["Keep the allowlist in one reviewed config source and validate entries with tests.","Normalize case when adding entries if the matcher is case-sensitive.","Prefer domain entries (@company.com) over long individual lists.","Tell invited users exactly which address was allowlisted."],"tags":["auth","signup","allowlist","invite-only","forbidden"],"backgroundTag":"email-not-on-allowlist","analyzedSha":"0773b9745896ef0612e709c80845a0f7db315b19","analyzedAt":"2026-09-01T02:28:07.064Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}