Merge pull request #518 from eitchtee/dev

feat(auth): trust OIDC connections and automatically connect them with local accounts
This commit is contained in:
Herculino Trotta
2026-02-15 14:43:33 -03:00
committed by GitHub
3 changed files with 85 additions and 1 deletions

View File

@@ -157,6 +157,13 @@ WYGIWYH supports login via OpenID Connect (OIDC) through `django-allauth`. This
> [!NOTE]
> Currently only OpenID Connect is supported as a provider, open an issue if you need something else.
> [!Caution]
> WYGIWYH automatically connects OIDC accounts to existing local accounts with matching email addresses.
> This means if a user already exists with email `user@example.com` and someone logs in via OIDC with the same email, the OIDC account will be automatically linked to the existing account without requiring user confirmation.
> This is only recommended for trusted OIDC providers that verify email addresses and where you control who can create accounts.
### Configuration
To configure OIDC, you need to set the following environment variables:
| Variable | Description |

View File

@@ -376,8 +376,10 @@ ACCOUNT_EMAIL_VERIFICATION = "none"
SOCIALACCOUNT_LOGIN_ON_GET = True
SOCIALACCOUNT_ONLY = True
SOCIALACCOUNT_AUTO_SIGNUP = os.getenv("OIDC_ALLOW_SIGNUP", "true").lower() == "true"
SOCIALACCOUNT_EMAIL_AUTHENTICATION = True
SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT = True
ACCOUNT_ADAPTER = "allauth.account.adapter.DefaultAccountAdapter"
SOCIALACCOUNT_ADAPTER = "allauth.socialaccount.adapter.DefaultSocialAccountAdapter"
SOCIALACCOUNT_ADAPTER = "apps.users.adapters.AutoConnectSocialAccountAdapter"
# CRISPY FORMS
CRISPY_ALLOWED_TEMPLATE_PACKS = [

View File

@@ -0,0 +1,75 @@
import logging
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from django.contrib.auth import get_user_model
User = get_user_model()
logger = logging.getLogger(__name__)
class AutoConnectSocialAccountAdapter(DefaultSocialAccountAdapter):
"""
Custom adapter to automatically connect social accounts to existing users
with the same email address.
SECURITY WARNING:
This adapter automatically connects OIDC accounts to existing local accounts
based on email matching.
If your OIDC provider allows unverified emails, this could lead to
ACCOUNT TAKEOVER attacks where an attacker creates an OIDC account
with someone else's email and gains access to their account.
"""
def pre_social_login(self, request, sociallogin):
"""
Invoked just after a user successfully authenticates via a
social provider, but before the login is actually processed.
If a user with the same email already exists, connect the social
account to that existing user instead of creating a new account.
"""
# If the social account is already connected to a user, do nothing
if sociallogin.is_existing:
return
# Check if we have an email from the social provider
if not sociallogin.email_addresses:
logger.warning(
"OIDC login attempted without email address. "
f"Provider: {sociallogin.account.provider}"
)
return
# Get the email from the social login
email = sociallogin.email_addresses[0].email.lower()
# Try to find an existing user with this email
try:
user = User.objects.get(email__iexact=email)
# Log this connection for security audit trail
logger.info(
f"Auto-connecting OIDC account to existing user. "
f"Email: {email}, Provider: {sociallogin.account.provider}, "
f"User ID: {user.id}"
)
# Connect the social account to the existing user
sociallogin.connect(request, user)
except User.DoesNotExist:
# No user with this email exists, proceed with normal signup flow
logger.debug(
f"No existing user found for email {email}. "
"Proceeding with new account creation."
)
pass
except User.MultipleObjectsReturned:
# Multiple users with the same email (shouldn't happen with unique constraint)
logger.error(
f"Multiple users found with email {email}. "
"This should not happen with unique constraint. "
"Blocking auto-connect."
)
# Let the default behavior handle this
pass