Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/add-auth-endpoint #4

Merged
merged 2 commits into from
May 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 16 additions & 0 deletions accounts/backends.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from django.contrib.auth.backends import ModelBackend

from accounts.models import MockUser


class EmailAuthenticationBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
try:
user = MockUser.objects.get(email=username)
except MockUser.DoesNotExist:
return None
else:
if user.check_password(password):
return user

return None
8 changes: 8 additions & 0 deletions accounts/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from rest_framework import status
from rest_framework.exceptions import APIException


class ProfileDoesNotExist(APIException):
status_code = status.HTTP_400_BAD_REQUEST
default_detail = 'User profile does not exist'
default_code = '400_PROFILE_DOES_NOT_EXIST'
2 changes: 0 additions & 2 deletions accounts/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,3 @@ def create(cls, mock_user, language='', country='', locale=''):
mock_user=mock_user, language=language, country=country, locale=locale
)
return mock_profile

# TODO: add custom authentication backend to login with email
27 changes: 27 additions & 0 deletions accounts/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from django_countries.serializer_fields import CountryField
from rest_framework import serializers
from rest_framework_serializer_extensions.serializers import SerializerExtensionsMixin

from accounts.models import MockUser, MockProfile


class MockUserSerializer(SerializerExtensionsMixin, serializers.ModelSerializer):
language = serializers.CharField(write_only=True, max_length=2)
country = CountryField(write_only=True)
locale = serializers.CharField(write_only=True, max_length=5)

def create(self, validated_data):
# validated_data: email, password, language, country, locale
return MockUser.create(**validated_data)

class Meta:
model = MockUser
fields = ('email', 'password', 'locale', 'country', 'language', )
write_only_fields = ('email', 'password', )


class MockProfileSerializer(SerializerExtensionsMixin, serializers.ModelSerializer):
class Meta:
model = MockProfile
fields = ('nickname', 'locale', 'country', 'language', )
read_only_fields = ('nickname', 'locale', 'country', 'language', )
24 changes: 24 additions & 0 deletions accounts/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from django.urls import path

from rest_auth.views import LogoutView, PasswordChangeView, PasswordResetView, PasswordResetConfirmView
from rest_framework_jwt.views import obtain_jwt_token, refresh_jwt_token, verify_jwt_token

from accounts.views import RegisterMockUserView, FetchMockProfileView


app_name = 'accounts'
urlpatterns = [
path(r'login', obtain_jwt_token, name='login'),
path(r'logout', LogoutView.as_view(), name='logout'),
path('register', RegisterMockUserView.as_view(), name='register'),
path(r'profile', FetchMockProfileView.as_view(), name='profile'),

path(r'password/change', PasswordChangeView.as_view(), name='password_change'),
path(r'password/reset', PasswordResetView.as_view(), name='password_reset'),
path(r'password/reset/confirm', PasswordResetConfirmView.as_view(), name='password_reset_confirm'),

path('token/refresh', refresh_jwt_token),
path('token/verify', verify_jwt_token),
]

# More details: https://django-rest-auth.readthedocs.io/en/latest/api_endpoints.html#basic
26 changes: 24 additions & 2 deletions accounts/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
from django.shortcuts import render
from rest_framework.generics import CreateAPIView, RetrieveAPIView
from rest_framework.permissions import AllowAny, IsAuthenticated

# Create your views here.
from accounts.exceptions import ProfileDoesNotExist
from accounts.models import MockUser, MockProfile
from accounts.serializers import MockUserSerializer, MockProfileSerializer


class RegisterMockUserView(CreateAPIView):
model = MockUser
permission_classes = (AllowAny, )
serializer_class = MockUserSerializer


class FetchMockProfileView(RetrieveAPIView):
permission_classes = (IsAuthenticated, )
serializer_class = MockProfileSerializer

def get_object(self):
try:
mock_profile = MockProfile.objects.get(mock_user=self.request.user)
except MockProfile.DoesNotExist:
raise ProfileDoesNotExist()
else:
return mock_profile
4 changes: 4 additions & 0 deletions mock_server/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import hashids


HASH_IDS = hashids.Hashids(salt='MOCK-SERVER-SALT')
3 changes: 0 additions & 3 deletions mock_server/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,3 @@
class BaseModel(SafeDeleteModel, TimeStampedModel):
class Meta:
abstract = True


# TODO: https://django-rest-framework-serializer-extensions.readthedocs.io/en/latest/usage-hashids/
21 changes: 12 additions & 9 deletions mock_server/settings/settings_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

SECRET_KEY = '&6fbttu7hb^=-v!84htgi=eh$1bc7ov$d-!2fuzmb2sxyktu+!'


DEFAULT_DJANGO_APPS = [
'django.contrib.admin',
'django.contrib.auth',
Expand All @@ -20,8 +19,7 @@
'django.contrib.staticfiles',
]
PROJECT_APPS = ['accounts', ]
EXTERNAL_APPS = ['rest_framework', 'safedelete', ]

EXTERNAL_APPS = ['rest_auth', 'rest_framework', 'rest_framework.authtoken', 'safedelete', ]
INSTALLED_APPS = DEFAULT_DJANGO_APPS + PROJECT_APPS + EXTERNAL_APPS

MIDDLEWARE = [
Expand Down Expand Up @@ -55,6 +53,7 @@
WSGI_APPLICATION = 'mock_server.wsgi.application'

AUTH_USER_MODEL = 'accounts.MockUser'
AUTHENTICATION_BACKENDS = ['accounts.backends.EmailAuthenticationBackend', ]

# Password validation
AUTH_PASSWORD_VALIDATORS = [
Expand All @@ -64,15 +63,19 @@
{'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', },
]

REST_USE_JWT = True
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
]
'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAuthenticated', ),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
'SERIALIZER_EXTENSIONS': {'USE_HASH_IDS': True, 'HASH_IDS_SOURCE': 'mock_server.HASH_IDS'}
}

JWT_AUTH = {'JWT_AUTH_HEADER_PREFIX': 'Bearer', }

# Internationalization
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
Expand Down
5 changes: 1 addition & 4 deletions mock_server/urls.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
from django.contrib import admin
from django.urls import path, include

from rest_framework_simplejwt import views


urlpatterns = [
path('admin/', admin.site.urls),
path('admin-api/', include('rest_framework.urls')),

path('api/token/', views.TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', views.TokenRefreshView.as_view(), name='token_refresh'),
path('accounts/', include('accounts.urls')),
]
47 changes: 34 additions & 13 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ django-safedelete = "^0.5.1"
django-model-utils = "^3.1"
djangorestframework-serializer-extensions = "^1.0"
django-countries = "^5.3"
djangorestframework_simplejwt = "^4.3"
django-rest-auth = "^0.9.5"
djangorestframework-jwt = "^1.11"

[tool.poetry.dev-dependencies]

Expand Down