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