{"record":{"id":"d3174d6cea9c6ae2","repo":"bytedance/deer-flow","slug":"your-email-domain-is-not-allowed-please-use-an-ap","errorCode":null,"errorMessage":"Your email domain is not allowed. Please use an approved email address.","messagePattern":"Your email domain is not allowed\\. Please use an approved email address\\.","errorType":"http","errorClass":"HTTPException","httpStatus":403,"severity":"error","filePath":"backend/app/gateway/auth/user_provisioning.py","lineNumber":62,"sourceCode":"    if provider_config.require_verified_email and not identity.email_verified:\n        raise HTTPException(\n            status_code=status.HTTP_403_FORBIDDEN,\n            detail=(\"Your email could not be verified by the identity provider. Please contact your administrator.\"),\n        )\n\n    if not identity.email:\n        raise HTTPException(\n            status_code=status.HTTP_403_FORBIDDEN,\n            detail=\"The identity provider did not provide an email address.\",\n        )\n\n    email = identity.email.lower()\n\n    # 3. Domain restriction\n    if provider_config.allowed_email_domains:\n        domain = email.rsplit(\"@\", 1)[-1]\n        if domain not in {d.lower().lstrip(\"@\") for d in provider_config.allowed_email_domains}:\n            raise HTTPException(\n                status_code=status.HTTP_403_FORBIDDEN,\n                detail=\"Your email domain is not allowed. Please use an approved email address.\",\n            )\n\n    # 4. Block if a local account already owns this email. We never auto-link an\n    # SSO identity onto a pre-existing local account, since that would let an SSO\n    # login take over a password account that happens to share the email.\n    local_user = await local_provider.get_user_by_email(email)\n\n    if local_user:\n        raise HTTPException(\n            status_code=status.HTTP_409_CONFLICT,\n            detail=(\"An account with this email already exists. Contact your administrator to link it to your SSO account.\"),\n        )\n\n    # 5. Auto-create\n    if not provider_config.auto_create_users:\n        raise HTTPException(","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/app/gateway/auth/user_provisioning.py#L44-L80","documentation":"HTTP 403 raised when provider_config.allowed_email_domains is a non-empty list and the lowercased domain of the identity's email is not in it. The allow-list comparison strips a leading '@' and lowercases entries, so 'Example.com' in config matches 'example.com' in the email.","triggerScenarios":"First SSO login (no existing OAuth link) with allowed_email_domains set (e.g. [company.com]) while the user logs in with a personal address (gmail.com). Reached after the verified-email and email-present checks pass.","commonSituations":"Restricting a tenant to corporate domains; users choosing the wrong IdP account ('sign in with Google' personal vs workspace); typos or a leading '@' that is actually handled, but entries with uppercase-only mismatch already normalized; mergers adding new domains.","solutions":["Log in with an email under an approved domain","Add the missing domain to allowed_email_domains for the provider in config.yaml and restart the Gateway","Remove allowed_email_domains (or set it empty) if the restriction is not intended"],"exampleFix":"# config.yaml (provider entry)\n# before\nallowed_email_domains: [company.com]\n# after\nallowed_email_domains: [company.com, company-acquired.com]","handlingStrategy":"validation","validationCode":"if provider_config.allowed_email_domains:\n    domain = identity.email.rsplit(\"@\", 1)[-1].lower()\n    allowed = {d.lower().lstrip(\"@\") for d in provider_config.allowed_email_domains}\n    if domain not in allowed:\n        return RedirectResponse(\"/login?error=domain_not_allowed\")","typeGuard":null,"tryCatchPattern":"try:\n    await provision_oauth_user(provider_id, identity, provider_config)\nexcept HTTPException as e:\n    if e.status_code == 403 and \"domain is not allowed\" in e.detail:\n        return redirect_to_login(\"domain_not_allowed\")\n    raise","preventionTips":["Keep allowed_email_domains lowercase and without '@' to match the normalization","Add every corporate domain (including acquisitions) before enabling SSO","Log the rejected domain (not PII-heavy) to spot config gaps quickly"],"tags":["auth","oidc","sso","http-403","allow-list","user-provisioning"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}