Skip to content
This repository has been archived by the owner on Aug 6, 2020. It is now read-only.

Commit

Permalink
Merge pull request #25 from ncrmro/dev
Browse files Browse the repository at this point in the history
Auth and Browser testing
  • Loading branch information
ncrmro authored Jan 24, 2017
2 parents 21e0700 + 04e63c2 commit edb560c
Show file tree
Hide file tree
Showing 37 changed files with 653 additions and 1,650 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,5 @@ fabric.properties
/src/staticfiles/
node_modules
/bend/static/
/fend/src/server/data/schema.json
screenshots
3 changes: 2 additions & 1 deletion bend/deps/testing.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
prospector==0.12.4
coverage==4.2
coverage==4.2
selenium==3.0.2
17 changes: 9 additions & 8 deletions bend/src/ango/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
"""

from os.path import dirname, join
import datetime

# Build paths inside the project like this: join(BASE_DIR, "directory")
BASE_DIR = dirname(dirname(dirname(dirname(__file__))))

# Define STATIC_ROOT for the collectstatic command
STATIC_ROOT = join(BASE_DIR, 'static')

STATIC_URL = '/static/'
MEDIA_ROOT = join(BASE_DIR, 'media')
MEDIA_URL = "/media/"
# Application definition

INSTALLED_APPS = [
Expand Down Expand Up @@ -87,19 +90,17 @@

USE_TZ = True

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.10/howto/static-files/

STATIC_URL = '/static/'

GRAPHENE = {
'SCHEMA': 'ango.schema.schema', # Where your Graphene schema lives
'SCHEMA_OUTPUT': 'fend/server/data/schema.json' # defaults to schema.json
'SCHEMA_OUTPUT': 'fend/src/server/data/schema.json' # defaults to schema.json
}

WEBPACK_LOADER = {
'DEFAULT': {
'BUNDLE_DIR_NAME': 'bundles/',
'STATS_FILE': join('static', 'webpack-stats.json'),
'STATS_FILE': join(BASE_DIR, 'static', 'webpack-stats.json'),
}
}
}

JWT_EXPIRATION_DELTA = datetime.timedelta(days=7)
4 changes: 0 additions & 4 deletions bend/src/ango/settings/prod.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@
}


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.9/howto/static-files/
STATIC_ROOT = join(BASE_DIR, 'staticfiles')
STATIC_URL = '/static/'

# Simplified static file serving.
# https://warehouse.python.org/project/whitenoise/
Expand Down
25 changes: 25 additions & 0 deletions bend/src/ango/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from django.test import LiveServerTestCase
from selenium import webdriver
import os


if not os.path.exists('./screenshots'):
os.makedirs('./screenshots')


class HomePageTest(LiveServerTestCase):
def setUp(self):
self.selenium = webdriver.Firefox()
super(HomePageTest, self).setUp()

def tearDown(self):
self.selenium.quit()
super(HomePageTest, self).tearDown()

def test_home_page(self):
selenium = self.selenium
selenium.implicitly_wait(10) # seconds
# Opening the link we want to test
selenium.get(self.live_server_url)
selenium.save_screenshot('./screenshots/ff_landing.png')
assert 'Relay Fullstack' in selenium.page_source
5 changes: 3 additions & 2 deletions bend/src/ango/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@
from django.contrib import admin
from django.views.generic import TemplateView
from graphene_django.views import GraphQLView
from django.views.decorators.csrf import csrf_exempt


urlpatterns = [
url(r'^$', TemplateView.as_view(template_name='index.html'), name='home'),
url(r'^admin/', admin.site.urls),
url(r'^graphql', GraphQLView.as_view(graphiql=True)),
url(r'^.*/$', TemplateView.as_view(template_name='index.html')),
url(r'^graphql', csrf_exempt(GraphQLView.as_view(graphiql=True))),
url(r'^.*$', TemplateView.as_view(template_name='index.html')),
]
65 changes: 61 additions & 4 deletions bend/src/users/jwt_util.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
from jwt_auth.forms import JSONWebTokenForm
from jwt_auth.mixins import JSONWebTokenAuthMixin

jwtMixin = JSONWebTokenAuthMixin.authenticate_header

import jwt

from jwt_auth import settings, exceptions
from jwt_auth.utils import get_authorization_header
from jwt_auth.compat import json, smart_text, User

jwt_decode_handler = settings.JWT_DECODE_HANDLER
jwt_get_user_id_from_payload = settings.JWT_PAYLOAD_GET_USER_ID_HANDLER


def loginUser(username, password):
Expand All @@ -8,8 +20,53 @@ def loginUser(username, password):
if not form.is_valid():
return print("JWT form not valid")

context_dict = {
'token': form.object['token']
}
return form.object['token']


def authenticate(request):
auth = get_authorization_header(request).split()
auth_header_prefix = settings.JWT_AUTH_HEADER_PREFIX.lower()
if not auth or smart_text(auth[0].lower()) != auth_header_prefix:
raise exceptions.AuthenticationFailed()

if len(auth) == 1:
msg = 'Invalid Authorization header. No credentials provided.'
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = ('Invalid Authorization header. Credentials string '
'should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)


try:
payload = jwt_decode_handler(auth[1])
except jwt.ExpiredSignature:
msg = 'Signature has expired.'
print(msg, auth[1])
raise exceptions.AuthenticationFailed(msg)
except jwt.DecodeError:
msg = 'Error decoding signature.'
raise exceptions.AuthenticationFailed(msg)

user = authenticate_credentials(payload)

return (user, auth[1])


def authenticate_credentials(payload):
"""
Returns an active user that matches the payload's user id and email.
"""
try:
user_id = jwt_get_user_id_from_payload(payload)

if user_id:
user = User.objects.get(pk=user_id, is_active=True)
else:
msg = 'Invalid payload'
raise exceptions.AuthenticationFailed(msg)
except User.DoesNotExist:
msg = 'Invalid signature'
raise exceptions.AuthenticationFailed(msg)

return context_dict
return user
67 changes: 59 additions & 8 deletions bend/src/users/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,16 +74,67 @@ query {

```
mutation {
loginUser(input: {
username: "ncrmro",
password: "testpassword"
}) {
user {
username,
email,
loginUser(input: {username: "ncrmro", password: "testpassword"}) {
viewer {
username
email
todomodel(first: 2) {
edges {
node {
text
}
}
}
}
jwtToken
}
}
query {
login(username:"ncrmro", password: "testpassword") {
viewer{
username,
dateJoined
},
token
jwtToken
}
}
query {viewer(jwtToken: ""){
viewer {
username,
isAuthenticated,
isAnonymous
}
}}
```

```
{"input_0": {"username": "ncrmro", "password": "testpassword"}}
mutation LoginUserMutation($input_0:LogInUserInput!) {
loginUser(input:$input_0) {
clientMutationId,
...F0
}
}
fragment F0 on LogInUserPayload {
viewer {
username,
id
}
}
{
"data": {
"loginUser": {
"clientMutationId": null,
"viewer": {
"id": "VXNlck5vZGU6MQ=="
}
}
}
}
```
87 changes: 58 additions & 29 deletions bend/src/users/schema.py
Original file line number Diff line number Diff line change
@@ -1,60 +1,89 @@
from graphene import AbstractType, Node, Field, String, relay, ObjectType, Int, Boolean, ID
from graphene import AbstractType, Node, Field, String, relay, ObjectType, Int, Boolean, ID, InputObjectType
from django.contrib.auth.models import AnonymousUser, User
from graphene import AbstractType, Node, relay
from graphene_django.filter import DjangoFilterConnectionField
from graphene_django.types import DjangoObjectType
from .jwt_util import loginUser
from .jwt_util import loginUser, authenticate
from jwt_auth import settings, exceptions
import jwt

jwt_decode_handler = settings.JWT_DECODE_HANDLER


class UserNode(DjangoObjectType):
class Meta:
model = User
only_fields = (
'id',
'last_login',
'is_superuser',
'username',
'first_name',
'last_name',
'email',
'is_staff',
'is_active',
'date_joined',
'todomodel'
)
interfaces = (Node,)


class CurrentUser(ObjectType):
id = ID()
username = String()
password = String()
is_anonymous = Boolean()
is_authenticated = Boolean()
is_staff = Boolean()
is_active = Boolean()


class UserQueries(AbstractType):
user = Node.Field(UserNode)
user = Field(UserNode)
all_users = DjangoFilterConnectionField(UserNode)
viewer = Field(UserNode)

current_user = Field(CurrentUser)

def resolve_current_user(self, args, context, info):
anon = AnonymousUser
return CurrentUser(
id=anon.id,
username=anon.username,
is_anonymous=anon.is_anonymous,
is_authenticated=anon.is_authenticated,
is_staff=anon.is_staff,
is_active=anon.is_active,
)
def resolve_viewer(self, args, context, info):
try:
check_token = authenticate(context)
print('Found Token in Auth Header', check_token)
token_user = check_token[0]
user = User.objects.get(id=token_user.id, username=token_user.username)
return user
except:
return User(
id=0,
username=""
)


class LogInUser(relay.ClientIDMutation):
class Input:
username = String(required=True)
password = String(required=True)

user = Field(UserNode)
token = String()
viewer = Field(UserNode)
jwt_token = String()

@classmethod
def mutate_and_get_payload(cls, input, context, info):
print("Logging user in", input, context, info)
username = input.get('username')
password = input.get('password')
jwt_token = loginUser(username, password)
viewer = User.objects.get(username=username)
return LogInUser(viewer, jwt_token)


class CreateUser(relay.ClientIDMutation):
class Input:
username = String(required=True)
password = String(required=True)

viewer = Field(UserNode)
jwt_token = String()

@classmethod
def mutate_and_get_payload(cls, input, context, info):
print("Logging user in", input, context, info)
username = input.get('username')
password = input.get('password')
token = loginUser(username, password)
return LogInUser(token=token)
viewer = User.objects.create_user(username=username, password=password)
jwt_token = loginUser(username, password)
return CreateUser(viewer, jwt_token)


class UserMutations(AbstractType):
login_user = LogInUser.Field()
create_user = CreateUser.Field()
5 changes: 5 additions & 0 deletions circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ machine:
general:
artifacts:
- ".coverage"
- "screenshots"
dependencies:
pre:
- wget -qO- https://github.com/mozilla/geckodriver/releases/download/v0.13.0/geckodriver-v0.13.0-linux64.tar.gz | tar -xvz -C /home/ubuntu/bin
- chmod +x /home/ubuntu/bin/geckodriver
- |
if [[ ! -e ~/.yarn/bin/yarn || $(yarn --version) != "${YARN_VERSION}" ]]; then
echo "Download and install Yarn."
Expand All @@ -26,6 +29,8 @@ dependencies:
- ~/.cache/yarn
post:
- pip install -r ./bend/deps/testing.txt
- ./bend/manage.py collectstatic --no-input


test:
override:
Expand Down
Loading

0 comments on commit edb560c

Please sign in to comment.