se agrega endpoints para recuperar acceso

francisco/photos
Francisco Sandoval 2024-01-27 00:26:58 -03:00
parent 1a79bf8dd7
commit 36c46b93d6
9 changed files with 221 additions and 82 deletions

View File

@ -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
@ -17,11 +16,6 @@ class ApiMiddleware:
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:

View File

@ -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)

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div style="text-align: center;">
<p><strong>Estimado/a {{ nombre }}</strong></p>
<p>Para crear una nueva contraseña, es necesario que ingrese el siguiente código:
<strong style="font-size: 1.5rem;">{{ codigo }}</strong>
</p>
<p>Pinche el siguiente botón para crear una nueva contraseña.</p>
<p>
<a href="{{ vinculo }}" target="_blank" style="background: steelblue; color: #fff; padding: 1rem;">
CREAR NUEVA CONTRASEÑA
</a>
</p>
</div>
</body>
</html>

View File

@ -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'),

View File

@ -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' }
if not usuario:
return HttpResponse('Acceso no valido', status=400)
# 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 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)
@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

View File

@ -28,7 +28,7 @@ class ParaderoViewSet(viewsets.ModelViewSet):
@action(detail=False, methods=['get'], url_path='info-public/(?P<pk>\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

View File

@ -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,

View File

@ -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': [
@ -170,3 +176,10 @@ LOGGING = {
},
}
"""
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'

View File

@ -4,6 +4,7 @@ djangorestframework
django-cors-headers
django-filter
coreapi
python-dotenv
python-decouple
PyJWT
pymongo