diff --git a/README.md b/README.md index 76da509..d517d23 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ## stackS-api [![Build Status](https://travis-ci.org/hogum/stackS-api.svg?branch=develop)](https://travis-ci.org/hogum/stackS-api) -[![Coverage Status](https://coveralls.io/repos/github/hogum/stackS-api/badge.svg?branch=develop)](https://coveralls.io/github/hogum/stackS-api?branch=develop) [![Maintainability](https://api.codeclimate.com/v1/badges/026466004435ebedbdf8/maintainability)](https://codeclimate.com/github/hogum/stackS-api/maintainability) +[![Coverage Status](https://coveralls.io/repos/github/hogum/stackS-api/badge.svg)](https://coveralls.io/github/hogum/stackS-api) [![Maintainability](https://api.codeclimate.com/v1/badges/026466004435ebedbdf8/maintainability)](https://codeclimate.com/github/hogum/stackS-api/maintainability) [![Run in Postman](https://run.pstmn.io/button.svg)](https://app.getpostman.com/run-collection/faa1bb2518cd81a3e91d) @@ -53,9 +53,10 @@ $ flask run ##### Authorization Endpoints -Method | Enpoint | Functionality +Method | Endpoint | Functionality --- | --- |--- -POST | `api/v1/auth/users` | Re gister new User +POST | `api/v1/auth/register` | Register new User +POST | `api/v1/auth/login` | Login registered User PUT | `api/v1/auth/users/user_id` | Update user details DELETE | `api/v1/auth/users/user_id` | Delete a user account GET | `api/v1/auth/users` | Get all registered users diff --git a/app/__init__.py b/app/__init__.py index 4c3555d..5d346d0 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,5 +1,5 @@ from flask import Flask -from app.v1.views.users import auth +from app.v1.views.users import auth, blacklisted_tokens from instance.config import APP_CONFIG from flask_jwt_extended import JWTManager @@ -7,8 +7,16 @@ def create_app(config_setting): app = Flask(__name__) app.register_blueprint(auth) + app.config['JWT_BLACKLIST_ENABLED'] = True + app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = ['access'] + jwt = JWTManager(app) app.config.from_object(APP_CONFIG[ config_setting.strip().lower()]) + @jwt.token_in_blacklist_loader + def check_blacklisted_token(token): + jti = token['jti'] + return jti in blacklisted_tokens + return app diff --git a/app/v1/utils/helper.py b/app/v1/utils/helper.py index d708324..ffe9b50 100644 --- a/app/v1/utils/helper.py +++ b/app/v1/utils/helper.py @@ -1,7 +1,6 @@ import re -from functools import wraps +from functools import wraps, partial from flask import request, make_response, jsonify -from werkzeug.exceptions import BadRequest def verify_email(email): @@ -23,3 +22,20 @@ def wrapper(*args, **kwargs): Please provide a valid json header\t")), 400 return f(*args, **kwargs) return wrapper + + +""" +def verify_email(f, email): + @wraps(f) + def wrapper(*args, **kwargs): + email_regex = re.compile(r"[^@\\s]+@[^@\\s]+\\.[a-zA-Z0-9]+$") + + if email_regex.match(email): + return f(*args, **kwargs) + else: + return make_response(jsonify("Email format unkown")), 400 + return wrapper + + +real_email = partial(verify_email) +""" diff --git a/app/v1/views/users.py b/app/v1/views/users.py index 6323bcf..79a63d5 100644 --- a/app/v1/views/users.py +++ b/app/v1/views/users.py @@ -1,12 +1,15 @@ from flask import Blueprint, request, make_response, jsonify, session import random -from flask_jwt_extended import jwt_required, create_access_token +from flask_jwt_extended import ( + jwt_required, create_access_token, get_jwt_identity, get_raw_jwt) from app.v1.utils.helper import verify_email, validate_json_header from app.v1.models.users import UserModel auth = Blueprint('auth', __name__, url_prefix='/api/v1/auth/') +blacklisted_tokens = set() + @auth.route('register', methods=['POST', 'GET']) @validate_json_header @@ -18,8 +21,8 @@ def register_user(): """ if request.method == 'POST': data = request.get_json() - email = data['email'] - password = data['password'] + email = data.get('email') + password = data.get('password') username = data.get('username') if verify_email(email) and len(password) > 6: @@ -77,3 +80,16 @@ def login_user(): "Logged in as": repr(user), "Token": user_token })), 201 + + +@auth.route('logout', methods=['DELETE']) +@validate_json_header +@jwt_required +def logout_user(): + data = request.get_json() + + jti = get_raw_jwt()['jti'] + + blacklisted_tokens.add(jti) + return make_response(jsonify( + "Logout successful")), 201 diff --git a/instance/config.py b/instance/config.py index ab31de0..4912778 100644 --- a/instance/config.py +++ b/instance/config.py @@ -5,10 +5,12 @@ class BaseConfig: DEBUG = False TESTING = False SECRET_KEY = os.environ.get('SECRET_KEY', - os.urandom(16)) + b'\xc2;F]l\x046t\xfe\x08' + ) CSRF_ENABLED = True JWT_SECRET_KET = os.environ.get('JWT_SECRET_KEY', - os.urandom(25)) + b'\xa3$\x0bm\xae\xbbmd\x12\x8f' + ) class DevelopmentConfig(BaseConfig): diff --git a/requirements.txt b/requirements.txt index 22037d2..0554967 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,15 @@ atomicwrites==1.2.1 attrs==18.2.0 +certifi==2018.11.29 +chardet==3.0.4 Click==7.0 coverage==4.5.2 +coverage-badge==0.2.0 +coveralls==1.5.1 +docopt==0.6.2 Flask==1.0.2 Flask-JWT-Extended==3.14.0 +idna==2.8 itsdangerous==1.1.0 Jinja2==2.10 MarkupSafe==1.1.0 @@ -13,5 +19,7 @@ py==1.7.0 PyJWT==1.7.1 pytest==4.0.2 pytest-cov==2.6.0 +requests==2.21.0 six==1.12.0 +urllib3==1.24.1 Werkzeug==0.14.1 diff --git a/tests/auth_test.py b/tests/auth_test.py index 5f64c86..9ba8dfa 100644 --- a/tests/auth_test.py +++ b/tests/auth_test.py @@ -126,3 +126,16 @@ def test_login_with_unregistered_email(self): content_type='application/json') self.assertEqual(response.status_code, 400, msg="Fails. Logs in user with unknown email") + + def test_logged_in_user_can_log_out(self): + + self.client.post('/api/v1/auth/login', + data=self.user_data, + content_type='application/json') + + response = self.client.delete('/api/v1/auth/logout', + headers=self.auth_header, + data=self.user_data, + content_type='application/json') + self.assertEqual(response.status_code, 201, + msg="Fails to logout user") diff --git a/tests/base_test.py b/tests/base_test.py index 6f37e74..e7d32e6 100644 --- a/tests/base_test.py +++ b/tests/base_test.py @@ -19,3 +19,9 @@ def setUp(self): data=self.user_data, content_type='application/json') self.new_user = json.loads(response.data.decode()) + + login_response = self.client.post('/api/v1/auth/login', + data=self.user_data, + content_type='application/json') + user = json.loads(login_response.data.decode()).get("Token") + self.auth_header = {"Authorization": "Bearer " + user}