Skip to content

Commit

Permalink
Merge 0b9efe2 into 1cbad8f
Browse files Browse the repository at this point in the history
  • Loading branch information
fabzer0 committed Dec 8, 2018
2 parents 1cbad8f + 0b9efe2 commit 44380d7
Show file tree
Hide file tree
Showing 17 changed files with 256 additions and 36 deletions.
19 changes: 18 additions & 1 deletion authors/apps/authentication/backends.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import datetime
import logging

import jwt
from django.conf import settings

from django.contrib.auth import get_user_model
from rest_framework import exceptions
from rest_framework.authentication import TokenAuthentication

from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.utils import six

"""Configure JWT Here"""


# Get an instance of a logger
logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -61,3 +67,14 @@ def generate_reset_token(email):
settings.SECRET_KEY,
algorithm='HS256').decode()
return token


class TokenGenerator(PasswordResetTokenGenerator):
def _generate_hash_value(self, user, timestamp):
return (
six.text_type(user.email) + six.text_type(timestamp) +
six.text_type(user.is_active)
)


account_activation_token = TokenGenerator()
31 changes: 31 additions & 0 deletions authors/apps/authentication/confirmation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import os

from django.contrib.sites.shortcuts import get_current_site
from django.utils.http import urlsafe_base64_encode
from django.core.mail import EmailMessage
from django.utils.encoding import force_bytes

from .models import User
from .backends import account_activation_token


def send_confirmation_email(credentials, request):
"""
This is a function that sends the EmailMessage to the user
embedded with tokens and user id
"""
registered_user = User.objects.get(email=credentials.get('email'))

# send an email to the user with the token
mail_subject = 'Activate your account.'
current_site = get_current_site(request)
uid = urlsafe_base64_encode(force_bytes(registered_user.pk)).decode()
token = account_activation_token.make_token(registered_user)
protocol = os.getenv('PROTOCOL')
activation_link = protocol + "{0}/api/users/activate/{1}/{2}".format(
current_site, uid, token)
message = "Hello {0},\n {1}".format(
registered_user.username, activation_link)
to_email = credentials.get('email')
email = EmailMessage(mail_subject, message, to=[to_email])
email.send()
4 changes: 2 additions & 2 deletions authors/apps/authentication/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 2.1.3 on 2018-11-28 03:50
# Generated by Django 2.1.4 on 2018-12-07 18:23

from django.db import migrations, models

Expand All @@ -21,7 +21,7 @@ class Migration(migrations.Migration):
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(db_index=True, max_length=255, unique=True)),
('email', models.EmailField(db_index=True, max_length=254, unique=True)),
('is_active', models.BooleanField(default=True)),
('is_active', models.BooleanField(default=False)),
('is_staff', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
Expand Down
Empty file modified authors/apps/authentication/migrations/__init__.py
100755 → 100644
Empty file.
2 changes: 1 addition & 1 deletion authors/apps/authentication/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class User(AbstractBaseUser, PermissionsMixin):
# will simply offer users a way to deactivate their account instead of
# letting them delete it. That way they won't show up on the site anymore,
# but we can still analyze the data.
is_active = models.BooleanField(default=True)
is_active = models.BooleanField(default=False)

# The `is_staff` flag is expected by Django to determine who can and cannot
# log into the Django admin site. For most users, this flag will always be
Expand Down
12 changes: 8 additions & 4 deletions authors/apps/authentication/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,17 @@ def get_token(self, obj):
:return token:
"""
return JWTAuthentication.generate_token(
user=obj, is_refresh_token=False)
user=obj,
is_refresh_token=False)

def get_refresh_token(self, obj):
"""
Generate a refresh token
:return refresh token:
"""
return JWTAuthentication.generate_token(
user=obj, is_refresh_token=True)
user=obj,
is_refresh_token=True)


class LoginSerializer(serializers.ModelSerializer):
Expand All @@ -118,15 +120,17 @@ def get_token(self, obj):
:return token:
"""
return JWTAuthentication.generate_token(
user=obj, is_refresh_token=True)
user=obj,
is_refresh_token=True)

def get_refresh_token(self, obj):
"""
fetch and return refresh token
:return:
"""
return JWTAuthentication.generate_token(
user=obj, is_refresh_token=False)
user=obj,
is_refresh_token=False)

def validate(self, data):
# The `validate` method is where we make sure that the current
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class Meta:
username = factory.Sequence(lambda n: 'test_user%s' % n)
email = factory.LazyAttribute(lambda o: '%s@email.com' % o.username)
password = factory.Faker('password')
is_active = True


