added endpoint to account app; added "email_confirmed" field to User model; refactored auth app;
This commit is contained in:
parent
3c00dd3d1d
commit
047803bf43
60
apps/account/forms.py
Normal file
60
apps/account/forms.py
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
from django import forms
|
||||||
|
from django.contrib.auth import (
|
||||||
|
password_validation,
|
||||||
|
)
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from utils import exceptions
|
||||||
|
|
||||||
|
|
||||||
|
class SetPasswordForm(forms.Form):
|
||||||
|
"""
|
||||||
|
A form that lets a user change set their password without entering the old
|
||||||
|
password
|
||||||
|
"""
|
||||||
|
error_messages = {
|
||||||
|
'password_mismatch': _("The two password fields didn't match."),
|
||||||
|
'password_equal': _("Password already in use."),
|
||||||
|
}
|
||||||
|
new_password1 = forms.CharField(
|
||||||
|
label=_("New password"),
|
||||||
|
widget=forms.PasswordInput,
|
||||||
|
strip=False,
|
||||||
|
help_text=password_validation.password_validators_help_text_html(),
|
||||||
|
)
|
||||||
|
new_password2 = forms.CharField(
|
||||||
|
label=_("New password confirmation"),
|
||||||
|
strip=False,
|
||||||
|
widget=forms.PasswordInput,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, user, *args, **kwargs):
|
||||||
|
self.user = user
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def clean_new_password1(self):
|
||||||
|
password1 = self.cleaned_data.get('new_password1')
|
||||||
|
if self.user.check_password(password1):
|
||||||
|
raise forms.ValidationError(
|
||||||
|
self.error_messages['password_equal'],
|
||||||
|
code='password_equal',
|
||||||
|
)
|
||||||
|
return password1
|
||||||
|
|
||||||
|
def clean_new_password2(self):
|
||||||
|
password1 = self.cleaned_data.get('new_password1')
|
||||||
|
password2 = self.cleaned_data.get('new_password2')
|
||||||
|
if password1 and password2:
|
||||||
|
if password1 != password2:
|
||||||
|
raise forms.ValidationError(
|
||||||
|
self.error_messages['password_mismatch'],
|
||||||
|
code='password_mismatch',
|
||||||
|
)
|
||||||
|
password_validation.validate_password(password2, self.user)
|
||||||
|
return password2
|
||||||
|
|
||||||
|
def save(self, commit=True):
|
||||||
|
password = self.cleaned_data["new_password1"]
|
||||||
|
self.user.set_password(password)
|
||||||
|
if commit:
|
||||||
|
self.user.save()
|
||||||
|
return self.user
|
||||||
18
apps/account/migrations/0004_user_email_confirmed.py
Normal file
18
apps/account/migrations/0004_user_email_confirmed.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-08-20 12:14
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('account', '0003_resetpasswordtoken'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='email_confirmed',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='email status'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -3,18 +3,19 @@ from typing import Union
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import AbstractUser, UserManager as BaseUserManager
|
from django.contrib.auth.models import AbstractUser, UserManager as BaseUserManager
|
||||||
|
from django.contrib.auth.tokens import default_token_generator as password_token_generator
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.utils.encoding import force_bytes
|
||||||
|
from django.utils.http import urlsafe_base64_encode
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework.authtoken.models import Token
|
from rest_framework.authtoken.models import Token
|
||||||
from django.utils.encoding import force_bytes, force_text
|
|
||||||
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
|
|
||||||
from utils.models import gm_token_generator
|
|
||||||
|
|
||||||
from authorization.models import Application
|
from authorization.models import Application
|
||||||
from utils.models import ImageMixin, ProjectBaseMixin, PlatformMixin
|
from utils.models import ImageMixin, ProjectBaseMixin, PlatformMixin
|
||||||
|
from utils.models import gm_token_generator
|
||||||
|
|
||||||
|
|
||||||
class UserManager(BaseUserManager):
|
class UserManager(BaseUserManager):
|
||||||
|
|
@ -57,6 +58,7 @@ class User(ImageMixin, AbstractUser):
|
||||||
"""Base user model."""
|
"""Base user model."""
|
||||||
email = models.EmailField(_('email address'), blank=True,
|
email = models.EmailField(_('email address'), blank=True,
|
||||||
null=True, default=None)
|
null=True, default=None)
|
||||||
|
email_confirmed = models.BooleanField(_('email status'), default=False)
|
||||||
newsletter = models.NullBooleanField(default=True)
|
newsletter = models.NullBooleanField(default=True)
|
||||||
|
|
||||||
EMAIL_FIELD = 'email'
|
EMAIL_FIELD = 'email'
|
||||||
|
|
@ -93,6 +95,11 @@ class User(ImageMixin, AbstractUser):
|
||||||
def remove_token(self):
|
def remove_token(self):
|
||||||
Token.objects.filter(user=self).delete()
|
Token.objects.filter(user=self).delete()
|
||||||
|
|
||||||
|
def confirm_email(self):
|
||||||
|
"""Method to confirm user email address"""
|
||||||
|
self.email_confirmed = True
|
||||||
|
self.save()
|
||||||
|
|
||||||
def remove_access_tokens(self, source: Union[int, Union[tuple, list]]):
|
def remove_access_tokens(self, source: Union[int, Union[tuple, list]]):
|
||||||
"""Method to remove user access tokens"""
|
"""Method to remove user access tokens"""
|
||||||
source = source if isinstance(source, list) else [source, ]
|
source = source if isinstance(source, list) else [source, ]
|
||||||
|
|
@ -111,21 +118,26 @@ class User(ImageMixin, AbstractUser):
|
||||||
token.revoke()
|
token.revoke()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def get_signup_finish_token(self):
|
def get_confirm_email_token(self):
|
||||||
"""Make a token for finish signup."""
|
"""Make a token for finish signup."""
|
||||||
return gm_token_generator.make_token(self)
|
return gm_token_generator.make_token(self)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def get_user_uid(self):
|
def get_reset_password_token(self):
|
||||||
|
"""Make a token for finish signup."""
|
||||||
|
return password_token_generator.make_token(self)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def get_user_uidb64(self):
|
||||||
"""Get base64 value for user by primary key identifier"""
|
"""Get base64 value for user by primary key identifier"""
|
||||||
return urlsafe_base64_encode(force_bytes(self.pk))
|
return urlsafe_base64_encode(force_bytes(self.pk))
|
||||||
|
|
||||||
def get_confirm_signup_template(self):
|
def get_confirm_email_template(self):
|
||||||
"""Get confirm signup email template"""
|
"""Get confirm email template"""
|
||||||
return render_to_string(
|
return render_to_string(
|
||||||
template_name=settings.CONFIRM_SIGNUP_TEMPLATE,
|
template_name=settings.CONFIRM_EMAIL_TEMPLATE,
|
||||||
context={'token': self.get_signup_finish_token,
|
context={'token': self.get_confirm_email_token,
|
||||||
'uid': self.get_user_uid,
|
'uid': self.get_user_uidb64,
|
||||||
'domain_uri': settings.DOMAIN_URI})
|
'domain_uri': settings.DOMAIN_URI})
|
||||||
|
|
||||||
def get_body_email_message(self, subject: str, message: str):
|
def get_body_email_message(self, subject: str, message: str):
|
||||||
|
|
@ -200,7 +212,7 @@ class ResetPasswordToken(PlatformMixin, ProjectBaseMixin):
|
||||||
@property
|
@property
|
||||||
def generate_token(self):
|
def generate_token(self):
|
||||||
"""Generates a pseudo random code"""
|
"""Generates a pseudo random code"""
|
||||||
return gm_token_generator.make_token(self.user)
|
return password_token_generator.make_token(self.user)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def token_is_valid(user, token):
|
def token_is_valid(user, token):
|
||||||
|
|
@ -226,8 +238,8 @@ class ResetPasswordToken(PlatformMixin, ProjectBaseMixin):
|
||||||
def get_reset_password_template(self):
|
def get_reset_password_template(self):
|
||||||
"""Get reset password template"""
|
"""Get reset password template"""
|
||||||
return render_to_string(
|
return render_to_string(
|
||||||
template_name=settings.RESETTING_TOKEN_TEMPLATE_NAME,
|
template_name=settings.RESETTING_TOKEN_TEMPLATE,
|
||||||
context={'token': self.key,
|
context={'token': self.key,
|
||||||
'uid': self.user.get_user_uid,
|
'uidb64': self.user.get_user_uidb64,
|
||||||
'domain_uri': settings.DOMAIN_URI})
|
'domain_uri': settings.DOMAIN_URI})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import logging
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from . import models as account_models
|
from . import models
|
||||||
|
|
||||||
logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.INFO)
|
logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.INFO)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
@ -14,7 +14,7 @@ logger = logging.getLogger(__name__)
|
||||||
def send_reset_password_email(request_id):
|
def send_reset_password_email(request_id):
|
||||||
"""Send email to user for reset password."""
|
"""Send email to user for reset password."""
|
||||||
try:
|
try:
|
||||||
obj = account_models.ResetPasswordToken.objects.get(id=request_id)
|
obj = models.ResetPasswordToken.objects.get(id=request_id)
|
||||||
user = obj.user
|
user = obj.user
|
||||||
user.send_email(subject=_('Password resetting'),
|
user.send_email(subject=_('Password resetting'),
|
||||||
message=obj.get_reset_password_template())
|
message=obj.get_reset_password_template())
|
||||||
|
|
@ -22,3 +22,15 @@ def send_reset_password_email(request_id):
|
||||||
logger.error(f'METHOD_NAME: {send_reset_password_email.__name__}\n'
|
logger.error(f'METHOD_NAME: {send_reset_password_email.__name__}\n'
|
||||||
f'DETAIL: Exception occurred for ResetPasswordToken instance: '
|
f'DETAIL: Exception occurred for ResetPasswordToken instance: '
|
||||||
f'{request_id}')
|
f'{request_id}')
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def send_confirm_email(user_id):
|
||||||
|
"""Send verification email to user."""
|
||||||
|
try:
|
||||||
|
obj = models.User.objects.get(id=user_id)
|
||||||
|
obj.send_email(subject=_('Email confirmation'),
|
||||||
|
message=obj.get_confirm_email_template())
|
||||||
|
except:
|
||||||
|
logger.error(f'METHOD_NAME: {send_confirm_email.__name__}\n'
|
||||||
|
f'DETAIL: Exception occurred for user: {user_id}')
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,17 @@ from account.views import web as views
|
||||||
app_name = 'account'
|
app_name = 'account'
|
||||||
|
|
||||||
urlpatterns_api = [
|
urlpatterns_api = [
|
||||||
|
path('verify/email/', views.VerifyEmailView.as_view(),
|
||||||
|
name='verify-email'),
|
||||||
|
path('verify/email/confirm/<uidb64>/<token>/', views.VerifyEmailConfirmView.as_view(),
|
||||||
|
name='verify-email-confirm'),
|
||||||
path('reset-password/', views.PasswordResetView.as_view(),
|
path('reset-password/', views.PasswordResetView.as_view(),
|
||||||
name='password-reset'),
|
name='password-reset'),
|
||||||
path('reset-password/confirm/<str:uid>/<str:token>/',
|
path('form/reset-password/<uidb64>/<token>/', views.FormPasswordResetConfirmView.as_view(),
|
||||||
views.PasswordResetConfirmView.as_view(),
|
name='form-password-reset-confirm'),
|
||||||
name='password-reset-confirm'),
|
# Redirect endpoint after success
|
||||||
|
path('form/reset-password/success/', views.FormPasswordResetSuccessView.as_view(),
|
||||||
|
name='form-password-reset-success'),
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns = urlpatterns_api + \
|
urlpatterns = urlpatterns_api + \
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,70 @@
|
||||||
"""Web account views"""
|
"""Web account views"""
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.tokens import default_token_generator
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.http import HttpResponseRedirect
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
from django.utils.http import urlsafe_base64_decode
|
from django.utils.http import urlsafe_base64_decode
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.views.decorators.cache import never_cache
|
||||||
|
from django.views.decorators.debug import sensitive_post_parameters
|
||||||
|
from django.views.generic.edit import FormView
|
||||||
from rest_framework import permissions
|
from rest_framework import permissions
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from django.shortcuts import get_object_or_404
|
from rest_framework import views
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
from account import tasks
|
||||||
|
|
||||||
from account import models
|
from account import models
|
||||||
|
from account.forms import SetPasswordForm
|
||||||
from account.serializers import web as serializers
|
from account.serializers import web as serializers
|
||||||
from utils import exceptions as utils_exceptions
|
from utils import exceptions as utils_exceptions
|
||||||
from utils.models import gm_token_generator
|
from utils.models import gm_token_generator
|
||||||
from utils.views import (JWTCreateAPIView,
|
from utils.views import (JWTCreateAPIView,
|
||||||
JWTGenericViewMixin)
|
JWTGenericViewMixin,
|
||||||
|
JWTUpdateAPIView)
|
||||||
|
|
||||||
|
|
||||||
|
# Email confirmation
|
||||||
|
class VerifyEmailView(JWTGenericViewMixin):
|
||||||
|
"""View for confirmation email"""
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
"""Implement POST method"""
|
||||||
|
user = request.user
|
||||||
|
if user.email_confirmed:
|
||||||
|
raise utils_exceptions.EmailConfirmedError()
|
||||||
|
# Send verification link on user email
|
||||||
|
if settings.USE_CELERY:
|
||||||
|
tasks.send_confirm_email.delay(user.id)
|
||||||
|
else:
|
||||||
|
tasks.send_confirm_email(user.id)
|
||||||
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
class VerifyEmailConfirmView(JWTGenericViewMixin):
|
||||||
|
"""View for confirmation email"""
|
||||||
|
|
||||||
|
permission_classes = (permissions.AllowAny, )
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
"""Implement GET-method"""
|
||||||
|
uidb64 = kwargs.get('uidb64')
|
||||||
|
token = kwargs.get('token')
|
||||||
|
uid = force_text(urlsafe_base64_decode(uidb64))
|
||||||
|
user_qs = models.User.objects.filter(pk=uid)
|
||||||
|
if user_qs.exists():
|
||||||
|
user = user_qs.first()
|
||||||
|
if not gm_token_generator.check_token(user, token):
|
||||||
|
raise utils_exceptions.NotValidTokenError()
|
||||||
|
# Change email status
|
||||||
|
user.confirm_email()
|
||||||
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
else:
|
||||||
|
raise utils_exceptions.UserNotFoundError()
|
||||||
|
|
||||||
|
|
||||||
# Password reset
|
# Password reset
|
||||||
|
|
@ -34,12 +87,12 @@ class PasswordResetConfirmView(JWTGenericViewMixin):
|
||||||
"""Override get_object method
|
"""Override get_object method
|
||||||
"""
|
"""
|
||||||
queryset = self.filter_queryset(self.get_queryset())
|
queryset = self.filter_queryset(self.get_queryset())
|
||||||
uidb64 = self.kwargs.get('uid')
|
uidb64 = self.kwargs.get('uidb64')
|
||||||
|
|
||||||
uid = force_text(urlsafe_base64_decode(uidb64))
|
user_id = force_text(urlsafe_base64_decode(uidb64))
|
||||||
token = self.kwargs.get('token')
|
token = self.kwargs.get('token')
|
||||||
|
|
||||||
filter_kwargs = {'key': token, 'user_id': uid}
|
filter_kwargs = {'key': token, 'user_id': user_id}
|
||||||
obj = get_object_or_404(queryset, **filter_kwargs)
|
obj = get_object_or_404(queryset, **filter_kwargs)
|
||||||
|
|
||||||
if not gm_token_generator.check_token(user=obj.user, token=token):
|
if not gm_token_generator.check_token(user=obj.user, token=token):
|
||||||
|
|
@ -58,3 +111,100 @@ class PasswordResetConfirmView(JWTGenericViewMixin):
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
serializer.save()
|
serializer.save()
|
||||||
return Response(status=status.HTTP_200_OK)
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
# Form view
|
||||||
|
class PasswordContextMixin:
|
||||||
|
extra_context = None
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context.update({
|
||||||
|
'title': self.title,
|
||||||
|
**(self.extra_context or {})
|
||||||
|
})
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class FormPasswordResetSuccessView(views.APIView):
|
||||||
|
"""View for successful reset password"""
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
"""Implement GET-method"""
|
||||||
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
class FormPasswordResetConfirmView(PasswordContextMixin, FormView):
|
||||||
|
|
||||||
|
INTERNAL_RESET_URL_TOKEN = 'set-password'
|
||||||
|
INTERNAL_RESET_SESSION_TOKEN = '_password_reset_token'
|
||||||
|
|
||||||
|
form_class = SetPasswordForm
|
||||||
|
post_reset_login = False
|
||||||
|
post_reset_login_backend = None
|
||||||
|
success_url = reverse_lazy('web:account:form-password-reset-success')
|
||||||
|
template_name = settings.CONFIRMATION_PASSWORD_RESET_TEMPLATE
|
||||||
|
title = _('Enter new password')
|
||||||
|
token_generator = default_token_generator
|
||||||
|
|
||||||
|
@method_decorator(sensitive_post_parameters())
|
||||||
|
@method_decorator(never_cache)
|
||||||
|
def dispatch(self, *args, **kwargs):
|
||||||
|
assert 'uidb64' in kwargs and 'token' in kwargs
|
||||||
|
|
||||||
|
self.validlink = False
|
||||||
|
self.user = self.get_user(kwargs['uidb64'])
|
||||||
|
|
||||||
|
if self.user is not None:
|
||||||
|
token = kwargs['token']
|
||||||
|
if token == self.INTERNAL_RESET_URL_TOKEN:
|
||||||
|
session_token = self.request.session.get(self.INTERNAL_RESET_SESSION_TOKEN)
|
||||||
|
if self.token_generator.check_token(self.user, session_token):
|
||||||
|
# If the token is valid, display the password reset form.
|
||||||
|
self.validlink = True
|
||||||
|
return super().dispatch(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
if self.token_generator.check_token(self.user, token):
|
||||||
|
# Store the token in the session and redirect to the
|
||||||
|
# password reset form at a URL without the token. That
|
||||||
|
# avoids the possibility of leaking the token in the
|
||||||
|
# HTTP Referer header.
|
||||||
|
self.request.session[self.INTERNAL_RESET_SESSION_TOKEN] = token
|
||||||
|
redirect_url = self.request.path.replace(token, self.INTERNAL_RESET_URL_TOKEN)
|
||||||
|
return HttpResponseRedirect(redirect_url)
|
||||||
|
|
||||||
|
# Display the "Password reset unsuccessful" page.
|
||||||
|
return self.render_to_response(self.get_context_data())
|
||||||
|
|
||||||
|
def get_user(self, uidb64):
|
||||||
|
try:
|
||||||
|
# urlsafe_base64_decode() decodes to bytestring
|
||||||
|
uid = urlsafe_base64_decode(uidb64).decode()
|
||||||
|
user = models.User.objects.get(pk=uid)
|
||||||
|
except (TypeError, ValueError, OverflowError, models.User.DoesNotExist, ValidationError):
|
||||||
|
user = None
|
||||||
|
return user
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
kwargs = super().get_form_kwargs()
|
||||||
|
kwargs['user'] = self.user
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
# Saving form
|
||||||
|
form.save()
|
||||||
|
# Pop token
|
||||||
|
del self.request.session[self.INTERNAL_RESET_SESSION_TOKEN]
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
if self.validlink:
|
||||||
|
context['validlink'] = True
|
||||||
|
else:
|
||||||
|
context.update({
|
||||||
|
'form': None,
|
||||||
|
'title': _('Password reset unsuccessful'),
|
||||||
|
'validlink': False,
|
||||||
|
})
|
||||||
|
return context
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ class LoginByUsernameOrEmailSerializer(JWTBaseSerializerMixin, serializers.Model
|
||||||
authentication = authenticate(username=user.get_username(),
|
authentication = authenticate(username=user.get_username(),
|
||||||
password=password)
|
password=password)
|
||||||
if not authentication:
|
if not authentication:
|
||||||
raise utils_exceptions.UserNotFoundError()
|
raise utils_exceptions.WrongAuthCredentials()
|
||||||
self.instance = user
|
self.instance = user
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,3 @@ from account import models as account_models
|
||||||
|
|
||||||
logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.INFO)
|
logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.INFO)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
|
||||||
def send_confirm_signup_email(user_id):
|
|
||||||
"""Send verification email to user."""
|
|
||||||
try:
|
|
||||||
obj = account_models.User.objects.get(id=user_id)
|
|
||||||
obj.send_email(subject=_('Confirm signup'),
|
|
||||||
message=obj.get_confirm_signup_template())
|
|
||||||
except:
|
|
||||||
logger.error(f'METHOD_NAME: {send_confirm_signup_email.__name__}\n'
|
|
||||||
f'DETAIL: Exception occurred for user: {user_id}')
|
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,6 @@ urlpatterns_oauth2 = [
|
||||||
urlpatterns_jwt = [
|
urlpatterns_jwt = [
|
||||||
path('signup/', views.SignUpView.as_view(),
|
path('signup/', views.SignUpView.as_view(),
|
||||||
name='signup'),
|
name='signup'),
|
||||||
path('signup/finish/<str:uid>/<str:token>/', views.SignupFinishView.as_view(),
|
|
||||||
name='signup-finish'),
|
|
||||||
# sign in
|
# sign in
|
||||||
path('login/', views.LoginByUsernameOrEmailView.as_view(),
|
path('login/', views.LoginByUsernameOrEmailView.as_view(),
|
||||||
name='login'),
|
name='login'),
|
||||||
|
|
|
||||||
|
|
@ -195,11 +195,7 @@ class SignUpView(JWTCreateAPIView):
|
||||||
serializer = self.get_serializer(data=request.data)
|
serializer = self.get_serializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
serializer.save()
|
serializer.save()
|
||||||
response = Response(serializer.data, status=status.HTTP_201_CREATED)
|
response = Response(status=status.HTTP_201_CREATED)
|
||||||
if settings.USE_CELERY:
|
|
||||||
tasks.send_confirm_signup_email.delay(serializer.instance.id)
|
|
||||||
else:
|
|
||||||
tasks.send_confirm_signup_email(serializer.instance.id)
|
|
||||||
except utils_exceptions.LocaleNotExisted:
|
except utils_exceptions.LocaleNotExisted:
|
||||||
raise utils_exceptions.LocaleNotExisted(locale=_locale)
|
raise utils_exceptions.LocaleNotExisted(locale=_locale)
|
||||||
else:
|
else:
|
||||||
|
|
@ -208,43 +204,6 @@ class SignUpView(JWTCreateAPIView):
|
||||||
response=response)
|
response=response)
|
||||||
|
|
||||||
|
|
||||||
class SignupFinishView(JWTGenericViewMixin):
|
|
||||||
"""View for confirmation signup"""
|
|
||||||
|
|
||||||
permission_classes = (permissions.AllowAny, )
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
"""Implement GET-method"""
|
|
||||||
_locale = self._get_locale(request)
|
|
||||||
try:
|
|
||||||
locale = self._check_locale(locale=_locale)
|
|
||||||
uidb64 = kwargs.get('uid')
|
|
||||||
token = kwargs.get('token')
|
|
||||||
|
|
||||||
uid = force_text(urlsafe_base64_decode(uidb64))
|
|
||||||
user = User.objects.filter(pk=uid)
|
|
||||||
if user.exists():
|
|
||||||
if not gm_token_generator.check_token(user.first(), token):
|
|
||||||
raise utils_exceptions.NotValidTokenError()
|
|
||||||
response = Response(status=status.HTTP_200_OK)
|
|
||||||
else:
|
|
||||||
raise utils_exceptions.UserNotFoundError()
|
|
||||||
except utils_exceptions.LocaleNotExisted:
|
|
||||||
raise utils_exceptions.LocaleNotExisted(locale=_locale)
|
|
||||||
else:
|
|
||||||
return self._put_cookies_in_response(
|
|
||||||
cookies=self._put_data_in_cookies(locale=locale),
|
|
||||||
response=response)
|
|
||||||
|
|
||||||
def get_success_url(self):
|
|
||||||
"""Return url to success page considering Mobile component."""
|
|
||||||
return reverse('mobile:transaction-mobile:success')
|
|
||||||
|
|
||||||
def get_fail_url(self, **kwargs):
|
|
||||||
"""Return url to fail page considering Mobile component."""
|
|
||||||
return reverse('mobile:transaction-mobile:fail')
|
|
||||||
|
|
||||||
|
|
||||||
# Login by username|email + password
|
# Login by username|email + password
|
||||||
class LoginByUsernameOrEmailView(JWTAuthViewMixin):
|
class LoginByUsernameOrEmailView(JWTAuthViewMixin):
|
||||||
"""Login by email and password"""
|
"""Login by email and password"""
|
||||||
|
|
|
||||||
|
|
@ -82,3 +82,17 @@ class PasswordsAreEqual(exceptions.APIException):
|
||||||
"""
|
"""
|
||||||
status_code = status.HTTP_400_BAD_REQUEST
|
status_code = status.HTTP_400_BAD_REQUEST
|
||||||
default_detail = _('Password is already in use')
|
default_detail = _('Password is already in use')
|
||||||
|
|
||||||
|
|
||||||
|
class EmailConfirmedError(exceptions.APIException):
|
||||||
|
"""The exception should be raised when user email status is already confirmed
|
||||||
|
"""
|
||||||
|
status_code = status.HTTP_400_BAD_REQUEST
|
||||||
|
default_detail = _('Email address is already confirmed')
|
||||||
|
|
||||||
|
|
||||||
|
class WrongAuthCredentials(exceptions.APIException):
|
||||||
|
"""The exception should be raised when credentials is not valid for this user
|
||||||
|
"""
|
||||||
|
status_code = status.HTTP_400_BAD_REQUEST
|
||||||
|
default_detail = _('Wrong authorization credentials')
|
||||||
|
|
|
||||||
|
|
@ -133,6 +133,7 @@ class GMTokenGenerator(PasswordResetTokenGenerator):
|
||||||
def _make_hash_value(self, user, timestamp):
|
def _make_hash_value(self, user, timestamp):
|
||||||
return (
|
return (
|
||||||
str(user.pk) +
|
str(user.pk) +
|
||||||
|
str(user.email_confirmed) +
|
||||||
str(timestamp) +
|
str(timestamp) +
|
||||||
str(user.is_active)
|
str(user.is_active)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -356,5 +356,6 @@ PASSWORD_RESET_TIMEOUT_DAYS = 1
|
||||||
|
|
||||||
|
|
||||||
# TEMPLATES
|
# TEMPLATES
|
||||||
RESETTING_TOKEN_TEMPLATE_NAME = 'account/password_reset_email.html'
|
CONFIRMATION_PASSWORD_RESET_TEMPLATE = 'account/password_reset_confirm.html'
|
||||||
CONFIRM_SIGNUP_TEMPLATE = 'account/confirm_signup.html'
|
RESETTING_TOKEN_TEMPLATE = 'account/password_reset_email.html'
|
||||||
|
CONFIRM_EMAIL_TEMPLATE = 'account/confirm_email.html'
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
{% trans "Please confirm your email address to complete the registration:" %}
|
{% trans "Please confirm your email address to complete the registration:" %}
|
||||||
{% block signup_confirm %}
|
{% block signup_confirm %}
|
||||||
http://{{ domain_uri }}{% url 'auth:signup-finish' uid=uid token=token %}
|
http://{{ domain_uri }}{% url 'web:account:verify-email-confirm' uidb64=uid token=token %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% trans "Thanks for using our site!" %}
|
{% trans "Thanks for using our site!" %}
|
||||||
31
project/templates/account/password_reset_confirm.html
Normal file
31
project/templates/account/password_reset_confirm.html
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
{% load i18n static %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{% if validlink %}
|
||||||
|
|
||||||
|
<p>{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}</p>
|
||||||
|
|
||||||
|
<form method="post">{% csrf_token %}
|
||||||
|
<fieldset class="module aligned">
|
||||||
|
<div class="form-row field-password1">
|
||||||
|
{{ form.new_password1.errors }}
|
||||||
|
<label for="id_new_password1">{% trans 'New password:' %}</label>
|
||||||
|
{{ form.new_password1 }}
|
||||||
|
</div>
|
||||||
|
<div class="form-row field-password2">
|
||||||
|
{{ form.new_password2.errors }}
|
||||||
|
<label for="id_new_password2">{% trans 'Confirm password:' %}</label>
|
||||||
|
{{ form.new_password2 }}
|
||||||
|
</div>
|
||||||
|
<input type="submit" value="{% trans 'Change my password' %}">
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
<p>{% trans "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." %}</p>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
{% trans "Please go to the following page and choose a new password:" %}
|
{% trans "Please go to the following page and choose a new password:" %}
|
||||||
{% block reset_link %}
|
{% block reset_link %}
|
||||||
http://{{ domain_uri }}{% url 'web:account:password-reset-confirm' uid=uid token=token %}
|
http://{{ domain_uri }}{% url 'web:account:form-password-reset-confirm' uidb64=uidb64 token=token %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% trans "Thanks for using our site!" %}
|
{% trans "Thanks for using our site!" %}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user