Skip to content

Commit

Permalink
Merge 9f13d22 into e29d703
Browse files Browse the repository at this point in the history
  • Loading branch information
phillipseryazi committed Aug 9, 2018
2 parents e29d703 + 9f13d22 commit 97040cd
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 21 deletions.
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ omit =
*/migrations/*
*/venv/*
*email_reg_util*
*reset_password_util*

1 change: 0 additions & 1 deletion authors/apps/authentication/email_reg_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import smtplib
import os


class SendAuthEmail:
Expand Down
31 changes: 31 additions & 0 deletions authors/apps/authentication/reset_password_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from django.template.loader import get_template
import smtplib
import os


class ResetPasswordUtil():
def send_mail(self, request, sender_email, reciever_email, token):
msg = MIMEMultipart()

if sender_email == None or reciever_email == None:
raise ValueError('Invalid parameters!')

msg['From'] = sender_email
msg['To'] = reciever_email
msg['Subject'] = "Reseting password for your Author's Haven account."
body = self.html_renderer(request, token)
msg.attach(MIMEText(body, 'html'))

server = smtplib.SMTP('smtp.gmail.com', os.environ.get('EMAIL_PORT'))
server.starttls()
server.login(os.environ.get('EMAIL_HOST_USER'),
os.environ.get('EMAIL_HOST_USER_PASSWORD'))
server.sendmail(msg['From'], msg['To'], msg.as_string())
server.quit()

def html_renderer(self, request, token):
template = get_template('authentication/reset_link.html')
context = {'link': 'api/users/password/reset', 'token': token}
return template.render(context, request)
17 changes: 0 additions & 17 deletions authors/apps/authentication/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,23 +48,6 @@ def validate_password(self, value):
"Please include at least a number and any of these symbols in your password @,#,!,$,%,&,*,(,) ")
return value

def validate_email(self, value):
user_qs = User.objects.filter(email=value)
if user_qs.exists():
raise serializers.ValidationError("We cannot register you because there's a user with that email already.")
return value

def validate_username(self, value):
user_qs = User.objects.filter(username=value)
if user_qs.exists():
raise serializers.ValidationError("We cannot register you because there's a user with that username already.")
return value

def validate_password(self, value):
if re.match("^[a-zA-Z0-9_]+$", value) is not None :
raise serializers.ValidationError("Please include at least a number and any of these symbols in your password @,#,!,$,%,&,*,(,) ")
return value

def create(self, validated_data):
# Use the `create_user` method we wrote earlier to create a new user.
return User.objects.create_user(**validated_data)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<html>

<body>
<a href='http://{{ request.get_host }}/{{link}}/{{token}}'>Click here to reset your password!</a>
</body>

</html>
52 changes: 52 additions & 0 deletions authors/apps/authentication/tests/test_password_reset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from django.test import TestCase, RequestFactory
import json
import jwt
from authors.apps.authentication.views import SendPasswordResetEmailAPIView, ResetPasswordAPIView, RegistrationAPIView
from minimock import Mock
import smtplib


class ResetPasswordTestCase(TestCase):
def setUp(self):
self.factory = RequestFactory()
self.user = {
"user": {
"email": "test@gmail.com",
"username": "tester",
"password": "testpass@word"
}
}
self.email_dict = {'email': 'teste@gmail.com'}
self.password_dict = {'password': 'password1234567#',
'retyped_password': 'password1234567#'}

smtplib.SMTP = Mock('smtplib.SMTP')
smtplib.SMTP.mock_returns = Mock('smtp_connection')

self.request = self.factory.post(
'users/password/forgot/', data=json.dumps(self.email_dict), content_type='application/json')
self.response = SendPasswordResetEmailAPIView.as_view()(self.request)

"""create user in database"""
self.reg_request = self.factory.post(
'/api/users/', data=json.dumps(self.user), content_type='application/json')
self.reg_response = RegistrationAPIView.as_view()(self.reg_request)
self.kwargs = {'token': self.reg_response.data['token']}

def test_send_reset_mail(self):
self.assertEqual(self.response.status_code, 200)

def test_reset_password(self):
self.request = self.factory.put(
'users/password/reset/', data=json.dumps(self.password_dict), content_type='application/json')
self.response = ResetPasswordAPIView.as_view()(self.request, **self.kwargs)
self.assertEqual(self.response.status_code, 201)

def test_mismatched_password(self):
password_dict = {'password': 'password12345687#',
'retyped_password': '1234567#password'}
with self.assertRaises(Exception) as context:
self.request = self.factory.put(
'users/password/reset/', data=json.dumps(password_dict), content_type='application/json')
self.response = ResetPasswordAPIView.as_view()(self.request, **self.kwargs)
self.assertIn('Passwords do not match!', str(context.exception))
2 changes: 1 addition & 1 deletion authors/apps/authentication/tests/test_verification.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def setUp(self):
self.factory = RequestFactory()
self.user = {
"user": {
"email": "phillip.seryazi@andela.com",
"email": "test@gmail.com",
"username": "tester",
"password": "testpass@word"
}
Expand Down
6 changes: 5 additions & 1 deletion authors/apps/authentication/urls.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django.urls import path

from .views import (
LoginAPIView, RegistrationAPIView, UserRetrieveUpdateAPIView, VerificationAPIView
LoginAPIView, RegistrationAPIView, UserRetrieveUpdateAPIView, VerificationAPIView, SendPasswordResetEmailAPIView, ResetPasswordAPIView
)

app_name = "authentication"
Expand All @@ -11,4 +11,8 @@
path('users/', RegistrationAPIView.as_view()),
path('users/login/', LoginAPIView.as_view()),
path('users/verify/<token>', VerificationAPIView.as_view(), name='verification'),
path('users/password/forgot/',
SendPasswordResetEmailAPIView.as_view(), name='forgot_password'),
path('users/password/reset/<token>',
ResetPasswordAPIView.as_view(), name='reset_password'),
]
38 changes: 37 additions & 1 deletion authors/apps/authentication/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from datetime import datetime, timedelta

from authors.settings.base import SECRET_KEY
from .models import User
import jwt
import os
from django.contrib.auth.hashers import make_password

from .renderers import UserJSONRenderer
from .serializers import (
LoginSerializer, RegistrationSerializer, UserSerializer
LoginSerializer, RegistrationSerializer, UserSerializer,
)
from authors.apps.authentication.email_reg_util import SendAuthEmail
from authors.apps.authentication.reset_password_util import ResetPasswordUtil


class RegistrationAPIView(APIView):
Expand Down Expand Up @@ -99,3 +102,36 @@ def update(self, request, *args, **kwargs):
'is_verified': user[0]['is_verified']}

return Response(user_dict, status=status.HTTP_200_OK)


class SendPasswordResetEmailAPIView(APIView):
def post(self, request):
email = request.data.get('email')
dt = datetime.now()+timedelta(days=1)
token = jwt.encode({'email': email, 'exp': int(
dt.strftime('%s'))}, SECRET_KEY, 'HS256')
email_obj = ResetPasswordUtil()
email_obj.send_mail(request, os.environ.get(
'EMAIL_HOST_USER'), email, token)
return Response({'message': 'a link has been sent to your email.', 'token': token}, status=status.HTTP_200_OK)


class ResetPasswordAPIView(UpdateAPIView):
permission_classes = (AllowAny,)
look_url_kwarg = 'token'

def update(self, request, *args, **kwargs):
token = self.kwargs.get(self.look_url_kwarg)

password = request.data.get('password')
retyped_password = request.data.get('retyped_password')

if password != retyped_password:
raise Exception('Passwords do not match!')

decoded_token = jwt.decode(token, SECRET_KEY, 'HS256')
user = User.objects.get(email=decoded_token['email'])
user.set_password(retyped_password)
user.save()

return Response({'message': 'your password has been changed.'}, status=status.HTTP_201_CREATED)

0 comments on commit 97040cd

Please sign in to comment.