{"record":{"id":"81734a5c044baf7a","repo":"coleam00/Archon","slug":"email-is-required","errorCode":null,"errorMessage":"Email is required.","messagePattern":"Email is required\\.","errorType":"http","errorClass":"APIError","httpStatus":400,"severity":"error","filePath":"packages/server/src/auth/instance.ts","lineNumber":123,"sourceCode":"      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      },\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 */","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/server/src/auth/instance.ts#L105-L141","documentation":"Thrown from the signup createUser hook in buildAuth when the auth provider completes registration without an email address. The hook needs the email both to gate on the invite allowlist and to identify the user, so a missing email is treated as a BAD_REQUEST APIError (HTTP 400). This is a defensive check: email/password providers always supply an email, but OAuth or anonymous-style providers may create users with no email set.","triggerScenarios":"A signup flow (typically an OAuth/social login provider that doesn't return an email scope, or an anonymous signup path) invokes the createUser hook with a `user` object whose `email` field is null or undefined.","commonSituations":"Configuring a GitHub/Google OAuth provider without requesting the `email` scope; a user whose provider account has no verified primary email and no fallback; enabling an anonymous-credentials provider alongside the allowlist gate; calling the signup API directly with a payload lacking email.","solutions":["Configure the OAuth provider to request the `email`/`user:email` scope so the returned profile includes an email.","If using an anonymous or no-email auth path, disable it or switch to an email-bearing provider, since this instance requires emails.","Have the user set/verify a primary email on their provider account before signing in.","If client-side, ensure the signup request payload includes a valid `email` field."],"exampleFix":"// before: OAuth provider config without email scope\nscopes: [\"read:user\"]\n// after: request email access\nscopes: [\"read:user\", \"user:email\"]","handlingStrategy":"validation","validationCode":"function hasEmail(profile: { email?: string | null }): profile is { email: string } {\n  return typeof profile.email === 'string' && profile.email.includes('@');\n}\nif (!hasEmail(oauthProfile)) {\n  // prompt for email manually or block signup before calling the API\n}","typeGuard":"function isSignableUser(u: { email?: string | null }): u is { email: string } {\n  return typeof u.email === 'string' && u.email.length > 0;\n}","tryCatchPattern":"try {\n  await authClient.signUp.email({ email, password, name });\n} catch (e) {\n  if (e?.status === 400 && /email is required/i.test(e?.message ?? '')) {\n    promptUserForEmail();\n  } else throw e;\n}","preventionTips":["Always request the email scope when configuring OAuth providers.","Test each configured provider's returned profile for a non-empty email.","Disable anonymous/no-email credential providers on instances that gate by email."],"tags":["auth","signup","email","bad-request","oauth"],"backgroundTag":"missing-email-on-signup","analyzedSha":"0773b9745896ef0612e709c80845a0f7db315b19","analyzedAt":"2026-09-01T02:28:07.064Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}