diff --git a/web/core/auth_oidc.py b/web/core/auth_oidc.py index 326ce70..ff2e2fc 100644 --- a/web/core/auth_oidc.py +++ b/web/core/auth_oidc.py @@ -1,43 +1,58 @@ from mozilla_django_oidc.auth import OIDCAuthenticationBackend + class AuthentikOIDCBackend(OIDCAuthenticationBackend): """ - Minimal, safe backend: - - preserves existing local auth (ModelBackend stays enabled) - - creates/updates Django users from Authentik claims + Custom backend to integrate Authentik OIDC with existing Django users + without breaking local authentication. """ def filter_users_by_claims(self, claims): - # Prefer stable identifiers. Authentik often provides email + preferred_username. email = (claims.get("email") or "").strip().lower() - username = (claims.get("preferred_username") or claims.get("username") or "").strip() + username = ( + claims.get("preferred_username") + or claims.get("username") + or "" + ).strip() - # If you want "email is identity", use email matching first: + # 1) Prefer matching by email if email: - return self.UserModel.objects.filter(email__iexact=email) + qs = self.UserModel.objects.filter(email__iexact=email) + if qs.exists(): + return qs - # Fallback to username matching if no email + # 2) Fallback to username match (critical for existing local users) if username: - return self.UserModel.objects.filter(username__iexact=username) + qs = self.UserModel.objects.filter(username__iexact=username) + if qs.exists(): + return qs + # 3) Otherwise, no match -> create user return self.UserModel.objects.none() def create_user(self, claims): user = super().create_user(claims) email = (claims.get("email") or "").strip().lower() - username = (claims.get("preferred_username") or claims.get("username") or email or "").strip() + username = ( + claims.get("preferred_username") + or claims.get("username") + or email + or "" + ).strip() - if username: - user.username = username if email: user.email = email - # Optional nice-to-have mappings + # Only set username if Django hasn't already set one + if username and not user.username: + user.username = username + user.first_name = claims.get("given_name", "") or user.first_name user.last_name = claims.get("family_name", "") or user.last_name - user.set_unusable_password() # SSO-only unless you explicitly set local passwords + # SSO-only account unless you explicitly add a password later + user.set_unusable_password() user.save() return user @@ -46,7 +61,6 @@ class AuthentikOIDCBackend(OIDCAuthenticationBackend): if email and user.email.lower() != email: user.email = email - # keep username stable once set, unless you *want* it to track upstream changes user.first_name = claims.get("given_name", "") or user.first_name user.last_name = claims.get("family_name", "") or user.last_name