"""Accounts-module: login/logout, wie-ben-ik, gebruikersbeheer en wachtwoordreset. De login-endpoints horen hier thuis (de module is in de praktijk altijd aan). """ import logging import secrets from django.contrib.auth import authenticate, get_user_model from django.contrib.auth import login as auth_login from django.contrib.auth import logout as auth_logout from django.contrib.auth.tokens import default_token_generator from django.core.mail import send_mail from django.db.models import Q from django.utils.encoding import force_bytes, force_str from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode from django.views.decorators.csrf import ensure_csrf_cookie from rest_framework import status, viewsets from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import AllowAny, IsAdminUser from rest_framework.response import Response from .serializers import GebruikerSerializer logger = logging.getLogger("roosterwijs.auth") User = get_user_model() def _user_payload(user): return { "authenticated": True, "username": user.get_username(), "is_staff": user.is_staff, } @api_view(["GET"]) @permission_classes([AllowAny]) @ensure_csrf_cookie def auth_me(request): """Wie ben ik? Zet tegelijk de CSRF-cookie voor de frontend.""" if request.user.is_authenticated: return Response(_user_payload(request.user)) return Response({"authenticated": False}) @api_view(["POST"]) @permission_classes([AllowAny]) def auth_login_view(request): """Inloggen met gebruikersnaam of e-mailadres + wachtwoord (sessie-cookie).""" naam = str(request.data.get("username", "")).strip() wachtwoord = str(request.data.get("password", "")) if not naam or not wachtwoord: return Response({"detail": "Vul gebruikersnaam en wachtwoord in."}, status=status.HTTP_400_BAD_REQUEST) # Sta ook inloggen met e-mailadres toe: zoek de bijbehorende gebruikersnaam op. gebruikersnaam = naam if "@" in naam: match = User.objects.filter(email__iexact=naam).first() if match: gebruikersnaam = match.get_username() user = authenticate(request, username=gebruikersnaam, password=wachtwoord) if user is None: logger.warning("Mislukte inlogpoging voor %r", naam) return Response({"detail": "Onjuiste gebruikersnaam/e-mail of wachtwoord."}, status=status.HTTP_401_UNAUTHORIZED) auth_login(request, user) logger.info("Gebruiker %r ingelogd", user.get_username()) return Response(_user_payload(user)) @api_view(["POST"]) def auth_logout_view(request): """Uitloggen: sessie beëindigen.""" logger.info("Gebruiker %r uitgelogd", request.user.get_username()) auth_logout(request) return Response({"authenticated": False}) @api_view(["POST"]) @permission_classes([AllowAny]) def wachtwoord_vergeten(request): """Stuur (indien het account bestaat) een resetlink per e-mail.""" ident = str(request.data.get("identifier", "")).strip() if ident: user = User.objects.filter(Q(username__iexact=ident) | Q(email__iexact=ident)).first() if user and user.email and user.is_active: uid = urlsafe_base64_encode(force_bytes(user.pk)) token = default_token_generator.make_token(user) from django.conf import settings basis = (getattr(settings, "FRONTEND_BASE_URL", "") or request.build_absolute_uri("/")).rstrip("/") link = f"{basis}/?reset=1&uid={uid}&token={token}" send_mail( "Wachtwoord opnieuw instellen — Roosterwijs", f"Hallo {user.get_username()},\n\n" f"Stel je wachtwoord opnieuw in via deze link:\n{link}\n\n" f"Heb je dit niet aangevraagd? Dan kun je deze e-mail negeren.", None, [user.email], fail_silently=True, ) # Altijd hetzelfde antwoord: lek niet of een account bestaat. return Response({"detail": "Als het account bestaat, is er een e-mail met instructies verstuurd."}) @api_view(["POST"]) @permission_classes([AllowAny]) def wachtwoord_reset(request): """Zet een nieuw wachtwoord op basis van een geldige resetlink (uid + token).""" uidb64 = request.data.get("uid") token = request.data.get("token") nieuw = str(request.data.get("wachtwoord", "")) if len(nieuw) < 8: return Response({"detail": "Het wachtwoord moet minstens 8 tekens zijn."}, status=400) user = None try: user = User.objects.get(pk=force_str(urlsafe_base64_decode(uidb64))) except Exception: user = None if not user or not default_token_generator.check_token(user, token): return Response({"detail": "Deze resetlink is ongeldig of verlopen."}, status=400) user.set_password(nieuw) user.save() logger.info("Wachtwoord gereset voor %r", user.get_username()) return Response({"detail": "Je wachtwoord is aangepast. Je kunt nu inloggen."}) class GebruikerViewSet(viewsets.ModelViewSet): """Gebruikersbeheer — alleen voor beheerders (is_staff).""" serializer_class = GebruikerSerializer permission_classes = [IsAdminUser] def get_queryset(self): return User.objects.select_related("gebruikersprofiel", "gebruikersprofiel__persoon").order_by("username") @api_view(["POST"]) @permission_classes([IsAdminUser]) def wachtwoord_zet(request, pk): """Beheerder zet/genereert een (tijdelijk) wachtwoord; geeft het terug.""" try: user = User.objects.get(pk=pk) except User.DoesNotExist: return Response({"detail": "Gebruiker niet gevonden."}, status=404) nieuw = str(request.data.get("wachtwoord", "")).strip() or secrets.token_urlsafe(9) if len(nieuw) < 8: return Response({"detail": "Het wachtwoord moet minstens 8 tekens zijn."}, status=400) user.set_password(nieuw) user.save() logger.info("Beheerder %r zette nieuw wachtwoord voor %r", request.user.get_username(), user.get_username()) return Response({"wachtwoord": nieuw, "gebruiker": user.get_username()})