diff --git a/project/api/middlewares.py b/project/api/middlewares.py
index ca7f6d5..e3ad3a8 100755
--- a/project/api/middlewares.py
+++ b/project/api/middlewares.py
@@ -2,11 +2,10 @@ from django.urls import resolve
from django.http import HttpResponse
from .models import Usuario, Persona
from decouple import config
+from project.settings import SECRET_KEY
import jwt
import logging
-private_key = config('SECRET_JWT')
-
class ApiMiddleware:
def __init__(self, get_response):
self.get_response = get_response
@@ -16,11 +15,6 @@ class ApiMiddleware:
if request.path[0:5] != '/api/':
response = self.get_response(request)
return response
-
- # se omite esta regla en login
- if request.path == '/api/auth/' and request.method == 'POST':
- response = self.get_response(request)
- return response
match = resolve(request.path)
logging.error(match)
@@ -30,6 +24,22 @@ class ApiMiddleware:
response = self.get_response(request)
return response
+ if match.url_name == 'auth_login' and request.method == 'POST':
+ response = self.get_response(request)
+ return response
+
+ if match.url_name == 'auth_recuperar':
+ response = self.get_response(request)
+ return response
+
+ if match.url_name == 'auth_info':
+ response = self.get_response(request)
+ return response
+
+ if match.url_name == 'auth_contrasena':
+ response = self.get_response(request)
+ return response
+
# se omite esta regla al mostrar informacion publica de paradero
if match.url_name == 'paradero-info-public' and request.method == 'GET':
response = self.get_response(request)
@@ -42,7 +52,7 @@ class ApiMiddleware:
token = authorization[1]
try:
- decoded = jwt.decode(token, private_key, algorithms=["HS256"])
+ decoded = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
except jwt.ExpiredSignatureError:
return HttpResponse('token ya no es valido', status = 400)
except jwt.InvalidTokenError:
diff --git a/project/api/models.py b/project/api/models.py
index dcbbe10..f8debe0 100755
--- a/project/api/models.py
+++ b/project/api/models.py
@@ -369,24 +369,13 @@ class Usuario(models.Model):
vigente = models.BooleanField(blank=True, null=True)
superuser = models.BooleanField(blank=True, null=True)
id_rol = models.ForeignKey(Rol, models.DO_NOTHING, db_column='id_rol', blank=False, null=False)
+ clave = models.CharField(max_length=100, blank=True, null=True)
class Meta:
managed = False
db_table = 'usuario'
-class UsuarioClave(models.Model):
- login = models.OneToOneField(Usuario, models.DO_NOTHING, db_column='login', primary_key=True)
- clave = models.CharField(max_length=60, blank=True, null=True)
- clave_anterior = models.CharField(max_length=60, blank=True, null=True)
- fecha_modificacion = models.DateField(blank=True, null=True)
- codigo = models.DecimalField(max_digits=8, decimal_places=0, blank=True, null=True)
-
- class Meta:
- managed = False
- db_table = 'usuario_clave'
-
-
class Vehiculo(models.Model):
ppu = models.CharField(primary_key=True, max_length=10)
id_tipo_vehiculo = models.ForeignKey(TipoVehiculo, models.DO_NOTHING, db_column='id_tipo_vehiculo', blank=True, null=True)
diff --git a/project/api/templates/correo_recuperar.html b/project/api/templates/correo_recuperar.html
new file mode 100644
index 0000000..bb65dfc
--- /dev/null
+++ b/project/api/templates/correo_recuperar.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+ Document
+
+
+
+
Estimado/a {{ nombre }}
+
Para crear una nueva contraseña, es necesario que ingrese el siguiente código:
+ {{ codigo }}
+
+
+
Pinche el siguiente botón para crear una nueva contraseña.
+
+
+ CREAR NUEVA CONTRASEÑA
+
+
+
+
+
\ No newline at end of file
diff --git a/project/api/urls.py b/project/api/urls.py
index a4d0aa1..6df33dc 100755
--- a/project/api/urls.py
+++ b/project/api/urls.py
@@ -32,7 +32,10 @@ router.register('roles-lineas', rol_linea.RolLineaViewSet, basename='rol_linea')
urlpatterns = [
path('', include(router.urls)),
- path('auth/', auth.jwt_login, name='auth'),
+ path('auth/', auth.jwt_login, name='auth_login'),
+ path('auth/recuperar/', auth.recuperar, name='auth_recuperar'),
+ path('auth/info/', auth.info_token, name='auth_info'),
+ path('auth/nueva-contrasena/', auth.nueva_contrasena, name='auth_contrasena'),
path('mapas/paraderos/', mapa.paraderos, name='mapa-paraderos'),
path('mapas/rutas/', mapa.rutas, name='mapa-rutas'),
path('upload/zip/', upload.upload_zip, name='upload_zip'),
diff --git a/project/api/views/auth.py b/project/api/views/auth.py
index 3b48d95..89f93eb 100755
--- a/project/api/views/auth.py
+++ b/project/api/views/auth.py
@@ -1,18 +1,22 @@
from django.views.decorators.csrf import csrf_exempt
-from django.http import HttpResponse
-from django.http import JsonResponse
+from django.contrib.auth.hashers import check_password, make_password
+from django.http import HttpResponse, JsonResponse
from rest_framework.decorators import action, api_view, schema
+from project.settings import SECRET_KEY, EMAIL_HOST
-from .. import models, schemas
+from api import models, schemas
+from datetime import datetime, timedelta
from decouple import config
import json
import jwt
-from datetime import datetime, timedelta
import logging
+import random
-private_key = config('SECRET_JWT')
+from django.conf import settings
+from django.core.mail import EmailMultiAlternatives
+from django.template.loader import get_template
# Views jwt
@csrf_exempt
@@ -21,26 +25,27 @@ private_key = config('SECRET_JWT')
@schema(schemas.AuthSchema())
def jwt_login(request):
if request.method == 'POST':
- count = models.Usuario.objects.filter(vigente = True).count()
- logging.error(f'count usuario vigente = {count}')
-
- # validar username y password
+ # validar rut y password
input = json.loads(request.body)
- username = input['username']
- password = input['password']
+ rut = input['rut'].replace('.','').replace('-','')
+
+ if rut != '0':
+ dv = rut[-1].upper()
+ rut = rut[:-1]
+
usuario = None
- if count > 0:
- usuario = models.Usuario.objects.filter(login = username, vigente = True).values().first()
- elif username == '0' and password == '0':
+ if rut == '0' and password == '0':
usuario = { 'login': '0', 'clave': '0' }
+
+ # solo se permite usuario 0 si no existen usuarios vigentes
+ count = models.Usuario.objects.filter(vigente = True).count()
+ if count > 0:
+ return HttpResponse('Acceso no valido', status=400)
+ else:
+ usuario = models.Usuario.objects.filter(vigente=1, rut__rut=rut, rut__dv=dv).values().first()
- if not usuario:
- return HttpResponse('Acceso no valido', status=400)
-
- if username != '0':
- clave = models.UsuarioClave.objects.filter(login = username).first()
- if not clave or clave.clave != password:
+ if not check_password(input['password'], usuario['clave']):
return HttpResponse('Acceso no valido', status=400)
ahora = datetime.utcnow()
@@ -52,7 +57,132 @@ def jwt_login(request):
'exp': manana, # ahora + timedelta(minutes=60),
'login': usuario['login']
}
- token = jwt.encode(payload, private_key, algorithm="HS256")
+ token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
return JsonResponse({ 'token': token })
+
elif request.method == 'GET':
- return JsonResponse(request.jwt_info)
\ No newline at end of file
+ return JsonResponse(request.jwt_info)
+
+
+
+@csrf_exempt
+@action(detail=False, methods=['post'])
+@api_view(['POST'])
+def recuperar(request):
+ input = json.loads(request.body)
+ rut = input['rut'].replace('.','').replace('-','')
+
+ dv = rut[-1].upper()
+ rut = rut[:-1]
+
+ persona = models.Persona.objects.filter(rut=rut, dv=dv).first()
+ usuario = models.Usuario.objects.filter(rut=rut, vigente=True).first()
+
+ if usuario == None or persona == None:
+ return HttpResponse('Acceso no valido', status=400)
+
+ if persona.email != input['email'].lower():
+ return HttpResponse('Acceso no valido', status=400)
+
+ codigo_aleatorio = random.randint(100000, 999999)
+ ahora = datetime.utcnow()
+ expira = ahora + timedelta(minutes=5)
+ payload = {
+ 'iat': ahora,
+ 'exp': expira,
+ 'rut': f'{persona.rut}',
+ 'codigo': codigo_aleatorio
+ }
+ token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
+ vinculo = f"{http_referer(request)}/?s={token}#/new-password"
+
+ exito = enviar_correo(persona.email, 'Recuperar acceso', {
+ 'nombre': f'{persona.nombres} {persona.apellido_a} {persona.apellido_b}',
+ 'codigo': codigo_aleatorio,
+ 'vinculo': vinculo
+ })
+
+ return JsonResponse({ 'ok': exito, 'HTTP_REFERER': request.META['HTTP_REFERER'] })
+
+
+
+@csrf_exempt
+@action(detail=False, methods=['post'])
+@api_view(['POST'])
+def info_token(request):
+ input = json.loads(request.body)
+ token = input['token']
+ try:
+ decoded = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
+ persona = models.Persona.objects.filter(rut=decoded['rut']).first()
+
+ return JsonResponse({
+ 'nombres': persona.nombres,
+ 'apellido_a': persona.apellido_a,
+ 'apellido_b': persona.apellido_b
+ })
+ except jwt.ExpiredSignatureError:
+ return HttpResponse('token ya no es valido', status = 400)
+ except jwt.InvalidTokenError:
+ return HttpResponse('token es invalido', status = 400)
+
+
+
+@csrf_exempt
+@action(detail=False, methods=['post'])
+@api_view(['POST'])
+def nueva_contrasena(request):
+ input = json.loads(request.body)
+ token = input['token']
+ try:
+ decoded = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
+
+ if f"{decoded['codigo']}" != input['codigo']:
+ return HttpResponse('código es invalido', status = 400)
+
+ usuario = models.Usuario.objects.filter(rut = decoded['rut']).first()
+ if usuario == None:
+ return HttpResponse('Usuario no encontrado', status = 400)
+
+ usuario.clave = make_password(input['password'])
+ usuario.save()
+
+ return JsonResponse({ 'ok': True })
+
+ except jwt.ExpiredSignatureError:
+ return HttpResponse('token ya no es valido', status = 400)
+ except jwt.InvalidTokenError:
+ return HttpResponse('token es invalido', status = 400)
+ except Exception as e:
+ logging.error(e)
+ return HttpResponse('error al cambiar contraseña', status = 500)
+
+
+
+
+def enviar_correo(destinatario, asunto, contenido):
+ try:
+ template = get_template('correo_recuperar.html') # Ruta al template del correo
+ contenido_renderizado = template.render(contenido)
+
+ mensaje = EmailMultiAlternatives(asunto, '', settings.EMAIL_HOST_USER, [destinatario])
+ mensaje.attach_alternative(contenido_renderizado, 'text/html')
+ mensaje.send()
+ return True
+ except Exception as e:
+ print(f'EMAIL_HOST: {EMAIL_HOST}', flush=True)
+ print(f'ERROR: {e}', flush=True)
+ return False
+
+
+
+
+def http_referer(request):
+ if 'HTTP_REFERER' in request.META:
+ referer = request.META['HTTP_REFERER']
+ else:
+ protocol = request.scheme
+ host = request.META['HTTP_HOST']
+ port = request.META['SERVER_PORT']
+ referer = f'{protocol}://{host}'
+ return referer
\ No newline at end of file
diff --git a/project/api/views/paradero.py b/project/api/views/paradero.py
index bb16613..35e4f30 100755
--- a/project/api/views/paradero.py
+++ b/project/api/views/paradero.py
@@ -28,7 +28,7 @@ class ParaderoViewSet(viewsets.ModelViewSet):
@action(detail=False, methods=['get'], url_path='info-public/(?P\S+)')
def info_public(self, request, pk=None):
- if hasattr(request.META,'HTTP_REFERER'):
+ if 'HTTP_REFERER' in request.META:
referer = request.META['HTTP_REFERER']
else:
protocol = request.scheme
diff --git a/project/api/views/usuario.py b/project/api/views/usuario.py
index 62438d6..1cfedd7 100755
--- a/project/api/views/usuario.py
+++ b/project/api/views/usuario.py
@@ -1,6 +1,7 @@
from django.db import transaction
from django.http import HttpResponse, JsonResponse
+from django.contrib.auth.hashers import make_password
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework.decorators import action
@@ -60,24 +61,10 @@ class UsuarioViewSet(viewsets.ModelViewSet):
)
usuario.save()
- # logging.error(f'clave = {input["clave"]}')
if input['clave']:
logging.error('Modificar clave de usuario')
- clave = models.UsuarioClave.objects.filter(login = usuario.login).first()
- if clave:
- logging.error('Clave Usuario ya existe')
- clave.clave_anterior = clave.clave
- clave.clave = input['clave']
- clave.fecha_modificacion = datetime.datetime.now()
- clave.save()
- else:
- logging.error('Clave Usuario se creará')
- clave = models.UsuarioClave(
- login = usuario,
- clave = input['clave'],
- fecha_modificacion = datetime.datetime.now()
- )
- clave.save()
+ usuario.clave = make_password(input['clave'])
+ usuario.save()
return Response({
'rut': persona.rut,
@@ -101,13 +88,11 @@ class UsuarioViewSet(viewsets.ModelViewSet):
def update(self, request, *args, **kwargs):
input = json.loads(request.body)
- logging.error(input)
try:
pk = input['rut']
with transaction.atomic():
# validaciones se realiza a nivel del model
-
persona = models.Persona.objects.filter(rut = pk).first()
rol = models.Rol.objects.filter(id_rol = input.get('id_rol')).first()
@@ -119,23 +104,8 @@ class UsuarioViewSet(viewsets.ModelViewSet):
if 'clave' in input:
logging.error('Modificar clave de usuario')
- logging.error(f'clave = {input["clave"]}')
-
- clave = models.UsuarioClave.objects.filter(login = usuario.login).first()
- if clave:
- logging.error('Clave Usuario ya existe')
- clave.clave_anterior = clave.clave
- clave.clave = input['clave']
- clave.fecha_modificacion = datetime.datetime.now()
- clave.save()
- else:
- logging.error('Clave Usuario se creará')
- clave = models.UsuarioClave(
- login = usuario,
- clave = input['clave'],
- fecha_modificacion = datetime.datetime.now()
- )
- clave.save()
+ usuario.clave = make_password(input['clave'])
+ usuario.save()
return Response({
'rut': persona.rut,
diff --git a/project/project/settings.py b/project/project/settings.py
index 6ecb485..138674e 100644
--- a/project/project/settings.py
+++ b/project/project/settings.py
@@ -9,9 +9,12 @@ https://docs.djangoproject.com/en/4.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.2/ref/settings/
"""
-
+from dotenv import load_dotenv
from pathlib import Path
from decouple import config
+import os
+
+load_dotenv()
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
@@ -62,7 +65,10 @@ ROOT_URLCONF = 'project.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [ BASE_DIR / 'dist' ],
+ 'DIRS': [
+ os.path.join(BASE_DIR, 'dist'),
+ os.path.join(BASE_DIR, 'api', 'templates')
+ ],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
@@ -169,4 +175,11 @@ LOGGING = {
},
},
}
-"""
\ No newline at end of file
+"""
+
+
+EMAIL_HOST = config('SMTP_HOST')
+EMAIL_PORT = config('SMTP_PORT', 587)
+EMAIL_HOST_USER = config('SMTP_USER', 'tu_correo@gmail.com') # Tu dirección de correo
+EMAIL_HOST_PASSWORD = config('SMTP_PASS', 'tu_contraseña') # Tu contraseña de correo
+EMAIL_USE_TLS = config('SMTP_PROTOCOL') == 'tls'
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 5511fdb..b268bca 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,6 +4,7 @@ djangorestframework
django-cors-headers
django-filter
coreapi
+python-dotenv
python-decouple
PyJWT
pymongo