diff --git a/web/core/auth_oidc.py b/web/core/auth_oidc.py new file mode 100644 index 0000000..326ce70 --- /dev/null +++ b/web/core/auth_oidc.py @@ -0,0 +1,54 @@ +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 + """ + + 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() + + # If you want "email is identity", use email matching first: + if email: + return self.UserModel.objects.filter(email__iexact=email) + + # Fallback to username matching if no email + if username: + return self.UserModel.objects.filter(username__iexact=username) + + 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() + + if username: + user.username = username + if email: + user.email = email + + # Optional nice-to-have mappings + 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 + user.save() + return user + + def update_user(self, user, claims): + email = (claims.get("email") or "").strip().lower() + 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 + + user.save() + return user