147 lines
6 KiB
Python
147 lines
6 KiB
Python
"""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()})
|