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

Dj rest auth #187

Merged
merged 30 commits into from
Sep 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
dcbab36
Added osprojects to admin interface.
BethanyG Sep 7, 2020
31edf0f
Added osprojects to admin interface.
BethanyG Sep 7, 2020
d585cdd
Added OSProjects to admin interface.
BethanyG Sep 7, 2020
606c871
added django-res-auth and associated basic settings to test DRF and a…
BethanyG Sep 8, 2020
30936fe
Working state Mon Sept 14 - dj-rest-auth and django-allauth
BethanyG Sep 14, 2020
cdadba1
Removed django-rest-jwt and django-rest-auth added simplejwt and dj-r…
BethanyG Sep 22, 2020
782e05b
Added routes for registration and allauth.
BethanyG Sep 22, 2020
040c6f3
Added password reset email template.
BethanyG Sep 22, 2020
ef3a5ad
Added confirm_email method. Will probably remove it as uneeded later.
BethanyG Sep 22, 2020
7e6ff72
Added CustomUserDetailsSerializer. Stubbed other serializers for con…
BethanyG Sep 22, 2020
e97153b
Added routes for registration, email confirmation, password reset, lo…
BethanyG Sep 22, 2020
b6c4ac2
Added CustomVerifyEmailView to enable email verify emails from the ba…
BethanyG Sep 22, 2020
f06a74c
Added tests for registration flow, token authorization, email verific…
BethanyG Sep 22, 2020
221b7b6
Fixed tests that were failing due to auth change.
BethanyG Sep 24, 2020
e8197e7
Added final simpleJWT tests for auth.
BethanyG Sep 24, 2020
f220224
Added skips for User tests broken due to auth changes.
BethanyG Sep 24, 2020
bb47eb0
Removed django-rest_auth and django-rest-jwt from requirememts.
BethanyG Sep 24, 2020
9fd6eb7
Removed unused import that was causing an error.
BethanyG Sep 24, 2020
2670780
Removed excess space from email_confirmation_subject.txt filename.
BethanyG Sep 24, 2020
9451986
Revert "Removed excess space from email_confirmation_subject.txt file…
BethanyG Sep 24, 2020
aa6218a
Attempted to fix intermittent email verification post test failure by…
BethanyG Sep 24, 2020
f91a22d
Changed order of included URLs.
BethanyG Sep 24, 2020
a2b2b4b
Added specific path for email link verification post and changed path…
BethanyG Sep 24, 2020
569ee99
Fun trick: If your regex fails to capture all the key generations cas…
BethanyG Sep 24, 2020
35c5610
Removed print statement from email post verificaton test case.
BethanyG Sep 24, 2020
75e9461
Corrected circular reference that was breaking tests and renamed file…
BethanyG Sep 24, 2020
b2ba2c2
Removed regex from path url to avoid djang deprecation and migration …
BethanyG Sep 24, 2020
b127b67
Merge branch 'main' into dj-rest-auth
lpatmo Sep 26, 2020
4a2bfd4
Update project/config/settings/base.py
BethanyG Sep 26, 2020
604bac8
Update project/userauth/tests.py
BethanyG Sep 26, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 45 additions & 22 deletions project/config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,13 @@
"allauth.account",
"allauth.socialaccount",
"rest_framework",
"rest_framework.authtoken",
"corsheaders",
"taggit",
"django_celery_beat",
"taggit_serializer"
"taggit_serializer",
"dj_rest_auth",
"dj_rest_auth.registration",
]

