Skip to content

Commit

Permalink
Merge c1e503b into 5355da1
Browse files Browse the repository at this point in the history
  • Loading branch information
JoshuaOndieki committed Jul 5, 2018
2 parents 5355da1 + c1e503b commit afd7501
Show file tree
Hide file tree
Showing 10 changed files with 137 additions and 11 deletions.
3 changes: 3 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ class Config:
Application configuration base class
"""
SECRET_KEY = 'secret'
JWT_SECRET_KEY = 'super-secret'
JWT_BLACKLIST_ENABLED = True
JWT_BLACKLIST_TOKEN_CHECKS = ['access', 'refresh']
BUNDLE_ERRORS = True
WTF_CSRF_ENABLED = False
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
Expand Down
8 changes: 8 additions & 0 deletions ridemyway/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@


from flask import Flask
from flask_jwt_extended import JWTManager

from config import config
from .api.v1.routes import v1
Expand Down Expand Up @@ -48,6 +49,13 @@ def create_app(config_name):
app.database = DATABASE
app.config.from_object(config[config_name])
app.conn = app.config['DB_CONN']
app.jwt = JWTManager(app)
app.blacklist = set()

@app.jwt.token_in_blacklist_loader
def check_if_token_in_blacklist(decrypted_token):
jti = decrypted_token['jti']
return jti in app.blacklist

@app.route('/')
def api_docs():
Expand Down
24 changes: 24 additions & 0 deletions ridemyway/api/v2/controllers/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
"""

from datetime import datetime
from werkzeug.security import check_password_hash
from flask_jwt_extended import create_access_token

from ridemyway.utils.response import Response
from ridemyway.utils.db_queries import sql_signup
Expand Down Expand Up @@ -34,3 +36,25 @@ def signup(self, **kwargs):
if user_added:
return Response.success(message=message, attributes=attributes), 201
return Response.failed(message='failed to add user'), 400

def login(self, **kwargs):
"""
Controls the login process
"""
username = None
email = None
if 'username' in kwargs:
username = kwargs['username']
if 'email' in kwargs:
email = kwargs['email']
self.user = get_user(username=username, email=email)
if self.user and check_password_hash(self.user['password'],
kwargs['password']):
message = 'Login successful'
access_token = create_access_token(identity=self.user['username'])
response = Response.success(message=message,
access_token=access_token)
return response, 200
message = 'Login unsuccessful'
info = 'Invalid username or password'
return Response.failed(message=message, info=info), 401
6 changes: 5 additions & 1 deletion ridemyway/api/v2/models/user.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
User model
"""
from werkzeug.security import generate_password_hash


class User():
Expand All @@ -26,4 +27,7 @@ def __init__(self, kwargs):
self.date_joined = kwargs['date_joined']
self.contacts = int(kwargs['contacts'])
self.email = kwargs['email']
self.password = kwargs['password']
self.set_password(kwargs['password'])

def set_password(self, password):
self.password = generate_password_hash(password)
26 changes: 23 additions & 3 deletions ridemyway/api/v2/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,28 @@ def post(self):
"""
User SIGNUP
"""
data = self.parser.parse_args()
signup_errors = errors.signup_errors(**data)
self.data = self.parser.parse_args()
signup_errors = errors.signup_errors(**self.data)
if signup_errors:
return json.loads(json.dumps(signup_errors)), 422
return auth.signup(**data)
return auth.signup(**self.data)


class Login(Resource):
"""
Login Resource
"""
def __init__(self):
self.parser = reqparse.RequestParser()
self.parser.add_argument('password',
help='This field cannot be blank',
required=True)
self.parser.add_argument('username')
self.parser.add_argument('email')

def post(self):
self.data = self.parser.parse_args()
login_errors = errors.login_errors(**self.data)
if login_errors:
return json.loads(json.dumps(login_errors)), 422
return auth.login(**self.data)
1 change: 1 addition & 0 deletions ridemyway/api/v2/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@

# Add routes here
add(r.Signup, '/auth/signup') # POST
add(r.Login, '/auth/login') # POST
56 changes: 56 additions & 0 deletions ridemyway/tests/tests_v2/test_auth/test_login.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""
This module tests login
"""
import unittest
import json

from ridemyway.tests.tests_v2 import V2BaseTest
from ridemyway.tests.tests_v2.urls import SIGNUP, LOGIN
from ridemyway.tests.tests_v2.data import VALID_DRIVER


class TestLogin(V2BaseTest):
"""
Tests login API endpoint
- Auth: '/api/v2/auth/login' # POST
"""
def signup(self):
self.client().post(SIGNUP, data=VALID_DRIVER)

def test_user_can_login_successfully(self):
self.signup()
self.response = self.client().post(LOGIN, data=VALID_DRIVER)
self.assertEqual(self.response.status_code, 200,
msg='Should return 200 status code for successful login')
result = json.loads(self.response.data.decode())
self.assertTrue(result['access_token'] is not False,
msg='Should return access token')

def test_non_user_cannot_login(self):
NON_USER = {
'username': 'null',
'password': 'null[pass]'
}
self.response = self.client().post(LOGIN, data=NON_USER)
self.assertEqual(self.response.status_code, 401,
msg='Should return 401 status code for non users')
result = json.loads(self.response.data.decode())
self.assertTrue(result['status'] == 'failed',
msg='Should return status failed in response data')

def test_non_matching_credentials_not_authorized(self):
self.signup()
FAKE_PASSWORD = {
'username': 'driver',
'password': 'fake[pass]'
}
self.response = self.client().post(LOGIN, data=FAKE_PASSWORD)
self.assertEqual(self.response.status_code, 401,
msg='Should return 401 status code for fake users')
result = json.loads(self.response.data.decode())
self.assertTrue(result['status'] == 'failed',
msg='Should return status failed in response data')


if __name__ == '__main__':
unittest.main()
1 change: 0 additions & 1 deletion ridemyway/tests/tests_v2/test_auth/test_signup.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ class TestSignUp(V2BaseTest):
"""

def test_driver_can_signup_successfully(self):
print(VALID_DRIVER)
self.response = self.client().post(SIGNUP, data=VALID_DRIVER)
self.assertEqual(self.response.status_code, 201,
msg='Should return 201 status code' +
Expand Down
11 changes: 5 additions & 6 deletions ridemyway/utils/db_queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from flask import current_app as app
import psycopg2
import psycopg2.extras


def sql_signup(user):
Expand All @@ -24,18 +25,16 @@ def sql_signup(user):
return True
except psycopg2.Error:
app.conn.rollback()
return 0


def get_user(username=None, email=None):
cur = app.conn.cursor()
cur = app.conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
search_by_username_email_sql = """
SELECT * FROM appuser WHERE username=%s OR email=%s;
"""
cur.execute(search_by_username_email_sql, (username, email))
exists = cur.fetchone()
user = cur.fetchone()
app.conn.commit()
cur.close()

if exists:
return True
if user:
return user
12 changes: 12 additions & 0 deletions ridemyway/utils/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,15 @@ def signup_errors(**kwargs):
meta = {'errors': len(errors)}
if errors:
return Response.failed(meta=meta, message=message, errors=errors)


def login_errors(**kwargs):
message = 'Login unsuccessful'
errors = {}
if 'username' not in kwargs and 'email' not in kwargs:
errors['identity'] = 'Provide either an email or username to login'
if 'password' not in kwargs:
errors['password'] = 'This field cannot be blank'
meta = {'errors': len(errors)}
if errors:
return Response.failed(meta=meta, message=message, errors=errors)

0 comments on commit afd7501

Please sign in to comment.