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:
parent
09ad324b43
commit
556e9ea564
|
|
@ -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])
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
24
apps/account/tasks.py
Normal file
24
apps/account/tasks.py
Normal 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}')
|
||||
|
|
@ -9,8 +9,9 @@ app_name = 'account'
|
|||
urlpatterns_api = [
|
||||
path('reset-password/', views.PasswordResetView.as_view(),
|
||||
name='password-reset'),
|
||||
# path('reset-password/<str:token>/confirm/', views.PasswordResetConfirmView.as_view(),
|
||||
# name='password-reset-confirm'),
|
||||
path('reset-password/confirm/<str:uid>/<str:token>/',
|
||||
views.PasswordResetConfirmView.as_view(),
|
||||
name='password-reset-confirm'),
|
||||
]
|
||||
|
||||
urlpatterns = urlpatterns_api + \
|
||||
|
|
|
|||
|
|
@ -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
|
||||
return obj
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
|
||||
|
|
|
|||
21
apps/authorization/tasks.py
Normal file
21
apps/authorization/tasks.py
Normal 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}')
|
||||
|
|
@ -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/<str:uid>/<str:token>/', views.SignupFinishView.as_view(),
|
||||
name='signup-finish'),
|
||||
# sign in
|
||||
path('login/', views.LoginByUsernameOrEmailView.as_view(),
|
||||
name='login'),
|
||||
|
|
|
|||
|
|
@ -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"""
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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 {}
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
12
project/templates/account/confirm_signup.html
Normal file
12
project/templates/account/confirm_signup.html
Normal 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 %}
|
||||
|
|
@ -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!" %}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user