diff --git a/apps/account/models.py b/apps/account/models.py index c9342680..8f4e45b3 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -3,13 +3,15 @@ from typing import Union from django.conf import settings from django.contrib.auth.models import AbstractUser, UserManager as BaseUserManager -from django.contrib.auth.tokens import default_token_generator from django.core.mail import send_mail from django.db import models from django.template.loader import render_to_string from django.utils import timezone from django.utils.translation import ugettext_lazy as _ 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 utils.models import ImageMixin, ProjectBaseMixin, PlatformMixin @@ -108,6 +110,24 @@ class User(ImageMixin, AbstractUser): for token in refresh_tokens: token.revoke() + @property + def get_signup_finish_token(self): + """Make a token for finish signup.""" + return gm_token_generator.make_token(self) + + @property + def get_user_uid(self): + """Get base64 value for user by primary key identifier""" + return urlsafe_base64_encode(force_bytes(self.pk)) + + def get_confirm_signup_template(self): + """Get confirm signup email template""" + return render_to_string( + template_name=settings.CONFIRM_SIGNUP_TEMPLATE, + context={'token': self.get_signup_finish_token, + 'uid': self.get_user_uid, + 'domain_uri': settings.DOMAIN_URI}) + def get_body_email_message(self, subject: str, message: str): """Prepare the body of the email message""" return { @@ -119,7 +139,6 @@ class User(ImageMixin, AbstractUser): def send_email(self, subject: str, message: str): """Send an email to reset user password""" - # todo: move to celery as celery task send_mail(**self.get_body_email_message(subject=subject, message=message)) @@ -129,11 +148,11 @@ class ResetPasswordTokenQuerySet(models.QuerySet): def expired(self): """Show only expired""" - return self.filter(expiry_datetime__gt=timezone.now()) + return self.filter(expiry_datetime__lt=timezone.now()) def valid(self): """Show only valid""" - return self.filter(expiry_datetime__lt=timezone.now()) + return self.filter(expiry_datetime__gt=timezone.now()) def by_user(self, user): """Show obj by user""" @@ -143,8 +162,6 @@ class ResetPasswordTokenQuerySet(models.QuerySet): class ResetPasswordToken(PlatformMixin, ProjectBaseMixin): """Reset password model""" - RESETTING_TOKEN_TEMPLATE_NAME = 'account/password_reset_email.html' - user = models.ForeignKey(User, related_name='password_reset_tokens', on_delete=models.CASCADE, @@ -180,14 +197,20 @@ class ResetPasswordToken(PlatformMixin, ProjectBaseMixin): """Check if valid token or not""" return timezone.now() > self.expiry_datetime + @property def generate_token(self): """Generates a pseudo random code""" - return default_token_generator.make_token(self.user) + return gm_token_generator.make_token(self.user) @staticmethod def token_is_valid(user, token): """Check if token is valid""" - return default_token_generator.check_token(user, token) + return gm_token_generator.check_token(user, token) + + def overdue(self): + """Overdue instance""" + self.expiry_datetime = timezone.now() + self.save() def save(self, *args, **kwargs): """Override save method""" @@ -197,25 +220,14 @@ class ResetPasswordToken(PlatformMixin, ProjectBaseMixin): timezone.timedelta(hours=self.get_resetting_token_expiration) ) if not self.key: - self.key = self.generate_token() + self.key = self.generate_token return super(ResetPasswordToken, self).save(*args, **kwargs) def get_reset_password_template(self): """Get reset password template""" return render_to_string( - template_name=self.RESETTING_TOKEN_TEMPLATE_NAME, + template_name=settings.RESETTING_TOKEN_TEMPLATE_NAME, context={'token': self.key, + 'uid': self.user.get_user_uid, 'domain_uri': settings.DOMAIN_URI}) - def send_reset_password_request(self): - """Method to reset user password""" - subject = _('Password resetting') - # Send an email with url for resetting a password - self.user.send_email(subject=subject, - message=self.get_reset_password_template()) - - def confirm_reset_password_request(self): - """Method to confirm reset user password request""" - # Remove access token and revoke refresh tokens - self.user.remove_access_tokens(source=[Application.MOBILE, - Application.WEB]) diff --git a/apps/account/serializers/web.py b/apps/account/serializers/web.py index 6305c5a0..014a58b8 100644 --- a/apps/account/serializers/web.py +++ b/apps/account/serializers/web.py @@ -1,8 +1,10 @@ """Serializers for account web""" -from django.contrib.auth import password_validation as password_validators +from django.conf import settings from rest_framework import serializers +from django.contrib.auth import password_validation as password_validators from account import models +from account import tasks from utils import exceptions as utils_exceptions @@ -22,26 +24,46 @@ class PasswordResetSerializer(serializers.ModelSerializer): obj = models.ResetPasswordToken.objects.create( user=user, ip_address=ip_address, - source=models.ResetPasswordToken.MOBILE + source=models.ResetPasswordToken.WEB ) + if settings.USE_CELERY: + tasks.send_reset_password_email.delay(obj.id) + else: + tasks.send_reset_password_email(obj.id) + return obj + + +class PasswordResetConfirmSerializer(serializers.ModelSerializer): + """Serializer for model User""" + + password = serializers.CharField(write_only=True) + + class Meta: + """Meta class""" + model = models.ResetPasswordToken + fields = ('password', ) + + def validate(self, attrs): + """Override validate method""" + user = self.instance.user + password = attrs.get('password') try: - # todo: make as celery task - obj.send_reset_password_request() - return obj - except: - raise utils_exceptions.EmailSendingError(user.email) + # Compare new password with the old ones + if user.check_password(raw_password=password): + raise utils_exceptions.PasswordsAreEqual() + # Validate password + password_validators.validate_password(password=password) + except serializers.ValidationError as e: + raise serializers.ValidationError(str(e)) + else: + return attrs + def update(self, instance, validated_data): + """Override update method""" + # Update user password from instance + instance.user.set_password(validated_data.get('password')) + instance.user.save() -# class PasswordResetConfirmSerializer(serializers.Serializer): -# """Serializer for reset password""" -# -# password = serializers.CharField(write_only=True) -# -# def validate_password(self, data): -# """Custom password validation""" -# try: -# password_validators.validate_password(password=data) -# except serializers.ValidationError as e: -# raise serializers.ValidationError(str(e)) -# else: -# return data + # Overdue instance + instance.overdue() + return instance diff --git a/apps/account/tasks.py b/apps/account/tasks.py new file mode 100644 index 00000000..812f11dd --- /dev/null +++ b/apps/account/tasks.py @@ -0,0 +1,24 @@ +"""Account app celery tasks.""" +import logging + +from celery import shared_task +from django.utils.translation import gettext_lazy as _ + +from . import models as account_models + +logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.INFO) +logger = logging.getLogger(__name__) + + +@shared_task +def send_reset_password_email(request_id): + """Send email to user for reset password.""" + try: + obj = account_models.ResetPasswordToken.objects.get(id=request_id) + user = obj.user + user.send_email(subject=_('Password resetting'), + message=obj.get_reset_password_template()) + except: + logger.error(f'METHOD_NAME: {send_reset_password_email.__name__}\n' + f'DETAIL: Exception occurred for ResetPasswordToken instance: ' + f'{request_id}') diff --git a/apps/account/urls/web.py b/apps/account/urls/web.py index 92f69e77..110fccb2 100644 --- a/apps/account/urls/web.py +++ b/apps/account/urls/web.py @@ -9,8 +9,9 @@ app_name = 'account' urlpatterns_api = [ path('reset-password/', views.PasswordResetView.as_view(), name='password-reset'), - # path('reset-password//confirm/', views.PasswordResetConfirmView.as_view(), - # name='password-reset-confirm'), + path('reset-password/confirm///', + views.PasswordResetConfirmView.as_view(), + name='password-reset-confirm'), ] urlpatterns = urlpatterns_api + \ diff --git a/apps/account/views/common.py b/apps/account/views/common.py index eee66b19..d7a422a2 100644 --- a/apps/account/views/common.py +++ b/apps/account/views/common.py @@ -52,4 +52,4 @@ class FCMDeviceViewSet(generics.GenericAPIView): # get object and check permissions or return None obj = queryset.filter(**filter).first() obj and self.check_object_permissions(self.request, obj) - return obj \ No newline at end of file + return obj diff --git a/apps/account/views/web.py b/apps/account/views/web.py index f61923c8..c780b6db 100644 --- a/apps/account/views/web.py +++ b/apps/account/views/web.py @@ -1,29 +1,60 @@ """Web account views""" -from rest_framework import generics +from django.utils.encoding import force_text +from django.utils.http import urlsafe_base64_decode +from rest_framework import permissions +from rest_framework import status +from django.shortcuts import get_object_or_404 +from rest_framework.response import Response from account import models from account.serializers import web as serializers +from utils import exceptions as utils_exceptions +from utils.models import gm_token_generator +from utils.views import (JWTCreateAPIView, + JWTGenericViewMixin) # Password reset -class PasswordResetView(generics.CreateAPIView): +class PasswordResetView(JWTCreateAPIView): """View for resetting user password""" - serializer_class = serializers.PasswordResetSerializer - queryset = models.ResetPasswordToken + queryset = models.ResetPasswordToken.objects.valid() -# class PasswordResetConfirmView(generics.GenericAPIView): -# """View for confirmation new password""" -# -# serializer_class = serializers.PasswordResetConfirmSerializer -# -# def post(self, request, *args, **kwargs): -# """Post method to confirm user change password request""" -# user = request.user -# token = kwargs.get('token') -# serializer = self.get_serializer(data=request.data) -# serializer.is_valid(raise_exception=True) -# if models.ResetPasswordToken.token_is_valid(user=user, -# token=token): -# pass +class PasswordResetConfirmView(JWTGenericViewMixin): + """View for confirmation new password""" + serializer_class = serializers.PasswordResetConfirmSerializer + permission_classes = (permissions.AllowAny,) + + def get_queryset(self): + """Override get_queryset method""" + return models.ResetPasswordToken.objects.valid() + + def get_object(self): + """Override get_object method + """ + queryset = self.filter_queryset(self.get_queryset()) + uidb64 = self.kwargs.get('uid') + + uid = force_text(urlsafe_base64_decode(uidb64)) + token = self.kwargs.get('token') + + filter_kwargs = {'key': token, 'user_id': uid} + obj = get_object_or_404(queryset, **filter_kwargs) + + if not gm_token_generator.check_token(user=obj.user, token=token): + raise utils_exceptions.NotValidTokenError() + + # May raise a permission denied + self.check_object_permissions(self.request, obj) + + return obj + + def put(self, request, *args, **kwargs): + """Implement PUT method""" + instance = self.get_object() + serializer = self.get_serializer(instance=instance, + data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(status=status.HTTP_200_OK) diff --git a/apps/authorization/serializers/common.py b/apps/authorization/serializers/common.py index 12a98bf7..8cff690d 100644 --- a/apps/authorization/serializers/common.py +++ b/apps/authorization/serializers/common.py @@ -9,7 +9,7 @@ from django.conf import settings from account import models as account_models from authorization.models import Application, BlacklistedAccessToken from utils import exceptions as utils_exceptions -from rest_framework_simplejwt.tokens import RefreshToken +from utils import methods as utils_methods # JWT from rest_framework_simplejwt import tokens @@ -60,7 +60,7 @@ class ClassicAuthSerializerMixin(BaseAuthSerializerMixin): # Serializers -class SignupSerializer(JWTBaseSerializerMixin, serializers.ModelSerializer): +class SignupSerializer(serializers.ModelSerializer): """Signup serializer serializer mixin""" # REQUEST username = serializers.CharField( @@ -76,10 +76,19 @@ class SignupSerializer(JWTBaseSerializerMixin, serializers.ModelSerializer): class Meta: model = account_models.User fields = ( - 'username', 'password', 'email', 'newsletter', - 'access_token', 'refresh_token', + 'username', + 'password', + 'email', + 'newsletter' ) + def validate_username(self, data): + """Custom username validation""" + valid = utils_methods.username_validator(username=data) + if not valid: + raise utils_exceptions.NotValidUsernameError() + return data + def validate_password(self, data): """Custom password validation""" try: @@ -137,7 +146,7 @@ class RefreshTokenSerializer(serializers.Serializer): def validate(self, attrs): """Override validate method""" - token = RefreshToken(attrs['refresh_token']) + token = tokens.RefreshToken(attrs['refresh_token']) data = {'access_token': str(token.access_token)} diff --git a/apps/authorization/tasks.py b/apps/authorization/tasks.py new file mode 100644 index 00000000..895150e8 --- /dev/null +++ b/apps/authorization/tasks.py @@ -0,0 +1,21 @@ +"""Authorization app celery tasks.""" +import logging +from django.utils.translation import gettext_lazy as _ +from celery import shared_task + +from account import models as account_models + +logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.INFO) +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}') diff --git a/apps/authorization/urls/common.py b/apps/authorization/urls/common.py index 941b7629..42bdc5cb 100644 --- a/apps/authorization/urls/common.py +++ b/apps/authorization/urls/common.py @@ -1,11 +1,11 @@ """Common url routing for application authorization""" from django.conf import settings -from django.urls import path from django.conf.urls import url +from django.urls import path from oauth2_provider.views import AuthorizationView -from social_django import views as social_django_views from rest_framework_social_oauth2 import views as drf_social_oauth2_views from social_core.utils import setting_name +from social_django import views as social_django_views from authorization.views import common as views @@ -31,6 +31,8 @@ urlpatterns_oauth2 = [ urlpatterns_jwt = [ path('signup/', views.SignUpView.as_view(), name='signup'), + path('signup/finish///', views.SignupFinishView.as_view(), + name='signup-finish'), # sign in path('login/', views.LoginByUsernameOrEmailView.as_view(), name='login'), diff --git a/apps/authorization/views/common.py b/apps/authorization/views/common.py index 60912337..f72a70e4 100644 --- a/apps/authorization/views/common.py +++ b/apps/authorization/views/common.py @@ -3,6 +3,9 @@ import json from braces.views import CsrfExemptMixin from django.conf import settings +from django.urls import reverse +from django.utils.encoding import force_text +from django.utils.http import urlsafe_base64_decode from django.utils.translation import gettext_lazy as _ from oauth2_provider.oauth2_backends import OAuthLibCore from oauth2_provider.settings import oauth2_settings @@ -16,9 +19,11 @@ from rest_framework_social_oauth2.oauth2_backends import KeepRequestCore from rest_framework_social_oauth2.oauth2_endpoints import SocialTokenServer from account.models import User +from authorization import tasks from authorization.models import Application from authorization.serializers import common as serializers from utils import exceptions as utils_exceptions +from utils.models import gm_token_generator from utils.views import (JWTGenericViewMixin, JWTCreateAPIView) @@ -187,26 +192,59 @@ class SignUpView(JWTCreateAPIView): _locale = self._get_locale(request) try: locale = self._check_locale(locale=_locale) - serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) serializer.save() - response = Response(serializer.data, status=status.HTTP_201_CREATED) - - access_token = serializer.data.get('access_token') - refresh_token = serializer.data.get('refresh_token') + 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: raise utils_exceptions.LocaleNotExisted(locale=_locale) else: return self._put_cookies_in_response( - cookies=self._put_data_in_cookies( - locale=locale, - access_token=access_token, - refresh_token=refresh_token), + cookies=self._put_data_in_cookies(locale=locale), 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 class LoginByUsernameOrEmailView(JWTAuthViewMixin): """Login by email and password""" diff --git a/apps/utils/exceptions.py b/apps/utils/exceptions.py index 4e8b56fa..389fe40a 100644 --- a/apps/utils/exceptions.py +++ b/apps/utils/exceptions.py @@ -61,3 +61,24 @@ class LocaleNotExisted(exceptions.APIException): 'detail': self.default_detail % locale } super().__init__() + + +class NotValidUsernameError(exceptions.APIException): + """The exception should be thrown when passed username has @ symbol + """ + status_code = status.HTTP_400_BAD_REQUEST + default_detail = _('Wrong username') + + +class NotValidTokenError(exceptions.APIException): + """The exception should be thrown when token in url is not valid + """ + status_code = status.HTTP_400_BAD_REQUEST + default_detail = _('Not valid token') + + +class PasswordsAreEqual(exceptions.APIException): + """The exception should be raised when passed password is the same as old ones + """ + status_code = status.HTTP_400_BAD_REQUEST + default_detail = _('Password is already in use') diff --git a/apps/utils/methods.py b/apps/utils/methods.py index a14b4460..f55b0fe2 100644 --- a/apps/utils/methods.py +++ b/apps/utils/methods.py @@ -2,6 +2,9 @@ import random from django.http.request import HttpRequest from rest_framework.request import Request +import re +from django.utils.timezone import datetime +from django.conf import settings def generate_code(digits=6, string_output=True): @@ -19,3 +22,21 @@ def get_token_from_request(request): return request.headers.get('Authorization').split(' ')[::-1][0] elif isinstance(request, Request): return request._request.headers.get('Authorization').split(' ')[::-1][0] + + +def username_validator(username: str) -> bool: + """Validate given username""" + pattern = r'[@,]+' + if re.search(pattern=pattern, string=username): + return False + else: + return True + + +def image_path(instance, filename): + """Determine avatar path method.""" + filename = '%s.jpeg' % generate_code() + return 'image/%s/%s/%s' % ( + instance._meta.model_name, + datetime.now().strftime(settings.REST_DATE_FORMAT), + filename) diff --git a/apps/utils/models.py b/apps/utils/models.py index 40b5fba1..f65b371e 100644 --- a/apps/utils/models.py +++ b/apps/utils/models.py @@ -1,16 +1,15 @@ """Utils app models.""" -import random -from datetime import datetime -from django.conf import settings +from django.contrib.auth.tokens import PasswordResetTokenGenerator from django.contrib.gis.db import models +from django.contrib.postgres.fields import JSONField +from django.contrib.postgres.fields.jsonb import KeyTextTransform from django.utils import timezone from django.utils.html import mark_safe from django.utils.translation import ugettext_lazy as _ from easy_thumbnails.fields import ThumbnailerImageField -from django.contrib.postgres.fields import JSONField -from django.contrib.postgres.fields.jsonb import KeyTextTransform -from django.contrib.auth.tokens import PasswordResetTokenGenerator + +from utils.methods import image_path class ProjectBaseMixin(models.Model): @@ -53,19 +52,6 @@ class BaseAttributes(ProjectBaseMixin): abstract = True -def generate_code(): - """Generate code method.""" - return '%06d' % random.randint(0, 999999) - - -def image_path(instance, filename): - """Determine avatar path method.""" - filename = '%s.jpeg' % generate_code() - return 'image/%s/%s/%s' % ( - instance._meta.model_name, - datetime.now().strftime(settings.REST_DATE_FORMAT), - filename) - class ImageMixin(models.Model): """Avatar model.""" @@ -142,12 +128,14 @@ class LocaleManagerMixin(models.Manager): return queryset -class SignupConfirmationTokenGenerator(PasswordResetTokenGenerator): +class GMTokenGenerator(PasswordResetTokenGenerator): def _make_hash_value(self, user, timestamp): return ( - str(user.pk) + str(timestamp) + str(user.is_active) + str(user.pk) + + str(timestamp) + + str(user.is_active) ) -confirm_signup_token = SignupConfirmationTokenGenerator() +gm_token_generator = GMTokenGenerator() diff --git a/apps/utils/oauth2.py b/apps/utils/oauth2.py index 8740f13b..2e11b68a 100644 --- a/apps/utils/oauth2.py +++ b/apps/utils/oauth2.py @@ -1,6 +1,6 @@ -from rest_framework_social_oauth2.backends import DjangoOAuth2 -from oauth2_provider.models import AccessToken from django.contrib.auth.models import User +from oauth2_provider.models import AccessToken +from rest_framework_social_oauth2.backends import DjangoOAuth2 class GMOAuth2(DjangoOAuth2): @@ -9,10 +9,10 @@ class GMOAuth2(DjangoOAuth2): if response.get(self.ID_KEY, None): user = User.objects.get(pk=response[self.ID_KEY]) return {'username': user.username, - 'email': user.email, - 'fullname': user.get_full_name(), - 'first_name': user.first_name, - 'last_name': user.last_name + 'email': user.email, + 'fullname': user.get_full_name(), + 'first_name': user.first_name, + 'last_name': user.last_name } return {} diff --git a/apps/utils/views.py b/apps/utils/views.py index 2040d55a..57f1f6b2 100644 --- a/apps/utils/views.py +++ b/apps/utils/views.py @@ -1,9 +1,12 @@ -from rest_framework import generics from collections import namedtuple + +from rest_framework import generics +from rest_framework import status +from rest_framework.response import Response + from translation import models as translation_models from utils import exceptions -from rest_framework.response import Response -from rest_framework import status +from rest_framework_simplejwt import tokens # JWT @@ -17,7 +20,16 @@ class JWTGenericViewMixin(generics.GenericAPIView): REFRESH_TOKEN_HTTP_ONLY = False REFRESH_TOKEN_SECURE = False COOKIE = namedtuple('COOKIE', ['key', 'value', 'http_only', 'secure']) - + + def _create_jwt_token(self, user) -> dict: + """Return dictionary with pairs access and refresh tokens""" + token = tokens.RefreshToken.for_user(user) + token['user'] = user.get_user_info() + return { + 'access_token': str(token.access_token), + 'refresh_token': str(token), + } + def _get_locale(self, request): """Get locale from request""" return request.COOKIES.get('locale') @@ -62,10 +74,20 @@ class JWTGenericViewMixin(generics.GenericAPIView): def _put_cookies_in_response(self, cookies: list, response: Response): """Update COOKIES in response from namedtuple""" for cookie in cookies: - response.set_cookie(key=cookie.key, - value=cookie.value, - secure=cookie.secure, - httponly=cookie.http_only) + # todo: remove config for develop + import os + configuration = os.environ.get('SETTINGS_CONFIGURATION', None) + if configuration == 'development': + response.set_cookie(key=cookie.key, + value=cookie.value, + secure=cookie.secure, + httponly=cookie.http_only, + domain='.id-east.ru') + else: + response.set_cookie(key=cookie.key, + value=cookie.value, + secure=cookie.secure, + httponly=cookie.http_only) return response def _get_tokens_from_cookies(self, request, cookies: dict = None): diff --git a/project/settings/base.py b/project/settings/base.py index 18df0282..5e5d1c23 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -72,7 +72,7 @@ EXTERNAL_APPS = [ 'social_django', 'rest_framework_social_oauth2', 'django_extensions', - 'rest_framework_simplejwt.token_blacklist' + 'rest_framework_simplejwt.token_blacklist', ] @@ -349,3 +349,12 @@ SIMPLE_JWT = { 'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5), 'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1), } + + +# AUTHORIZATION +PASSWORD_RESET_TIMEOUT_DAYS = 1 + + +# TEMPLATES +RESETTING_TOKEN_TEMPLATE_NAME = 'account/password_reset_email.html' +CONFIRM_SIGNUP_TEMPLATE = 'account/confirm_signup.html' diff --git a/project/settings/local.py b/project/settings/local.py index cb0af467..53e14a3b 100644 --- a/project/settings/local.py +++ b/project/settings/local.py @@ -5,8 +5,19 @@ ALLOWED_HOSTS = ['*', ] SEND_SMS = False SMS_CODE_SHOW = True +USE_CELERY = False DOMAIN_URI = '0.0.0.0:8000' +# OTHER SETTINGS +API_HOST = '0.0.0.0:8000' +API_HOST_URL = 'http://%s' % API_HOST + + +# CELERY +BROKER_URL = 'amqp://rabbitmq:5672' +CELERY_RESULT_BACKEND = BROKER_URL +CELERY_BROKER_URL = BROKER_URL + # Increase access token lifetime for local deploy SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'] = timedelta(days=365) diff --git a/project/templates/account/confirm_signup.html b/project/templates/account/confirm_signup.html new file mode 100644 index 00000000..52b98578 --- /dev/null +++ b/project/templates/account/confirm_signup.html @@ -0,0 +1,12 @@ +{% load i18n %}{% autoescape off %} +{% blocktrans %}You're receiving this email because you trying to register new account at {{ site_name }}.{% endblocktrans %} + +{% trans "Please confirm your email address to complete the registration:" %} +{% block signup_confirm %} +http://{{ domain_uri }}{% url 'auth:signup-finish' uid=uid token=token %} +{% endblock %} + +{% trans "Thanks for using our site!" %} + +{% blocktrans %}The {{ site_name }} team{% endblocktrans %} +{% endautoescape %} \ No newline at end of file diff --git a/project/templates/account/password_reset_email.html b/project/templates/account/password_reset_email.html index ee8c20f3..74fbc545 100644 --- a/project/templates/account/password_reset_email.html +++ b/project/templates/account/password_reset_email.html @@ -3,9 +3,8 @@ {% trans "Please go to the following page and choose a new password:" %} {% block reset_link %} -http://{{ domain_uri }}{% url 'web:account:password-reset-confirm' token=token %} +http://{{ domain_uri }}{% url 'web:account:password-reset-confirm' uid=uid token=token %} {% endblock %} -{% trans 'Your username, in case you’ve forgotten:' %} {{ user.get_username }} {% trans "Thanks for using our site!" %}