LOCAL_APPS = [
Expand Down Expand Up @@ -287,21 +290,49 @@
# CELERY_TASK_SOFT_TIME_LIMIT = 60
# # http://docs.celeryproject.org/en/latest/userguide/configuration.html#beat-scheduler
# CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler"
# # django-allauth
# # ------------------------------------------------------------------------------
# ACCOUNT_ALLOW_REGISTRATION = env.bool("DJANGO_ACCOUNT_ALLOW_REGISTRATION", True)
# # https://django-allauth.readthedocs.io/en/latest/configuration.html
# ACCOUNT_AUTHENTICATION_METHOD = "username"
# # https://django-allauth.readthedocs.io/en/latest/configuration.html
# ACCOUNT_EMAIL_REQUIRED = True
# # https://django-allauth.readthedocs.io/en/latest/configuration.html
# ACCOUNT_EMAIL_VERIFICATION = "mandatory"
# # https://django-allauth.readthedocs.io/en/latest/configuration.html
# ACCOUNT_ADAPTER = "users.adapters.AccountAdapter"


# # django-allauth config
# # https://django-allauth.readthedocs.io/en/latest/configuration.html
# # ------------------------------------------------------------------------------
ACCOUNT_ADAPTER = "userauth.adapter.CustomAccountAdapter"
CUSTOM_ACCOUNT_CONFIRM_EMAIL_URL = "verify-email/?key={0}"
CUSTOM_ACCOUNT_PASSWORD_RESET_CONFIRM_URL = "password/reset/<uidb64>/<token>/"
#URL_FRONT = "http://localhost:8000/"
#ACCOUNT_ADAPTER = "users.adapters.AccountAdapter"
ACCOUNT_ALLOW_REGISTRATION = env.bool("DJANGO_ACCOUNT_ALLOW_REGISTRATION", True)
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
ACCOUNT_AUTHENTICATION_METHOD = "username_email"
ACCOUNT_CONFIRM_EMAIL_ON_GET = False
#ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_EMAIL_CONFIRMATION_AUTHENTICATED_REDIRECT_URL = None
#ACCOUNT_EMAIL_CONFIRMATION_ANONYMOUS_REDIRECT_URL = reverse_lazy('account_confirm_complete')
ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = 3
ACCOUNT_EMAIL_CONFIRMATION_HMAC = True
ACCOUNT_EMAIL_SUBJECT_PREFIX = "CodeBuddies: "
ACCOUNT_DEFAULT_HTTP_PROTOCOL = "http"
ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = True
ACCOUNT_LOGOUT_ON_GET = False

# SOCIALACCOUNT_ADAPTER = "users.adapters.SocialAccountAdapter"


# #dj-rest-auth config
# #https://dj-rest-auth.readthedocs.io/en/latest/configuration.html
# # ---------------------------------------------------------------------------------
REST_USE_JWT = True
JWT_AUTH_COOKIE = 'cb-auth'
OLD_PASSWORD_FIELD_ENABLED = True
LOGOUT_ON_PASSWORD_CHANGE = True

REST_AUTH_SERIALIZERS = {
'USER_DETAILS_SERIALIZER': 'userauth.serializers.CustomUserDetailSerializer',
'PASSWORD_RESET_SERIALIZER' : 'userauth.serializers.CustomPasswordResetSerializer',
'PASSWORD_RESET_CONFIRM_SERIALIZER': 'userauth.serializers.CustomPasswordResetConfirmSerializer',
}


REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],

Expand All @@ -320,22 +351,14 @@
'rest_framework.renderers.BrowsableAPIRenderer',
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework_simplejwt.authentication.JWTAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication', ],

'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,
}


JWT_AUTH = {
'JWT_AUTH_HEADER_PREFIX': 'Bearer',
'JWT_RESPONSE_PAYLOAD_HANDLER': 'core.utils.my_jwt_response_handler',
'JWT_ALLOW_REFRESH': True,
'JWT_EXPIRATION_DELTA': timedelta(hours=1),
'JWT_REFRESH_EXPIRATION_DELTA': timedelta(days=3),
}