class ProfileFactory(factory.DjangoModelFactory):
Expand Down
79 changes: 79 additions & 0 deletions authors/apps/authentication/tests/test_account_activation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from django.test import TestCase
from django.urls import reverse
from django.core import mail
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode

from rest_framework.test import (
APIClient, APIRequestFactory
)
from rest_framework import status

from ..models import User
from ..backends import account_activation_token


class AccountActivationTestCase(TestCase):
"""
This class defines tests for user account activation view
"""

def setUp(self):
self.client = APIClient()
self.factory = APIRequestFactory()

self.user = {
'user': {
'username': 'fabish',
'email': 'fabish.olasi@andela.com',
'password': 'secretsantaS#3'
}
}

self.user_2 = {
'user': {
'username': 'jimmy',
'email': 'jimmy@example.com',
'password': 'secretsantaS#3'
}
}

def sign_user(self, user_data):
response = self.client.post(
reverse('register'),
user_data, format='json')
return response

def test_email_sent_to_new_user(self):

self.client.post(
'/api/users/register',
self.user,
format='json'
)
self.assertEqual(len(mail.outbox), 1)
msg = mail.outbox[0]
self.assertEqual(msg.subject, 'Activate your account.')

def test_email_not_sent_if_invalid(self):
self.client.post(
'api/users/register',
self.user_2,
format='json'
)
self.assertEqual(len(mail.outbox), 0)

def test_link_activation_successful(self):
self.sign_user(self.user)
details = User.objects.get(username=self.user['user']['username'])
pk = urlsafe_base64_encode(force_bytes(details.id)).decode()
token = account_activation_token.make_token(details)
protocol = 'http://'
path = 'api/users/activate/'
url = '{0}localhost:8000/{1}{pk}/{token}'.format(
protocol,
path,
pk=pk,
token=token)
response = self.client.get(url, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
3 changes: 3 additions & 0 deletions authors/apps/authentication/tests/test_authors.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ def register_user(
password="avengersassemble"):
"""Creating a test user"""
user = User.objects.create_user(username, email, password)
thisuser = User.objects.get(email='thanos@avengers.com')
thisuser.is_active = True
thisuser.save()
return user


Expand Down
31 changes: 25 additions & 6 deletions authors/apps/authentication/tests/test_login.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import pytest

from rest_framework.reverse import reverse

from authors.apps.authentication.models import User
from authors.apps.authentication.tests.factories.authentication import \
UserFactory
from authors.apps.authentication.tests.factories import authentication


@pytest.mark.django_db
class TestLogin:
"""
Test cases for user login
"""
test_user = UserFactory.create()
test_user = authentication.UserFactory.create()
user = {
"user": {
"username": test_user.username,
Expand All @@ -20,10 +20,29 @@ class TestLogin:
}
}

def test_login_endpoint(self, test_client):
def test_login_endpoint_with_inactive_account(self, test_client):
"""
This tests user cannot login if the account is not acttivated
"""
User.objects.create_user(**self.user['user'])
assert User.objects.count() > 0

response = test_client.post(
reverse('login'),
data=self.user, format='json')
assert response.status_code == 400

def test_login_endpoint_with_active_account(self, test_client):
"""
This tests user cannot login if the account is not acttivated
"""
User.objects.create_user(**self.user['user'])
user = User.objects.get(email=self.user['user']['email'])
user.is_active = True
user.save()
assert User.objects.count() > 0

response = test_client.post(reverse('login'), data=self.user,
format='json')
response = test_client.post(
reverse('login'),
data=self.user, format='json')
assert response.status_code == 200
4 changes: 2 additions & 2 deletions authors/apps/authentication/tests/test_registration.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import random
import string

import pytest

from faker import Factory
from rest_framework import status
from rest_framework.reverse import reverse
Expand Down Expand Up @@ -34,7 +34,7 @@ def test_user_instance(self):
def test_create_super_user(self):
"""Test super user can be created"""
su = User.objects.create_superuser(**self.user['user'])
assert su.is_active is True
assert su.is_active is False
assert su.is_staff is True
assert su.is_superuser is True

Expand Down
9 changes: 8 additions & 1 deletion authors/apps/authentication/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
UserRetrieveUpdateAPIView,
ResetPasswordRequestAPIView,
ResetPasswordConfirmAPIView,
ListUsersAPIView)
ListUsersAPIView,
ActivateAPIView
)


urlpatterns = [
Expand All @@ -33,4 +35,9 @@
'users/reset_password_confirm/<str:token>',
ResetPasswordConfirmAPIView.as_view(),
name="reset_password_confirm"),
path(
'users/activate/<uidb64>/<token>',
ActivateAPIView.as_view(),
name='activate'),

]
Loading

0 comments on commit 44380d7

Please sign in to comment.