encripted in reset password

This commit is contained in:
Cayo Puigdefabregas 2024-01-04 12:43:24 +01:00
parent f62348dcdb
commit bd84dbc3bb
4 changed files with 77 additions and 24 deletions

View File

@ -17,7 +17,7 @@ Including another URLconf
from django.contrib.auth import views as auth_views from django.contrib.auth import views as auth_views
from django.views.generic import RedirectView from django.views.generic import RedirectView
from django.urls import path, reverse_lazy from django.urls import path, reverse_lazy
from .views import LoginView from .views import LoginView, PasswordResetConfirmView
from .admin import views as views_admin from .admin import views as views_admin
from .user import views as views_user from .user import views as views_user
@ -44,13 +44,16 @@ urlpatterns = [
), ),
name='password_reset_done' name='password_reset_done'
), ),
path('auth/reset/<uidb64>/<token>/', path('auth/reset/<uidb64>/<token>/', PasswordResetConfirmView.as_view(),
auth_views.PasswordResetConfirmView.as_view(
template_name='auth/password_reset_confirm.html',
success_url=reverse_lazy('idhub:password_reset_complete')
),
name='password_reset_confirm' name='password_reset_confirm'
), ),
# path('auth/reset/<uidb64>/<token>/',
# auth_views.PasswordResetConfirmView.as_view(
# template_name='auth/password_reset_confirm.html',
# success_url=reverse_lazy('idhub:password_reset_complete')
# ),
# name='password_reset_confirm'
# ),
path('auth/reset/done/', path('auth/reset/done/',
auth_views.PasswordResetCompleteView.as_view( auth_views.PasswordResetCompleteView.as_view(
template_name='auth/password_reset_complete.html' template_name='auth/password_reset_complete.html'

View File

@ -4,7 +4,6 @@ from django.utils.translation import gettext_lazy as _
from django.contrib.auth import views as auth_views from django.contrib.auth import views as auth_views
from django.contrib.auth import login as auth_login from django.contrib.auth import login as auth_login
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from nacl import secret
class LoginView(auth_views.LoginView): class LoginView(auth_views.LoginView):
@ -23,7 +22,7 @@ class LoginView(auth_views.LoginView):
user = form.get_user() user = form.get_user()
# Decrypt the user's sensitive data encryption key and store it in the session. # Decrypt the user's sensitive data encryption key and store it in the session.
password = form.cleaned_data.get("password") password = form.cleaned_data.get("password")
sensitive_data_encryption_key = user.decrypt_sensitive_data_encryption_key(password) sensitive_data_encryption_key = user.decrypt_sensitive_data(password)
key_dids = cache.get("KEY_DIDS", {}) key_dids = cache.get("KEY_DIDS", {})
if not user.is_anonymous and user.is_admin: if not user.is_anonymous and user.is_admin:
user_dashboard = reverse_lazy('idhub:user_dashboard') user_dashboard = reverse_lazy('idhub:user_dashboard')
@ -41,3 +40,14 @@ class LoginView(auth_views.LoginView):
return HttpResponseRedirect(self.extra_context['success_url']) return HttpResponseRedirect(self.extra_context['success_url'])
class PasswordResetConfirmView(auth_views.PasswordResetConfirmView):
template_name = 'auth/password_reset_confirm.html'
success_url = reverse_lazy('idhub:password_reset_complete')
def form_valid(self, form):
password = form.cleaned_data.get("password")
user = form.get_user()
user.set_encrypted_sensitive_data(password)
user.save()
return HttpResponseRedirect(self.success_url)

View File

@ -2,7 +2,7 @@ import re
from django import forms from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from idhub_auth.models import User from idhub_auth.models import User, gen_salt
class ProfileForm(forms.ModelForm): class ProfileForm(forms.ModelForm):
@ -31,4 +31,3 @@ class ProfileForm(forms.ModelForm):
return last_name return last_name

View File

@ -1,6 +1,9 @@
import nacl
import base64
from django.db import models from django.db import models
from django.core.cache import cache
from django.contrib.auth.models import BaseUserManager, AbstractBaseUser from django.contrib.auth.models import BaseUserManager, AbstractBaseUser
from nacl import secret, pwhash
class UserManager(BaseUserManager): class UserManager(BaseUserManager):
@ -44,10 +47,8 @@ class User(AbstractBaseUser):
is_admin = models.BooleanField(default=False) is_admin = models.BooleanField(default=False)
first_name = models.CharField(max_length=255, blank=True, null=True) first_name = models.CharField(max_length=255, blank=True, null=True)
last_name = models.CharField(max_length=255, blank=True, null=True) last_name = models.CharField(max_length=255, blank=True, null=True)
# TODO: Hay que generar una clave aleatoria para cada usuario cuando se le da de alta en el sistema. encrypted_sensitive_data = models.CharField(max_length=255)
encrypted_sensitive_data_encryption_key = models.BinaryField(max_length=255) salt = models.CharField(max_length=255)
# TODO: Hay que generar un salt aleatorio para cada usuario cuando se le da de alta en el sistema.
salt_of_sensitive_data_encryption_key = models.BinaryField(max_length=255)
objects = UserManager() objects = UserManager()
@ -92,14 +93,54 @@ class User(AbstractBaseUser):
return ", ".join(set(roles)) return ", ".join(set(roles))
def derive_key_from_password(self, password): def derive_key_from_password(self, password):
kdf = pwhash.argon2i.kdf # TODO: Move the KDF choice to SETTINGS.PY kdf = nacl.pwhash.argon2i.kdf
ops = pwhash.argon2i.OPSLIMIT_INTERACTIVE # TODO: Move the KDF choice to SETTINGS.PY ops = nacl.pwhash.argon2i.OPSLIMIT_INTERACTIVE
mem = pwhash.argon2i.MEMLIMIT_INTERACTIVE # TODO: Move the KDF choice to SETTINGS.PY mem = nacl.pwhash.argon2i.MEMLIMIT_INTERACTIVE
salt = self.salt_of_sensitive_data_encryption_key return kdf(
return kdf(secret.SecretBox.KEY_SIZE, password, salt, opslimit=ops, memlimit=mem) nacl.secret.SecretBox.KEY_SIZE,
password,
self.get_salt(),
opslimit=ops,
memlimit=mem
)
def decrypt_sensitive_data(self, password, data=None):
sb_key = self.derive_key_from_password(password.encode('utf-8'))
sb = nacl.secret.SecretBox(sb_key)
if not data:
data = self.get_encrypted_sensitive_data()
if not isinstance(data, bytes):
data = data.encode('utf-8')
return sb.decrypt(data).decode('utf-8')
def encrypt_sensitive_data(self, password, data):
sb_key = self.derive_key_from_password(password.encode('utf-8'))
sb = nacl.secret.SecretBox(sb_key)
if not isinstance(data, bytes):
data = data.encode('utf-8')
return sb.encrypt(data).decode('utf-8')
def get_salt(self):
return base64.b64decode(self.salt.encode('utf-8'))
def set_salt(self):
self.salt = base64.b64encode(nacl.utils.random(16)).decode('utf-8')
def get_encrypted_sensitive_data(self):
return base64.b64decode(self.encrypted_sensitive_data.encode('utf-8'))
def set_encrypted_sensitive_data(self, password):
key = base64.b64encode(nacl.utils.random(64))
key_dids = cache.get("KEY_DIDS", {})
if key_dids.get(user.id):
key = key_dids[user.id]
else:
self.set_salt()
key_crypted = self.encrypt_sensitive_data(password, key)
self.encrypted_sensitive_data = base64.b64encode(key_crypted).decode('utf-8')
def decrypt_sensitive_data_encryption_key(self, password):
sb_key = self.derive_key_from_password(password)
sb = secret.SecretBox(sb_key)
return sb.decrypt(self.encrypted_sensitive_data_encryption_key)