Système d'authentification utilisateur : inscription sécurisée, gestion de session et interface principale

Page HTML avec validation client et gestion de fichier


<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Page d'inscription</title>
    {% load static %}
    <script src="{% static 'js/jquery.min.js' %}"></script>
    <link href="{% static 'bootstrap/css/bootstrap.min.css' %}" rel="stylesheet">
    <script src="{% static 'bootstrap/js/bootstrap.min.js' %}"></script>
    <script src="{% static 'layer/layer.js' %}"></script>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, sans-serif;
            margin: 0;
            padding: 0;
            background: url('https://pegasus.epweike.com/Public/uploads/introduce/55dc0c8c010df.jpg') no-repeat center center fixed;
            background-size: cover;
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
        }
        .card-container {
            background: rgba(255, 255, 255, 0.95);
            border-radius: 12px;
            box-shadow: 0 8px 30px rgba(0,0,0,0.15);
            padding: 25px;
            width: 100%;
            max-width: 650px;
        }
        .form-group {
            margin-bottom: 22px;
        }
        label {
            display: block;
            margin-bottom: 8px;
            font-weight: 600;
        }
        .form-control {
            width: 100%;
            padding: 12px;
            border: 1px solid #ddd;
            border-radius: 6px;
            transition: border-color 0.3s;
        }
        .form-control:focus {
            border-color: #007bff;
            box-shadow: 0 0 0 0.2rem rgba(0,123,255,0.25);
        }
        .btn-submit {
            background: #007bff;
            color: white;
            padding: 12px 24px;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            width: 100%;
            font-weight: 500;
            transition: background 0.3s;
        }
        .btn-submit:hover {
            background: #0056b3;
        }
    </style>
</head>
<body>

<div class="card-container">
    <h1 class="text-center">Créer un compte</h1>
    <div class="form-group">
        <label for="nom_utilisateur">Nom d'utilisateur</label>
        <input type="text" id="nom_utilisateur" class="form-control" data-error="Le nom est requis">
    </div>
    <div class="form-group">
        <label for="mot_de_passe">Mot de passe</label>
        <input type="password" id="mot_de_passe" class="form-control" data-error="Le mot de passe est requis">
    </div>
    <div class="form-group">
        <label for="confirmation_mot">Confirmer le mot de passe</label>
        <input type="password" id="confirmation_mot" class="form-control" data-error="La confirmation est requise">
    </div>
    <div class="form-group">
        <label for="email">Adresse e-mail</label>
        <input type="email" id="email" class="form-control" data-error="L'email est requis">
    </div>
    <div class="form-group">
        <label for="avatar">Télécharger l'avatar</label>
        <img src="/static/img/default.png" id="preview_avatar" width="120" alt="Aperçu avatar">
        <input type="file" id="avatar" style="display: none" accept="image/*">
    </div>
    <div class="form-group">
        <button class="btn-submit">Soumettre</button>
    </div>
</div>

<script>
    document.getElementById('avatar').addEventListener('change', function() {
        const reader = new FileReader();
        const file = this.files[0];
        reader.readAsDataURL(file);
        reader.onload = function() {
            document.getElementById('preview_avatar').src = reader.result;
        };
    });

    document.querySelector('.btn-submit').addEventListener('click', function() {
        const champs = ['nom_utilisateur', 'mot_de_passe', 'confirmation_mot', 'email'];
        for (const champ of champs) {
            if (!document.getElementById(champ).value) {
                layer.msg(document.getElementById(champ).dataset.error);
                return;
            }
        }

        if (document.getElementById('mot_de_passe').value !== document.getElementById('confirmation_mot').value) {
            layer.msg('Les mots de passe ne correspondent pas');
            return;
        }

        const formData = new FormData();
        formData.append('nom_utilisateur', document.getElementById('nom_utilisateur').value);
        formData.append('mot_de_passe', document.getElementById('mot_de_passe').value);
        formData.append('confirmation_mot', document.getElementById('confirmation_mot').value);
        formData.append('email', document.getElementById('email').value);
        formData.append('avatar', document.getElementById('avatar').files[0]);
        formData.append('csrfmiddlewaretoken', '{{ csrf_token }}');

        fetch('', {
            method: 'POST',
            body: formData,
            headers: {
                'X-CSRFToken': '{{ csrf_token }}'
            }
        })
        .then(response => response.json())
        .then(data => {
            if (data.code === 200) {
                layer.msg(data.message, {}, () => location.href = data.url);
            } else {
                layer.msg(data.message);
            }
        });
    });
