-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from snnbotchway/feature/authentication
Redo authentication
- Loading branch information
Showing
18 changed files
with
286 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
"""The account app.""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
"""Account app configuration.""" | ||
from django.apps import AppConfig | ||
|
||
|
||
class AccountConfig(AppConfig): | ||
"""Account app config class.""" | ||
|
||
default_auto_field = "django.db.models.BigAutoField" | ||
name = "account" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
"""Account app models.""" | ||
from typing import List | ||
|
||
from django.contrib.auth.base_user import BaseUserManager | ||
from django.contrib.auth.models import AbstractUser | ||
from django.db import models | ||
from django.utils.translation import gettext_lazy as _ | ||
|
||
|
||
class UserManager(BaseUserManager): | ||
"""Custom user manager.""" | ||
|
||
use_in_migrations = True | ||
|
||
def _create_user(self, email, password, **extra_fields): | ||
"""Create and return a user.""" | ||
if not email: | ||
raise ValueError("Users require an email field") | ||
email = self.normalize_email(email) | ||
user = self.model(email=email, **extra_fields) | ||
user.set_password(password) | ||
user.save(using=self._db) | ||
return user | ||
|
||
def create_user(self, email, password=None, **extra_fields): | ||
"""Set defaults and call _create_user.""" | ||
extra_fields.setdefault("is_staff", False) | ||
extra_fields.setdefault("is_superuser", False) | ||
return self._create_user(email, password, **extra_fields) | ||
|
||
def create_superuser(self, email, password, **extra_fields): | ||
"""Create and return a superuser.""" | ||
extra_fields.setdefault("is_staff", True) | ||
extra_fields.setdefault("is_superuser", True) | ||
|
||
if extra_fields.get("is_staff") is not True: | ||
raise ValueError("Superuser must have is_staff=True.") | ||
if extra_fields.get("is_superuser") is not True: | ||
raise ValueError("Superuser must have is_superuser=True.") | ||
|
||
return self._create_user(email, password, **extra_fields) | ||
|
||
|
||
class User(AbstractUser): | ||
"""Custom user model.""" | ||
|
||
username = None | ||
first_name = None | ||
last_name = None | ||
name = models.CharField(max_length=100) | ||
email = models.EmailField(_("email address"), unique=True) | ||
|
||
objects = UserManager() | ||
|
||
USERNAME_FIELD = "email" | ||
REQUIRED_FIELDS: List[str] = [] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
"""Serializers for the account app views.""" | ||
|
||
from django.contrib.auth import authenticate | ||
from django.utils.translation import gettext_lazy as _ | ||
from rest_framework import serializers | ||
|
||
|
||
class UserTokenSerializer(serializers.Serializer): | ||
"""Serializer for authentication token.""" | ||
|
||
email = serializers.EmailField(label=_("Email"), write_only=True) | ||
password = serializers.CharField( | ||
label=_("Password"), | ||
style={"input_type": "password"}, | ||
trim_whitespace=False, | ||
write_only=True, | ||
) | ||
token = serializers.CharField(label=_("Token"), read_only=True) | ||
|
||
def validate(self, attrs): | ||
"""Validate email and password.""" | ||
email = attrs.get("email") | ||
password = attrs.get("password") | ||
|
||
if email and password: | ||
user = authenticate( | ||
request=self.context.get("request"), email=email, password=password | ||
) | ||
if not user: | ||
msg = _("Unable to log in with provided credentials.") | ||
raise serializers.ValidationError(msg, code="authorization") | ||
else: | ||
msg = _('Must include "email" and "password".') | ||
raise serializers.ValidationError(msg, code="authorization") | ||
|
||
attrs["user"] = user | ||
return attrs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
"""Tests for the account app.""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
"""Common fixtures for the account app tests.""" | ||
import pytest | ||
from django.contrib.auth import get_user_model | ||
|
||
User = get_user_model() | ||
|
||
|
||
@pytest.fixture | ||
def user_payload(): | ||
"""Return sample user information as a payload.""" | ||
return { | ||
"email": "user@example.com", | ||
"password": "test_pass123", | ||
"name": "Test User", | ||
} | ||
|
||
|
||
@pytest.fixture | ||
def sample_user(user_payload): | ||
"""Create and return a sample user.""" | ||
return User.objects.create_user(**user_payload) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import pytest | ||
from django.urls import reverse | ||
from rest_framework import status | ||
|
||
ACCOUNT_LOGIN_URL = reverse("account:login") | ||
|
||
|
||
@pytest.mark.django_db | ||
class TestUserToken: | ||
def test_generate_token_with_valid_credentials_returns_200( | ||
self, sample_user, api_client | ||
): | ||
"""Test a token is returned with valid credentials""" | ||
payload = { | ||
"email": sample_user.email, | ||
"password": "test_pass123", | ||
} | ||
|
||
response = api_client.post(ACCOUNT_LOGIN_URL, payload) | ||
|
||
assert response.status_code == status.HTTP_200_OK | ||
assert "token" in response.data | ||
|
||
def test_generate_token_with_invalid_email_returns_400( | ||
self, sample_user, api_client | ||
): | ||
"""Test a token is not returned with invalid email""" | ||
payload = { | ||
"email": "other@example.com", | ||
"password": "testPass123", | ||
} | ||
|
||
response = api_client.post(ACCOUNT_LOGIN_URL, payload) | ||
|
||
assert response.status_code == status.HTTP_400_BAD_REQUEST | ||
assert "token" not in response.data | ||
|
||
def test_generate_token_with_invalid_password_returns_400( | ||
self, sample_user, api_client | ||
): | ||
"""Test a token is not returned with invalid password""" | ||
payload = { | ||
"email": sample_user.email, | ||
"password": "badPass123", | ||
} | ||
|
||
response = api_client.post(ACCOUNT_LOGIN_URL, payload) | ||
|
||
assert response.status_code == status.HTTP_400_BAD_REQUEST | ||
assert "token" not in response.data | ||
|
||
def test_generate_token_without_password_returns_400(self, api_client, sample_user): | ||
"""Test a token is not returned with blank password""" | ||
payload = { | ||
"email": "user@example.com", | ||
"password": "", | ||
} | ||
|
||
response = api_client.post(ACCOUNT_LOGIN_URL, payload) | ||
|
||
assert response.status_code == status.HTTP_400_BAD_REQUEST | ||
assert "token" not in response.data |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import pytest | ||
from django.contrib.auth import get_user_model | ||
|
||
User = get_user_model() | ||
|
||
|
||
@pytest.mark.django_db | ||
class TestUserManager: | ||
def test_create_user(self, user_payload): | ||
user = User.objects.create_user(**user_payload) | ||
assert user.email == user_payload.get("email") | ||
assert user.name == user_payload.get("name") | ||
assert user.check_password(user_payload.get("password")) | ||
assert not user.is_staff | ||
assert not user.is_superuser | ||
|
||
def test_create_user_missing_email(self): | ||
with pytest.raises(ValueError) as excinfo: | ||
User.objects.create_user(email=None, password="password") | ||
assert str(excinfo.value) == "Users require an email field" | ||
|
||
def test_create_superuser(self): | ||
email = "testsuperuser@example.com" | ||
password = "password" | ||
user = User.objects.create_superuser(email=email, password=password) | ||
assert user.email == email | ||
assert user.is_staff | ||
assert user.is_superuser | ||
|
||
def test_create_superuser_missing_is_staff(self, user_payload): | ||
with pytest.raises(ValueError) as excinfo: | ||
User.objects.create_superuser(**user_payload, is_staff=False) | ||
assert str(excinfo.value) == "Superuser must have is_staff=True." | ||
|
||
def test_create_superuser_missing_is_superuser(self, user_payload): | ||
with pytest.raises(ValueError) as excinfo: | ||
User.objects.create_superuser(**user_payload, is_superuser=False) | ||
assert str(excinfo.value) == "Superuser must have is_superuser=True." |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
"""Account app urls.""" | ||
from django.urls import path | ||
|
||
from .views import CreateUserTokenView | ||
|
||
app_name = "account" | ||
|
||
urlpatterns = [ | ||
path("login/", CreateUserTokenView.as_view(), name="login"), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
"""Account app views.""" | ||
from django.contrib.auth import get_user_model | ||
from rest_framework.authtoken.views import ObtainAuthToken | ||
from rest_framework.settings import api_settings | ||
|
||
from .serializers import UserTokenSerializer | ||
|
||
User = get_user_model() | ||
|
||
|
||
class CreateUserTokenView(ObtainAuthToken): | ||
"""Get token for valid user email and password.""" | ||
|
||
serializer_class = UserTokenSerializer | ||
|
||
# To get the browsable API; | ||
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
"""Global project fixtures.""" | ||
import pytest | ||
from rest_framework.test import APIClient | ||
|
||
|
||
@pytest.fixture | ||
def api_client(): | ||
"""Return API client.""" | ||
return APIClient() |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.