feat(API): change to jwt authentication.

This commit is contained in:
2026-02-14 15:12:32 -05:00
parent 4812160ea2
commit 7a9034943a
18 changed files with 130 additions and 82 deletions

View File

@@ -1,4 +1,5 @@
Django==5.0.6 Django==5.0.6
djangorestframework djangorestframework
django-cors-headers django-cors-headers
djangorestframework-simplejwt
sabatron-tryton-rpc-client==7.4.0 sabatron-tryton-rpc-client==7.4.0

View File

@@ -1,10 +1,19 @@
from django.contrib.auth.models import User from django.contrib.auth.models import User
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework.test import APIClient
class LoginMixin: class LoginMixin:
def login(self): def login(self):
username = 'nombre_usuario' self.user = User.objects.create_superuser(
password = 'contraseña' username='admin',
email = 'correo@example.com' email='admin@example.com',
self.user = User.objects.create_user(username, email, password) password='adminpass'
self.client.login(username=username, password=password) )
refresh = RefreshToken.for_user(self.user)
self.access_token = str(refresh.access_token)
self.client = APIClient()
self.client.credentials(
HTTP_AUTHORIZATION=f'Bearer {self.access_token}')

View File

@@ -0,0 +1,5 @@
Django==5.0.6
djangorestframework
django-cors-headers
djangorestframework-simplejwt
sabatron-tryton-rpc-client==7.4.0

View File

@@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/5.0/ref/settings/
""" """
import os import os
from datetime import timedelta
from pathlib import Path from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
@@ -44,7 +45,9 @@ INSTALLED_APPS = [
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'rest_framework', 'rest_framework',
'rest_framework.authtoken',
'corsheaders', 'corsheaders',
'users',
# 'don_confiao' # 'don_confiao'
] ]
@@ -57,7 +60,6 @@ MIDDLEWARE = [
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'corsheaders.middleware.CorsMiddleware', 'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
] ]
ROOT_URLCONF = 'tienda_ilusion.urls' ROOT_URLCONF = 'tienda_ilusion.urls'
@@ -137,11 +139,19 @@ FIXTURE_DIRS = ['don_confiao/tests/Fixtures']
REST_FRAMEWORK = { REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [ 'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication', "rest_framework_simplejwt.authentication.JWTAuthentication",
], ],
'DEFAULT_PERMISSION_CLASSES': [ 'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated', 'rest_framework.permissions.IsAuthenticated',
], ],
} }
LOGOUT_REDIRECT_URL = '/start/' SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=30),
"REFRESH_TOKEN_LIFETIME": timedelta(days=1),
"AUTH_HEADER_TYPES": ("Bearer",),
}
# CORS_ALLOWED_ORIGINS = [
# "http://localhost:5173",
# ]

View File

@@ -1,5 +0,0 @@
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Iniciar sesión</button>
</form>

View File

@@ -1,27 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Perfil de usuario</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
<style>
body {
font-family: Arial, sans-serif;
}
</style>
</head>
<body>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<h1>Perfil de usuario</h1>
<p>Nombre de usuario: {{ user.username }}</p>
<p>Email: {{ user.email }}</p>
<form action="{% url 'logout' %}" method="post" style="display: inline-block;">
{% csrf_token %}
<button type="submit" class="btn btn-danger">Cerrar sesión</button>
</form>
</div>
</div>
</div>
</body>
</html>

View File

@@ -1,22 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Bienvenido a la tienda la ilusión</title>
<style>
body {
font-family: Arial, sans-serif;
}
</style>
</head>
<body>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<h1>Bienvenido a la tienda la ilusion</h1>
<a href="{% url 'login' %}">Login</a>
</div>
</div>
</div>
</body>
</html>

View File

@@ -16,15 +16,19 @@ Including another URLconf
""" """
from django.contrib import admin from django.contrib import admin
from django.urls import include, path from django.urls import include, path
from . import views from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)
app_name = "don_confiao" app_name = "don_confiao"
urlpatterns = [ urlpatterns = [
path("don_confiao/", include("don_confiao.urls")), path("don_confiao/", include("don_confiao.urls")),
path("accounts/", include("django.contrib.auth.urls")),
path('accounts/profile/', views.ProfileView.as_view(), name='profile'),
path('start/', views.StartView.as_view(), name='start'),
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('api/token/', TokenObtainPairView.as_view(),
name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(),
name='token_refresh'),
path('api/users/', include('users.urls')),
] ]

View File

@@ -1,15 +0,0 @@
from django.views.generic import TemplateView
from django.contrib.auth.mixins import LoginRequiredMixin
class ProfileView(LoginRequiredMixin, TemplateView):
template_name = 'registration/profile.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['user'] = self.request.user
return context
class StartView(TemplateView):
template_name = 'start.html'

View File

View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class UsersConfig(AppConfig):
name = 'users'

View File

@@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

View File

@@ -0,0 +1,8 @@
from django.contrib.auth.models import User
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'email', 'first_name', 'last_name')

View File

@@ -0,0 +1,51 @@
from django.test import TestCase
from django.urls import reverse
from django.contrib.auth.models import User
from rest_framework.test import APIClient
from rest_framework_simplejwt.tokens import RefreshToken
class MeEndpointTests(TestCase):
def setUp(self):
self.user = User.objects.create_superuser(
username='admin',
email='admin@example.com',
password='adminpass'
)
refresh = RefreshToken.for_user(self.user)
self.access_token = str(refresh.access_token)
self.client = APIClient()
self.client.credentials(
HTTP_AUTHORIZATION=f'Bearer {self.access_token}')
def test_me_endpoint_returns_correct_user_data(self):
"""
Verifica que GET /api/users/me/ devuelve los datos del usuario
autenticado.
"""
url = reverse('current-user')
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
expected_fields = {'id', 'username', 'email',
'first_name', 'last_name'}
self.assertTrue(expected_fields.issubset(response.json().keys()))
data = response.json()
self.assertEqual(data['username'], self.user.username)
self.assertEqual(data['email'], self.user.email)
self.assertEqual(data['first_name'], self.user.first_name)
self.assertEqual(data['last_name'], self.user.last_name)
def test_me_endpoint_requires_authentication(self):
"""
Sin token el endpoint debe devolver 401 Unauthorized.
"""
client_no_auth = APIClient()
url = reverse('current-user')
response = client_no_auth.get(url)
self.assertEqual(response.status_code, 401)

View File

@@ -0,0 +1,6 @@
from django.urls import path
from .views import CurrentUserView
urlpatterns = [
path('me/', CurrentUserView.as_view(), name='current-user'),
]

View File

@@ -0,0 +1,12 @@
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from .serializers import UserSerializer
class CurrentUserView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
serializer = UserSerializer(request.user)
return Response(serializer.data)