</script>

</body>
</html>

Back end Django - Gestion de l'inscription

from django.http import JsonResponse
from django.shortcuts import render
from . import models
import hashlib
from django.conf import settings

def inscription(request):
    if request.method == 'POST':
        reponse = {'code': 200, 'message': 'Inscription réussie', 'donnees': []}
        
        nom = request.POST.get('nom_utilisateur')
        mot_passe = request.POST.get('mot_de_passe')
        confirmation = request.POST.get('confirmation_mot')
        email = request.POST.get('email')
        avatar = request.FILES.get('avatar')
        
        if not nom:
            reponse['code'] = 400
            reponse['message'] = 'Nom d\'utilisateur requis'
            return JsonResponse(reponse)
        if not mot_passe:
            reponse['code'] = 401
            reponse['message'] = 'Mot de passe requis'
            return JsonResponse(reponse)
        if not confirmation:
            reponse['code'] = 402
            reponse['message'] = 'Confirmation requise'
            return JsonResponse(reponse)
        if mot_passe != confirmation:
            reponse['code'] = 403
            reponse['message'] = 'Mots de passe non correspondants'
            return JsonResponse(reponse)
        if not email:
            reponse['code'] = 404
            reponse['message'] = 'Adresse email requise'
            return JsonResponse(reponse)
        
        mot_passe_hache = hasher_mot_de_passe(mot_passe)
        donnees = {
            'username': nom,
            'password': mot_passe_hache[:16],
            'email': email
        }
        if avatar:
            donnees['avatar'] = avatar
            
        models.Utilisateur.objects.create(**donnees)
        reponse['url'] = '/connexion/'
        return JsonResponse(reponse)
    
    return render(request, 'inscription.html')

Hashage de mot de passe avec salage

def hasher_mot_de_passe(password):
    m = hashlib.md5()
    mot_passe_salé = password + settings.SECRET_KEY
    m.update(mot_passe_salé.encode('utf-8'))
    return m.hexdigest()

Connexion utilisateur avec captcha


<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Connexion</title>
    {% load static %}
    <script src="{% static 'js/jquery.min.js' %}"></script>
    <link href="{% static 'bootstrap/css/bootstrap.min.css' %}" rel="stylesheet">
    <script src="{% static 'bootstrap/js/bootstrap.min.js' %}"></script>
    <script src="{% static 'layer/layer.js' %}"></script>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, sans-serif;
            margin: 0;
            padding: 0;
            background: url('https://pegasus.epweike.com/Public/uploads/introduce/55dc0c8c010df.jpg') no-repeat center center fixed;
            background-size: cover;
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
        }
        .card-container {
            background: rgba(255, 255, 255, 0.95);
            border-radius: 12px;
            box-shadow: 0 8px 30px rgba(0,0,0,0.15);
            padding: 25px;
            width: 100%;
            max-width: 650px;
        }
        .captcha-container {
            display: flex;
            gap: 15px;
            margin-bottom: 20px;
        }
        .captcha-image {
            width: 160px;
            height: 40px;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
    </style>
</head>
<body>

<div class="card-container">
    <h1 class="text-center">Connexion sécurisée</h1>
    <div class="form-group">
        <label for="nom_utilisateur">Nom d'utilisateur</label>
        <input type="text" id="nom_utilisateur" class="form-control" data-error="Nom requis">
    </div>
    <div class="form-group">
        <label for="mot_de_passe">Mot de passe</label>
        <input type="password" id="mot_de_passe" class="form-control" data-error="Mot de passe requis">
    </div>
    <div class="form-group captcha-container">
        <label for="captcha">Code de sécurité</label>
        <input type="text" id="captcha" class="form-control" data-error="Code requis" style="width: 60%">
        <img src="/generer-captcha/" class="captcha-image" id="captcha-image">
    </div>
    <div class="form-group">
        <button class="btn-submit">Se connecter</button>
    </div>
</div>

<script>
    document.querySelector('.btn-submit').addEventListener('click', function() {
        const champs = ['nom_utilisateur', 'mot_de_passe', 'captcha'];
        for (const champ of champs) {
            if (!document.getElementById(champ).value) {
                layer.msg(document.getElementById(champ).dataset.error);
                return;
            }
        }
        
        if (document.getElementById('captcha').value.length !== 5) {
            layer.msg('Code de sécurité invalide');
            return;
        }
        
        fetch('', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRFToken': '{{ csrf_token }}'
            },
            body: JSON.stringify({
                nom_utilisateur: document.getElementById('nom_utilisateur').value,
                mot_de_passe: document.getElementById('mot_de_passe').value,
                captcha: document.getElementById('captcha').value,
                csrfmiddlewaretoken: '{{ csrf_token }}'
            })
        })
        .then(response => response.json())
        .then(data => {
            if (data.code === 200) {
                layer.msg(data.message, {}, () => location.href = data.url);
            } else {
                layer.msg(data.message);
            }
        });
    });