CORS_ORIGIN_WHITELIST = (
'https://127.0.0.1:3000',
'http://localhost:3000',
Expand Down
26 changes: 19 additions & 7 deletions project/config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,35 @@
from django.contrib import admin
from django.views.generic import TemplateView
from django.views import defaults as default_views
from rest_framework.exceptions import server_error
from rest_framework import routers, serializers, viewsets
from resources.urls import router as resources_router
from userauth.views import CustomVerifyEmailView
from userauth.urls import router as userauth_router

router = routers.DefaultRouter()
router.registry.extend(resources_router.registry)
router.registry.extend(userauth_router.registry)

urlpatterns = [
path("", TemplateView.as_view(template_name="pages/home.html"), name="home"),
path(
"about/", TemplateView.as_view(template_name="pages/about.html"), name="about"
),
path("about/", TemplateView.as_view(template_name="pages/about.html"), name="about"),

# Django Admin, use {% url 'admin:index' %}
path(settings.ADMIN_URL, admin.site.urls),

# User management
#currently an unused endpoint, but can be used if needed for extended user profiles, etc.
path("users/", include("users.urls", namespace="users")),
path("accounts/", include("allauth.urls")),

# Your stuff: custom urls includes go here
path('api/v1/', include('resources.urls')),
path('auth/', include('userauth.urls', namespace="userauth")),
#this is a route for logging into the "browsable api" if not needed for testing, it should be omitted.
path('api/v1/', include('rest_framework.urls', namespace='rest_framework')),
path('api/v1/auth/', include(('userauth.urls', 'userauth'), namespace="userauth")),
path('api/v1/', include('resources.urls', namespace='resources')),

#we have to include these for registration email validation, but otherwise these paths are NOT used
path("accounts/", include("allauth.urls")),


] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{% load account %}{% user_display user as user_display %}{% load i18n %}{% autoescape off %}{% blocktrans with site_name=current_site.name site_domain=current_site.domain %}Hello from {{ site_name }}!

You're receiving this e-mail because user {{ user_display }} has used this e-mail address to register an account on {{ site_domain }}.
BethanyG marked this conversation as resolved.
Show resolved Hide resolved

To confirm this is correct, go to {{ activate_url }}
{% endblocktrans %}
{% blocktrans with site_name=current_site.name site_domain=current_site.domain %}Thank you from {{ site_name }}!
{{ site_domain }}{% endblocktrans %}
{% endautoescape %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{% load i18n %}
{% autoescape off %}
{% blocktrans %}Please Confirm Your E-mail Address{% endblocktrans %}
{% endautoescape %}
14 changes: 14 additions & 0 deletions project/core/templates/registration/password_reset_email.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{% load i18n %}{% autoescape off %}
2 {% blocktrans %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktrans %}
3
4 {% trans "Please go to the following page and choose a new password:" %}
5 {% block reset_link %}
6 {{ protocol }}://{{ domain }}{% url 'userauth:password_reset_confirm' uidb64=uid token=token %}
7 {% endblock %}
8 {% trans "Your username, in case you've forgotten:" %} {{ user.get_username }}
9
10 {% trans "Thanks for using our site!" %}
11
12 {% blocktrans %}The {{ site_name }} team{% endblocktrans %}
13
14 {% endautoescape %}
14 changes: 14 additions & 0 deletions project/osprojects/admin.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
from django.contrib import admin
from .models import OSProjects


# Register your models here.
class OSProjectAdmin(admin.ModelAdmin):

list_display = ['tag_list']

def get_queryset(self, request):
return super().get_queryset(request).prefetch_related('tags')

def tag_list(self, obj):
return u", ".join(o.name for o in obj.tags.all())


admin.site.register(OSProjects)
4 changes: 3 additions & 1 deletion project/requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,6 @@ django-taggit==1.2.0 # https://github.com/jazzband/django-taggit
djangorestframework==3.10.2 # https://github.com/encode/django-rest-framework
coreapi==2.3.3 # https://github.com/core-api/python-client
django_taggit_serializer==0.1.7 #https://github.com/glemmaPaul/django-taggit-serializer
drf-jwt==1.13.4 # https://github.com/Styria-Digital/django-rest-framework-jwt
djangorestframework-simplejwt #https://github.com/SimpleJWT/django-rest-framework-simplejwt
dj-rest-auth==1.1.1 #https://github.com/jazzband/dj-rest-auth
django-rest-authtoken==2.1.3 #https://pypi.org/project/django-rest-authtoken/
9 changes: 2 additions & 7 deletions project/resources/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,14 @@
class MediaTypeSerializerField(serializers.ChoiceField):

def to_representation(self, value):

valid_media_types = ', '.join(item for item in self.choices)

if not value:
return ''

else:
try:
media_type = self.choices[value]

except KeyError as err:
raise KeyError(f'Invalid media type. The media type should be one of the following: {valid_media_types}') from err

except KeyError:
raise serializers.ValidationError( f'Invalid media type. The media type should be one of the following: {valid_media_types}')
return media_type

def to_internal_value(self, value):
Expand Down
58 changes: 28 additions & 30 deletions project/resources/tests.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
from unittest import skip
from pytest import raises
from random import randint
from rest_framework import status
from rest_framework.test import APITestCase
from rest_framework_jwt.settings import api_settings
from rest_framework import status, serializers
from rest_framework.test import APITestCase, URLPatternsTestCase
from users.factories import UserFactory
from resources.factories import ResourceFactory
from factory import PostGenerationMethodCall, LazyAttribute, create, create_batch


jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER


class PublicResourcesTests(APITestCase):
# Viewing resources, viewing a single resource, and search don't require user authentication

Expand Down Expand Up @@ -73,13 +66,16 @@ def test_create_a_resource(self):
class AuthedResourcesTests(APITestCase):

def setUp(self):
self.user = UserFactory(password=PostGenerationMethodCall('set_password', 'codebuddies'))
token_uri = '/api/v1/auth/token/'
user_to_auth = UserFactory(password=PostGenerationMethodCall('set_password', 'codebuddies'))
user_auth_data = {
"username": user_to_auth.username,
"password": 'codebuddies'
}

url = '/auth/obtain_token/'
data = {"username": self.user.username, "password": "codebuddies"}
token_response = self.client.post(url, data, format='json')
JWT_user_tokens = self.client.post(token_uri, user_auth_data, format='json')

self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + token_response.data['token'])
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + JWT_user_tokens.data['access'])

