version 0.0.21: added endpoints to reset password, added endpoint to finish signing up, move some tasks to celery, added templates

This commit is contained in:
Anatoly 2019-08-20 11:10:07 +03:00
parent 09ad324b43
commit 556e9ea564
19 changed files with 361 additions and 118 deletions

View File

@ -3,13 +3,15 @@ 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
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.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
@ -108,6 +110,24 @@ class User(ImageMixin, AbstractUser):
for token in refresh_tokens: for token in refresh_tokens:
token.revoke() 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): def get_body_email_message(self, subject: str, message: str):
"""Prepare the body of the email message""" """Prepare the body of the email message"""
return { return {
@ -119,7 +139,6 @@ class User(ImageMixin, AbstractUser):
def send_email(self, subject: str, message: str): def send_email(self, subject: str, message: str):
"""Send an email to reset user password""" """Send an email to reset user password"""
# todo: move to celery as celery task
send_mail(**self.get_body_email_message(subject=subject, send_mail(**self.get_body_email_message(subject=subject,
message=message)) message=message))
@ -129,11 +148,11 @@ class ResetPasswordTokenQuerySet(models.QuerySet):
def expired(self): def expired(self):
"""Show only expired""" """Show only expired"""
return self.filter(expiry_datetime__gt=timezone.now()) return self.filter(expiry_datetime__lt=timezone.now())
def valid(self): def valid(self):
"""Show only valid""" """Show only valid"""
return self.filter(expiry_datetime__lt=timezone.now()) return self.filter(expiry_datetime__gt=timezone.now())
def by_user(self, user): def by_user(self, user):
"""Show obj by user""" """Show obj by user"""
@ -143,8 +162,6 @@ class ResetPasswordTokenQuerySet(models.QuerySet):
class ResetPasswordToken(PlatformMixin, ProjectBaseMixin): class ResetPasswordToken(PlatformMixin, ProjectBaseMixin):
"""Reset password model""" """Reset password model"""
RESETTING_TOKEN_TEMPLATE_NAME = 'account/password_reset_email.html'
user = models.ForeignKey(User, user = models.ForeignKey(User,
related_name='password_reset_tokens', related_name='password_reset_tokens',
on_delete=models.CASCADE, on_delete=models.CASCADE,
@ -180,14 +197,20 @@ class ResetPasswordToken(PlatformMixin, ProjectBaseMixin):
"""Check if valid token or not""" """Check if valid token or not"""
return timezone.now() > self.expiry_datetime return timezone.now() > self.expiry_datetime
@property
def generate_token(self): def generate_token(self):
"""Generates a pseudo random code""" """Generates a pseudo random code"""
return default_token_generator.make_token(self.user) return gm_token_generator.make_token(self.user)
@staticmethod @staticmethod
def token_is_valid(user, token): def token_is_valid(user, token):
"""Check if token is valid""" """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): def save(self, *args, **kwargs):
"""Override save method""" """Override save method"""
@ -197,25 +220,14 @@ class ResetPasswordToken(PlatformMixin, ProjectBaseMixin):
timezone.timedelta(hours=self.get_resetting_token_expiration) timezone.timedelta(hours=self.get_resetting_token_expiration)
) )
if not self.key: if not self.key:
self.key = self.generate_token() self.key = self.generate_token
return super(ResetPasswordToken, self).save(*args, **kwargs) return super(ResetPasswordToken, self).save(*args, **kwargs)
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=self.RESETTING_TOKEN_TEMPLATE_NAME, template_name=settings.RESETTING_TOKEN_TEMPLATE_NAME,
context={'token': self.key, context={'token': self.key,
'uid': self.user.get_user_uid,
'domain_uri': settings.DOMAIN_URI}) '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])

View File

@ -1,8 +1,10 @@
"""Serializers for account web""" """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 rest_framework import serializers
from django.contrib.auth import password_validation as password_validators
from account import models from account import models
from account import tasks
from utils import exceptions as utils_exceptions from utils import exceptions as utils_exceptions
@ -22,26 +24,46 @@ class PasswordResetSerializer(serializers.ModelSerializer):
obj = models.ResetPasswordToken.objects.create( obj = models.ResetPasswordToken.objects.create(
user=user, user=user,
ip_address=ip_address, 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: try:
# todo: make as celery task # Compare new password with the old ones
obj.send_reset_password_request() if user.check_password(raw_password=password):
return obj raise utils_exceptions.PasswordsAreEqual()
except: # Validate password
raise utils_exceptions.EmailSendingError(user.email) 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): # Overdue instance
# """Serializer for reset password""" instance.overdue()
# return instance
# 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

24
apps/account/tasks.py Normal file
View File

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

View File

@ -9,8 +9,9 @@ app_name = 'account'
urlpatterns_api = [ urlpatterns_api = [
path('reset-password/', views.PasswordResetView.as_view(), path('reset-password/', views.PasswordResetView.as_view(),
name='password-reset'), name='password-reset'),
# path('reset-password/<str:token>/confirm/', views.PasswordResetConfirmView.as_view(), path('reset-password/confirm/<str:uid>/<str:token>/',
# name='password-reset-confirm'), views.PasswordResetConfirmView.as_view(),
name='password-reset-confirm'),
] ]
urlpatterns = urlpatterns_api + \ urlpatterns = urlpatterns_api + \

View File

@ -52,4 +52,4 @@ class FCMDeviceViewSet(generics.GenericAPIView):
# get object and check permissions or return None # get object and check permissions or return None
obj = queryset.filter(**filter).first() obj = queryset.filter(**filter).first()
obj and self.check_object_permissions(self.request, obj) obj and self.check_object_permissions(self.request, obj)
return obj return obj

View File

@ -1,29 +1,60 @@
"""Web account views""" """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 import models
from account.serializers import web as serializers 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 # Password reset
class PasswordResetView(generics.CreateAPIView): class PasswordResetView(JWTCreateAPIView):
"""View for resetting user password""" """View for resetting user password"""
serializer_class = serializers.PasswordResetSerializer serializer_class = serializers.PasswordResetSerializer
queryset = models.ResetPasswordToken queryset = models.ResetPasswordToken.objects.valid()
# class PasswordResetConfirmView(generics.GenericAPIView): class PasswordResetConfirmView(JWTGenericViewMixin):
# """View for confirmation new password""" """View for confirmation new password"""
# serializer_class = serializers.PasswordResetConfirmSerializer
# serializer_class = serializers.PasswordResetConfirmSerializer permission_classes = (permissions.AllowAny,)
#
# def post(self, request, *args, **kwargs): def get_queryset(self):
# """Post method to confirm user change password request""" """Override get_queryset method"""
# user = request.user return models.ResetPasswordToken.objects.valid()
# token = kwargs.get('token')
# serializer = self.get_serializer(data=request.data) def get_object(self):
# serializer.is_valid(raise_exception=True) """Override get_object method
# if models.ResetPasswordToken.token_is_valid(user=user, """
# token=token): queryset = self.filter_queryset(self.get_queryset())
# pass 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)

View File

@ -9,7 +9,7 @@ from django.conf import settings
from account import models as account_models from account import models as account_models
from authorization.models import Application, BlacklistedAccessToken from authorization.models import Application, BlacklistedAccessToken
from utils import exceptions as utils_exceptions from utils import exceptions as utils_exceptions
from rest_framework_simplejwt.tokens import RefreshToken from utils import methods as utils_methods
# JWT # JWT
from rest_framework_simplejwt import tokens from rest_framework_simplejwt import tokens
@ -60,7 +60,7 @@ class ClassicAuthSerializerMixin(BaseAuthSerializerMixin):
# Serializers # Serializers
class SignupSerializer(JWTBaseSerializerMixin, serializers.ModelSerializer): class SignupSerializer(serializers.ModelSerializer):
"""Signup serializer serializer mixin""" """Signup serializer serializer mixin"""
# REQUEST # REQUEST
username = serializers.CharField( username = serializers.CharField(
@ -76,10 +76,19 @@ class SignupSerializer(JWTBaseSerializerMixin, serializers.ModelSerializer):
class Meta: class Meta:
model = account_models.User model = account_models.User
fields = ( fields = (
'username', 'password', 'email', 'newsletter', 'username',
'access_token', 'refresh_token', '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): def validate_password(self, data):
"""Custom password validation""" """Custom password validation"""
try: try:
@ -137,7 +146,7 @@ class RefreshTokenSerializer(serializers.Serializer):
def validate(self, attrs): def validate(self, attrs):
"""Override validate method""" """Override validate method"""
token = RefreshToken(attrs['refresh_token']) token = tokens.RefreshToken(attrs['refresh_token'])
data = {'access_token': str(token.access_token)} data = {'access_token': str(token.access_token)}

View File

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

View File

@ -1,11 +1,11 @@
"""Common url routing for application authorization""" """Common url routing for application authorization"""
from django.conf import settings from django.conf import settings
from django.urls import path
from django.conf.urls import url from django.conf.urls import url
from django.urls import path
from oauth2_provider.views import AuthorizationView 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 rest_framework_social_oauth2 import views as drf_social_oauth2_views
from social_core.utils import setting_name from social_core.utils import setting_name
from social_django import views as social_django_views
from authorization.views import common as views from authorization.views import common as views
@ -31,6 +31,8 @@ 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'),

View File

@ -3,6 +3,9 @@ import json
from braces.views import CsrfExemptMixin from braces.views import CsrfExemptMixin
from django.conf import settings 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 django.utils.translation import gettext_lazy as _
from oauth2_provider.oauth2_backends import OAuthLibCore from oauth2_provider.oauth2_backends import OAuthLibCore
from oauth2_provider.settings import oauth2_settings 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 rest_framework_social_oauth2.oauth2_endpoints import SocialTokenServer
from account.models import User from account.models import User
from authorization import tasks
from authorization.models import Application from authorization.models import Application
from authorization.serializers import common as serializers from authorization.serializers import common 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.views import (JWTGenericViewMixin, from utils.views import (JWTGenericViewMixin,
JWTCreateAPIView) JWTCreateAPIView)
@ -187,26 +192,59 @@ class SignUpView(JWTCreateAPIView):
_locale = self._get_locale(request) _locale = self._get_locale(request)
try: try:
locale = self._check_locale(locale=_locale) locale = self._check_locale(locale=_locale)
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(serializer.data, status=status.HTTP_201_CREATED)
if settings.USE_CELERY:
access_token = serializer.data.get('access_token') tasks.send_confirm_signup_email.delay(serializer.instance.id)
refresh_token = serializer.data.get('refresh_token') 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:
return self._put_cookies_in_response( return self._put_cookies_in_response(
cookies=self._put_data_in_cookies( cookies=self._put_data_in_cookies(locale=locale),
locale=locale,
access_token=access_token,
refresh_token=refresh_token),
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"""

View File

@ -61,3 +61,24 @@ class LocaleNotExisted(exceptions.APIException):
'detail': self.default_detail % locale 'detail': self.default_detail % locale
} }
super().__init__() 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')

View File

@ -2,6 +2,9 @@
import random import random
from django.http.request import HttpRequest from django.http.request import HttpRequest
from rest_framework.request import Request 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): 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] return request.headers.get('Authorization').split(' ')[::-1][0]
elif isinstance(request, Request): elif isinstance(request, Request):
return request._request.headers.get('Authorization').split(' ')[::-1][0] 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)

View File

@ -1,16 +1,15 @@
"""Utils app models.""" """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.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 import timezone
from django.utils.html import mark_safe from django.utils.html import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from easy_thumbnails.fields import ThumbnailerImageField from easy_thumbnails.fields import ThumbnailerImageField
from django.contrib.postgres.fields import JSONField
from django.contrib.postgres.fields.jsonb import KeyTextTransform from utils.methods import image_path
from django.contrib.auth.tokens import PasswordResetTokenGenerator
class ProjectBaseMixin(models.Model): class ProjectBaseMixin(models.Model):
@ -53,19 +52,6 @@ class BaseAttributes(ProjectBaseMixin):
abstract = True 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): class ImageMixin(models.Model):
"""Avatar model.""" """Avatar model."""
@ -142,12 +128,14 @@ class LocaleManagerMixin(models.Manager):
return queryset return queryset
class SignupConfirmationTokenGenerator(PasswordResetTokenGenerator): class GMTokenGenerator(PasswordResetTokenGenerator):
def _make_hash_value(self, user, timestamp): def _make_hash_value(self, user, timestamp):
return ( 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()

View File

@ -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 django.contrib.auth.models import User
from oauth2_provider.models import AccessToken
from rest_framework_social_oauth2.backends import DjangoOAuth2
class GMOAuth2(DjangoOAuth2): class GMOAuth2(DjangoOAuth2):
@ -9,10 +9,10 @@ class GMOAuth2(DjangoOAuth2):
if response.get(self.ID_KEY, None): if response.get(self.ID_KEY, None):
user = User.objects.get(pk=response[self.ID_KEY]) user = User.objects.get(pk=response[self.ID_KEY])
return {'username': user.username, return {'username': user.username,
'email': user.email, 'email': user.email,
'fullname': user.get_full_name(), 'fullname': user.get_full_name(),
'first_name': user.first_name, 'first_name': user.first_name,
'last_name': user.last_name 'last_name': user.last_name
} }
return {} return {}

View File

@ -1,9 +1,12 @@
from rest_framework import generics
from collections import namedtuple 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 translation import models as translation_models
from utils import exceptions from utils import exceptions
from rest_framework.response import Response from rest_framework_simplejwt import tokens
from rest_framework import status
# JWT # JWT
@ -17,7 +20,16 @@ class JWTGenericViewMixin(generics.GenericAPIView):
REFRESH_TOKEN_HTTP_ONLY = False REFRESH_TOKEN_HTTP_ONLY = False
REFRESH_TOKEN_SECURE = False REFRESH_TOKEN_SECURE = False
COOKIE = namedtuple('COOKIE', ['key', 'value', 'http_only', 'secure']) 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): def _get_locale(self, request):
"""Get locale from request""" """Get locale from request"""
return request.COOKIES.get('locale') return request.COOKIES.get('locale')
@ -62,10 +74,20 @@ class JWTGenericViewMixin(generics.GenericAPIView):
def _put_cookies_in_response(self, cookies: list, response: Response): def _put_cookies_in_response(self, cookies: list, response: Response):
"""Update COOKIES in response from namedtuple""" """Update COOKIES in response from namedtuple"""
for cookie in cookies: for cookie in cookies:
response.set_cookie(key=cookie.key, # todo: remove config for develop
value=cookie.value, import os
secure=cookie.secure, configuration = os.environ.get('SETTINGS_CONFIGURATION', None)
httponly=cookie.http_only) 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 return response
def _get_tokens_from_cookies(self, request, cookies: dict = None): def _get_tokens_from_cookies(self, request, cookies: dict = None):

View File

@ -72,7 +72,7 @@ EXTERNAL_APPS = [
'social_django', 'social_django',
'rest_framework_social_oauth2', 'rest_framework_social_oauth2',
'django_extensions', '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_LIFETIME': timedelta(minutes=5),
'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1), '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'

View File

@ -5,8 +5,19 @@ ALLOWED_HOSTS = ['*', ]
SEND_SMS = False SEND_SMS = False
SMS_CODE_SHOW = True SMS_CODE_SHOW = True
USE_CELERY = False
DOMAIN_URI = '0.0.0.0:8000' 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 # Increase access token lifetime for local deploy
SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'] = timedelta(days=365) SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'] = timedelta(days=365)

View File

@ -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 %}

View File

@ -3,9 +3,8 @@
{% 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' token=token %} http://{{ domain_uri }}{% url 'web:account:password-reset-confirm' uid=uid token=token %}
{% endblock %} {% endblock %}
{% trans 'Your username, in case youve forgotten:' %} {{ user.get_username }}
{% trans "Thanks for using our site!" %} {% trans "Thanks for using our site!" %}