</script>

</body>
</html>

Backend Django - Connexion avec vérification captcha

from django.http import JsonResponse
from django.shortcuts import render
from . import models
import hashlib
from .utils import generer_captcha

def connexion(request):
    if request.method == 'POST':
        reponse = {'code': 200, 'message': 'Connexion réussie, redirection dans 3s', 'donnees': []}
        nom = request.POST.get('nom_utilisateur')
        mot_passe = request.POST.get('mot_de_passe')
        code_saisi = request.POST.get('captcha')
        
        if not nom:
            reponse['code'] = 400
            reponse['message'] = 'Nom d\'utilisateur requis'
            return JsonResponse(reponse)
        if not mot_passe:
            reponse['code'] = 401
            reponse['message'] = 'Mot de passe requis'
            return JsonResponse(reponse)
        
        code_session = request.session.get('captcha_code', '').upper()
        if code_saisi.upper() != code_session:
            reponse['code'] = 402
            reponse['message'] = 'Code de sécurité incorrect'
            return JsonResponse(reponse)
        
        mot_passe_hache = hasher_mot_de_passe(mot_passe)
        utilisateur = models.Utilisateur.objects.filter(
            username=nom, 
            password=mot_passe_hache[:16]
        ).first()
        
        if not utilisateur:
            reponse['code'] = 403
            reponse['message'] = 'Identifiants invalides'
            return JsonResponse(reponse)
        
        request.session['utilisateur_id'] = utilisateur.pk
        request.session['nom_utilisateur'] = utilisateur.username
        reponse['url'] = '/accueil/'
        return JsonResponse(reponse)
    
    return render(request, 'connexion.html')

Génération de captcha

from PIL import Image, ImageDraw, ImageFont
from io import BytesIO
import random
from django.http import HttpResponse

def generer_couleur_aleatoire():
    return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))

def generer_captcha(request):
    largeur, hauteur = 160, 40
    image = Image.new('RGB', (largeur, hauteur), generer_couleur_aleatoire())
    dessin = ImageDraw.Draw(image)
    police = ImageFont.truetype('static/font/yun.ttf', 28)
    
    caracteres = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
    code = ''.join(random.choice(caracteres) for _ in range(5))
    request.session['captcha_code'] = code
    
    for i, char in enumerate(code):
        x = 10 + i * 25
        y = random.randint(5, 15)
        dessin.text((x, y), char, generer_couleur_aleatoire(), police)
    
    buffer = BytesIO()
    image.save(buffer, format='PNG')
    return HttpResponse(buffer.getvalue(), content_type='image/png')

Interface principale avec navigation et modal


<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Accueil</title>
    {% load static %}
    <script src="{% static 'js/jquery.min.js' %}"></script>
    <link href="{% static 'bootstrap/css/bootstrap.min.css' %}" rel="stylesheet">
    <script src="{% static 'bootstrap/js/bootstrap.min.js' %}"></script>
    <script src="{% static 'layer/layer.js' %}"></script>
</head>
<body>