def test_patch_one_resource(self):
new_resource = create(ResourceFactory)
Expand Down Expand Up @@ -189,20 +185,22 @@ def test_create_one_resource_without_media_type(self):
self.assertEqual(response.data['media_type'], '')

def test_create_one_resource_with_invalid_media_type(self):
with raises(KeyError, match=r"The media type should be one of the following:"):
url = '/api/v1/resources/'
data = {"title": "The Best Medium-Hard Data Analyst SQL Interview Questions",
"author": "Zachary Thomas",
"description": "The first 70% of SQL is pretty straightforward but the remaining 30% can be pretty tricky. These are good practice problems for that tricky 30% part.",
"url": "https://quip.com/2gwZArKuWk7W",
"referring_url": "https://quip.com",
"other_referring_source": "twitter.com/lpnotes",
"date_published": "2020-04-19T03:27:06Z",
"created": "2020-05-02T03:27:06.485Z",
"modified": "2020-05-02T03:27:06.485Z",
"media_type": "DOP",
"tags": ["SQLt", "BackEnd", "Databases"]
}

response = self.client.post(url, data, format='json')
url = '/api/v1/resources/'
data = {"title": "The Best Medium-Hard Data Analyst SQL Interview Questions",
"author": "Zachary Thomas",
"description": "The first 70% of SQL is pretty straightforward but the remaining 30% can be pretty tricky. These are good practice problems for that tricky 30% part.",
"url": "https://quip.com/2gwZArKuWk7W",
"referring_url": "https://quip.com",
"other_referring_source": "twitter.com/lpnotes",
"date_published": "2020-04-19T03:27:06Z",
"created": "2020-05-02T03:27:06.485Z",
"modified": "2020-05-02T03:27:06.485Z",
"media_type": "DOP",
"tags": ["SQLt", "BackEnd", "Databases"]
}

response = self.client.post(url, data, format='json')

self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(response.data[0], "Invalid media type. The media type should be one of the following: VID, POD, PODEP, TALK, TUTOR, COURSE, BOOK, BLOG, GAME, EVENT, TOOL, LIB, WEB")

4 changes: 2 additions & 2 deletions project/resources/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
from rest_framework import routers
from . import views

router = routers.DefaultRouter()
app_name = 'resources'
router = routers.SimpleRouter()
router.register(r'resources', views.ResourceView, basename='resources')

urlpatterns = [
path('', include(router.urls)),
path('resource/', include('rest_framework.urls', namespace='rest_framework')),
]
19 changes: 19 additions & 0 deletions project/userauth/adapter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from allauth.account.adapter import DefaultAccountAdapter
from django.contrib.sites.models import Site
from allauth.utils import build_absolute_uri
from django.conf import settings

class CustomAccountAdapter(DefaultAccountAdapter):

def get_email_confirmation_url(self, request, emailconfirmation):
url = settings.CUSTOM_ACCOUNT_CONFIRM_EMAIL_URL.format(emailconfirmation.key)
result = build_absolute_uri(request, url)
return result

def confirm_email(self, request, email_address):
"""
Marks the email address as confirmed on the db
"""
email_address.verified = True
email_address.set_as_primary(conditional=True)
email_address.save()
Loading