Skip to content

Commit

Permalink
Merge pull request #4 from punkyoon/feature/add-auth-endpoint
Browse files Browse the repository at this point in the history
feature/add-auth-endpoint
  • Loading branch information
punkyoon committed May 26, 2019
2 parents 165f6a2 + 2f0c40b commit e139ab2
Show file tree
Hide file tree
Showing 12 changed files with 152 additions and 34 deletions.
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

0 comments on commit e139ab2

Please sign in to comment.