mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-04-25 10:08:36 +02:00
Merge pull request #518 from eitchtee/dev
feat(auth): trust OIDC connections and automatically connect them with local accounts
This commit is contained in:
@@ -157,6 +157,13 @@ WYGIWYH supports login via OpenID Connect (OIDC) through `django-allauth`. This
|
|||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> Currently only OpenID Connect is supported as a provider, open an issue if you need something else.
|
> 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:
|
To configure OIDC, you need to set the following environment variables:
|
||||||
|
|
||||||
| Variable | Description |
|
| Variable | Description |
|
||||||
|
|||||||
@@ -376,8 +376,10 @@ ACCOUNT_EMAIL_VERIFICATION = "none"
|
|||||||
SOCIALACCOUNT_LOGIN_ON_GET = True
|
SOCIALACCOUNT_LOGIN_ON_GET = True
|
||||||
SOCIALACCOUNT_ONLY = True
|
SOCIALACCOUNT_ONLY = True
|
||||||
SOCIALACCOUNT_AUTO_SIGNUP = os.getenv("OIDC_ALLOW_SIGNUP", "true").lower() == "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"
|
ACCOUNT_ADAPTER = "allauth.account.adapter.DefaultAccountAdapter"
|
||||||
SOCIALACCOUNT_ADAPTER = "allauth.socialaccount.adapter.DefaultSocialAccountAdapter"
|
SOCIALACCOUNT_ADAPTER = "apps.users.adapters.AutoConnectSocialAccountAdapter"
|
||||||
|
|
||||||
# CRISPY FORMS
|
# CRISPY FORMS
|
||||||
CRISPY_ALLOWED_TEMPLATE_PACKS = [
|
CRISPY_ALLOWED_TEMPLATE_PACKS = [
|
||||||
|
|||||||
75
app/apps/users/adapters.py
Normal file
75
app/apps/users/adapters.py
Normal 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
|
||||||
Reference in New Issue
Block a user