<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
    <div class="container">
        <a class="navbar-brand" href="#">Réseau social professionnel</a>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarNav">
            <ul class="navbar-nav me-auto">
                <li class="nav-item"><a class="nav-link active" href="#">Articles</a></li>
                <li class="nav-item"><a class="nav-link" href="#">Catégories</a></li>
                <li class="nav-item dropdown">
                    <a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown">Découvrir</a>
                    <ul class="dropdown-menu">
                        <li><a class="dropdown-item" href="#">Communauté</a></li>
                        <li><a class="dropdown-item" href="#">Tendances</a></li>
                    </ul>
                </li>
            </ul>
            <form class="d-flex">
                <input class="form-control me-2" type="search" placeholder="Rechercher">
                <button class="btn btn-outline-light" type="submit">Rechercher</button>
            </form>
            <ul class="navbar-nav ms-auto">
                {% if request.session.nom_utilisateur %}
                    <li class="nav-item dropdown">
                        <a class="nav-link dropdown-toggle" href="#" id="userMenu" data-bs-toggle="dropdown">
                            {{ request.session.nom_utilisateur }}
                        </a>
                        <ul class="dropdown-menu dropdown-menu-end">
                            <li><a class="dropdown-item" href="#" data-bs-toggle="modal" data-bs-target="#modalChangerMotDePasse">Modifier mot de passe</a></li>
                            <li><a class="dropdown-item" href="/deconnexion/">Se déconnecter</a></li>
                        </ul>
                    </li>
                {% else %}
                    <li class="nav-item"><a class="nav-link" href="/connexion/">Connexion</a></li>
                    <li class="nav-item"><a class="nav-link" href="/inscription/">Inscription</a></li>
                {% endif %}
            </ul>
        </div>
    </div>
</nav>

<div class="modal fade" id="modalChangerMotDePasse" tabindex="-1">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title">Changer le mot de passe</h5>
                <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
            </div>
            <div class="modal-body">
                <div class="mb-3">
                    <label class="form-label">Nom d'utilisateur</label>
                    <input type="text" class="form-control" value="{{ request.session.nom_utilisateur }}" readonly>
                </div>
                <div class="mb-3">
                    <label class="form-label">Ancien mot de passe</label>
                    <input type="password" id="ancien_mot" class="form-control" data-error="Champ requis">
                </div>
                <div class="mb-3">
                    <label class="form-label">Nouveau mot de passe</label>
                    <input type="password" id="nouveau_mot" class="form-control" data-error="Champ requis">
                </div>
                <div class="mb-3">
                    <label class="form-label">Confirmer</label>
                    <input type="password" id="confirmation" class="form-control" data-error="Champ requis">
                </div>
                <button id="btnValiderMotDePasse" class="btn btn-primary">Valider</button>
            </div>
        </div>
    </div>
</div>

<script>
    document.getElementById('btnValiderMotDePasse').addEventListener('click', function() {
        const ancien = document.getElementById('ancien_mot').value;
        const nouveau = document.getElementById('nouveau_mot').value;
        const confirmation = document.getElementById('confirmation').value;
        
        const champs = ['ancien_mot', 'nouveau_mot', 'confirmation'];
        for (const champ of champs) {
            if (!document.getElementById(champ).value) {
                layer.msg(document.getElementById(champ).dataset.error);
                return;
            }
        }
        
        if (nouveau !== confirmation) {
            layer.msg('Les mots de passe ne correspondent pas');
            return;
        }
        
        fetch('/modifier-mot-de-passe/', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRFToken': '{{ csrf_token }}'
            },
            body: JSON.stringify({
                ancien_mot: ancien,
                nouveau_mot: nouveau,
                confirmation: confirmation,
                csrfmiddlewaretoken: '{{ csrf_token }}'
            })
        })
        .then(response => response.json())
        .then(data => {
            if (data.code === 200) {
                layer.msg(data.message, {}, () => location.reload());
            } else {
                layer.msg(data.message);
            }
        });
    });
</script>

</body>
</html>

Backend Django - Modification de mot de passe

from django.http import JsonResponse
from django.contrib.auth.hashers import make_password
from . import models

def modifier_mot_de_passe(request):
    if request.method == 'POST':
        reponse = {'code': 200, 'message': 'Mot de passe modifié avec succès', 'donnees': []}
        ancien = request.POST.get('ancien_mot')
        nouveau = request.POST.get('nouveau_mot')
        confirmation = request.POST.get('confirmation')
        
        if nouveau != confirmation:
            reponse['code'] = 400
            reponse['message'] = 'Les mots de passe ne correspondent pas'
            return JsonResponse(reponse)
        
        utilisateur = models.Utilisateur.objects.get(
            pk=request.session.get('utilisateur_id')
        )
        
        if not hasher_mot_de_passe(ancien) == utilisateur.password[:16]:
            reponse['code'] = 401
            reponse['message'] = 'Ancien mot de passe incorrect'
            return JsonResponse(reponse)
        
        utilisateur.password = hasher_mot_de_passe(nouveau)[:16]
        utilisateur.save()
        return JsonResponse(reponse)

Étiquettes: Django Pillow Bootstrap jQuery session-management

Publié le 27 septembre à 10h01