parent
6b97eaf850
commit
57ea6e8e78
@ -0,0 +1,15 @@ |
||||
from django.contrib import admin |
||||
|
||||
from .models import Device, LoginLog |
||||
|
||||
class DeviceAdmin(admin.ModelAdmin): |
||||
list_display = ['user', 'device_model', 'last_login', 'id'] |
||||
readonly_fields = ('last_login',) |
||||
ordering = ['-last_login'] |
||||
|
||||
class LoginLogAdmin(admin.ModelAdmin): |
||||
list_display = ['user', 'device', 'date'] |
||||
ordering = ['-date'] |
||||
|
||||
admin.site.register(Device, DeviceAdmin) |
||||
admin.site.register(LoginLog, LoginLogAdmin) |
||||
@ -0,0 +1,6 @@ |
||||
from django.apps import AppConfig |
||||
|
||||
|
||||
class AuthenticationConfig(AppConfig): |
||||
default_auto_field = 'django.db.models.BigAutoField' |
||||
name = 'authentication' |
||||
@ -0,0 +1,36 @@ |
||||
# Generated by Django 5.1 on 2025-03-20 14:49 |
||||
|
||||
import django.db.models.deletion |
||||
import uuid |
||||
from django.conf import settings |
||||
from django.db import migrations, models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
initial = True |
||||
|
||||
dependencies = [ |
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.CreateModel( |
||||
name='Device', |
||||
fields=[ |
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), |
||||
('last_login', models.DateTimeField(auto_now=True)), |
||||
('model_name', models.CharField(blank=True, max_length=100, null=True)), |
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='devices', to=settings.AUTH_USER_MODEL)), |
||||
], |
||||
), |
||||
migrations.CreateModel( |
||||
name='LoginLog', |
||||
fields=[ |
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), |
||||
('date', models.DateTimeField(auto_now=True)), |
||||
('device', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='login_logs', to='authentication.device')), |
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='login_logs', to=settings.AUTH_USER_MODEL)), |
||||
], |
||||
), |
||||
] |
||||
@ -0,0 +1,18 @@ |
||||
# Generated by Django 5.1 on 2025-03-20 15:42 |
||||
|
||||
from django.db import migrations |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('authentication', '0001_initial'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.RenameField( |
||||
model_name='device', |
||||
old_name='model_name', |
||||
new_name='device_model', |
||||
), |
||||
] |
||||
@ -0,0 +1,2 @@ |
||||
from .device import Device |
||||
from .login_log import LoginLog |
||||
@ -0,0 +1,13 @@ |
||||
from django.db import models |
||||
import uuid |
||||
from django.conf import settings |
||||
from . import Device |
||||
|
||||
class LoginLog(models.Model): |
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True) |
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='login_logs') |
||||
device = models.ForeignKey(Device, on_delete=models.SET_NULL, related_name='login_logs', null=True) |
||||
date = models.DateTimeField(auto_now=True) |
||||
|
||||
def __str__(self): |
||||
return f"{self.id} > {self.user.username}" |
||||
@ -0,0 +1,29 @@ |
||||
from django.contrib.auth import password_validation |
||||
|
||||
from rest_framework import serializers |
||||
|
||||
class ChangePasswordSerializer(serializers.Serializer): |
||||
old_password = serializers.CharField(max_length=128, write_only=True, required=True) |
||||
new_password1 = serializers.CharField(max_length=128, write_only=True, required=True) |
||||
new_password2 = serializers.CharField(max_length=128, write_only=True, required=True) |
||||
|
||||
def validate_old_password(self, value): |
||||
user = self.context['request'].user |
||||
if not user.check_password(value): |
||||
raise serializers.ValidationError( |
||||
_('Your old password was entered incorrectly. Please enter it again.') |
||||
) |
||||
return value |
||||
|
||||
def validate(self, data): |
||||
if data['new_password1'] != data['new_password2']: |
||||
raise serializers.ValidationError({'new_password2': _("The two password fields didn't match.")}) |
||||
password_validation.validate_password(data['new_password1'], self.context['request'].user) |
||||
return data |
||||
|
||||
def save(self, **kwargs): |
||||
password = self.validated_data['new_password1'] |
||||
user = self.context['request'].user |
||||
user.set_password(password) |
||||
user.save() |
||||
return user |
||||
@ -0,0 +1,3 @@ |
||||
from django.test import TestCase |
||||
|
||||
# Create your tests here. |
||||
@ -0,0 +1,5 @@ |
||||
import re |
||||
|
||||
def is_valid_email(email): |
||||
email_regex = r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$' |
||||
return re.match(email_regex, email) is not None |
||||
@ -0,0 +1,109 @@ |
||||
from django.shortcuts import render |
||||
from django.views.decorators.csrf import csrf_exempt |
||||
from django.contrib.auth import authenticate |
||||
from django.utils.decorators import method_decorator |
||||
from django.core.exceptions import ObjectDoesNotExist |
||||
from django.conf import settings |
||||
|
||||
from rest_framework.views import APIView |
||||
from rest_framework.response import Response |
||||
from rest_framework.permissions import IsAuthenticated |
||||
from rest_framework.authtoken.models import Token |
||||
from rest_framework import status |
||||
from rest_framework.generics import UpdateAPIView |
||||
|
||||
from .utils import is_valid_email |
||||
from .models import Device, LoginLog |
||||
|
||||
from .serializers import ChangePasswordSerializer |
||||
|
||||
CustomUser=settings.AUTH_USER_MODEL |
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch') |
||||
class CustomAuthToken(APIView): |
||||
permission_classes = [] |
||||
|
||||
def post(self, request, *args, **kwargs): |
||||
username = request.data.get('username') |
||||
password = request.data.get('password') |
||||
device_id = request.data.get('device_id') |
||||
|
||||
user = authenticate(username=username, password=password) |
||||
|
||||
if user is None and is_valid_email(username) == True: |
||||
true_username = self.get_username_from_email(username) |
||||
user = authenticate(username=true_username, password=password) |
||||
|
||||
if user: |
||||
# user.device_id = device_id |
||||
# user.save() |
||||
# self.create_or_update_device(user, device_id) |
||||
|
||||
# token, created = Token.objects.get_or_create(user=user) |
||||
# return Response({'token': token.key}) |
||||
|
||||
if user.device_id is None or user.device_id == device_id or user.username == 'apple-test': |
||||
user.device_id = device_id |
||||
user.save() |
||||
|
||||
device_model = request.data.get('device_model') |
||||
|
||||
device = self.create_or_update_device(user, device_id, device_model) |
||||
self.create_login_log(user, device) |
||||
|
||||
token, created = Token.objects.get_or_create(user=user) |
||||
return Response({'token': token.key}) |
||||
else: |
||||
return Response({'error': 'Vous ne pouvez pour l\'instant vous connecter sur plusieurs appareils en même temps. Veuillez vous déconnecter du précédent appareil. Autrement, veuillez contacter le support.'}, status=status.HTTP_403_FORBIDDEN) |
||||
|
||||
else: |
||||
return Response({'error': 'L\'utilisateur et le mot de passe de correspondent pas'}, status=status.HTTP_401_UNAUTHORIZED) |
||||
|
||||
def create_or_update_device(self, user, device_id, device_model): |
||||
obj, created = Device.objects.update_or_create( |
||||
id=device_id, |
||||
device_model=device_model, |
||||
defaults={ |
||||
'user': user |
||||
} |
||||
) |
||||
return obj |
||||
|
||||
def create_login_log(self, user, device): |
||||
LoginLog.objects.create(user=user, device=device) |
||||
|
||||
def get_username_from_email(self, email): |
||||
try: |
||||
user = CustomUser.objects.get(email=email) |
||||
return user.username |
||||
except ObjectDoesNotExist: |
||||
return None |
||||
|
||||
class Logout(APIView): |
||||
permission_classes = (IsAuthenticated,) |
||||
|
||||
def post(self, request, *args, **kwargs): |
||||
# request.user.auth_token.delete() |
||||
|
||||
device_id = request.data.get('device_id') |
||||
if request.user.device_id == device_id: |
||||
request.user.device_id = None |
||||
request.user.save() |
||||
|
||||
Device.objects.filter(id=device_id).delete() |
||||
|
||||
return Response(status=status.HTTP_200_OK) |
||||
|
||||
class ChangePasswordView(UpdateAPIView): |
||||
serializer_class = ChangePasswordSerializer |
||||
|
||||
def update(self, request, *args, **kwargs): |
||||
serializer = self.get_serializer(data=request.data) |
||||
serializer.is_valid(raise_exception=True) |
||||
user = serializer.save() |
||||
# if using drf authtoken, create a new token |
||||
if hasattr(user, 'auth_token'): |
||||
user.auth_token.delete() |
||||
token, created = Token.objects.get_or_create(user=user) |
||||
# return new token |
||||
return Response({'token': token.key}, status=status.HTTP_200_OK) |
||||
@ -0,0 +1,16 @@ |
||||
# Generated by Django 5.1 on 2025-03-20 14:49 |
||||
|
||||
from django.db import migrations |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('sync', '0003_device'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.DeleteModel( |
||||
name='Device', |
||||
), |
||||
] |
||||
Loading…
Reference in new issue