diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b17bfec --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.pyc +*__pycache__ +/env +.coverage +.env diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..2fe84b4 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,13 @@ +language: python +python: +- "3.6" +cache: pip +install: +- pip install -r requirements.txt +before_script: + - export SECRET_KEY="secret" +script: +- pytest +- pytest --cov=app +after_success: +- coveralls diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..d7de1ab --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,15 @@ +from flask import Flask, Blueprint +from flask_restful import Api +from instance.config import app_config +from .api.v1 import myblue + +def create_app(config_name): + app = Flask(__name__, instance_relative_config=True) + app.config.from_object(app_config["development"]) + app.config.from_pyfile('config.py') + app.register_blueprint(myblue) + + app.config["TESTING"] = True + + + return app diff --git a/app/api/__init__.py b/app/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/api/v1/__init__.py b/app/api/v1/__init__.py new file mode 100644 index 0000000..7c5e3c9 --- /dev/null +++ b/app/api/v1/__init__.py @@ -0,0 +1,8 @@ +from flask import Flask, Blueprint +from flask_restful import Api, Resource +from .views import SignUp, Login +myblue = Blueprint("api", __name__, url_prefix="/storemanager/api/v1") + +api = Api(myblue) +api.add_resource(SignUp, '/auth/signup') +api.add_resource(Login, '/auth/login') diff --git a/app/api/v1/models.py b/app/api/v1/models.py new file mode 100644 index 0000000..b88052a --- /dev/null +++ b/app/api/v1/models.py @@ -0,0 +1,23 @@ +users = [] + + +class UserAuth(): + def __init__(self, name, email, password, role): + self.name = username + self.email = email + self.password = password + self.role = role + + def save_user(self): + id = len(users) + 1 + user = { + 'id' : self.id, + 'name' : self.name, + 'email': self.email, + 'password' : self.password, + 'role' : self.role + } + users.append(user) + +def collapse(): + users = [] diff --git a/app/api/v1/utils.py b/app/api/v1/utils.py new file mode 100644 index 0000000..e69de29 diff --git a/app/api/v1/views.py b/app/api/v1/views.py new file mode 100644 index 0000000..401309a --- /dev/null +++ b/app/api/v1/views.py @@ -0,0 +1,83 @@ +from flask import jsonify, make_response, request +from flask_restful import Resource +from functools import wraps +from instance.config import Config +import datetime +import jwt +import json + +from .models import * + +def token_required(func): + @wraps(func) + def decorated(*args, **kwargs): + token = None + if 'x-access-token' in request.headers: + token = request.headers['x-access-token'] + if not token: + return make_response(jsonify({ + "Message": "the access token is missing, Login"}, 401)) + try: + data = jwt.decode(token, Config.SECRET_KEY) + for user in users: + if user['email'] == data['email']: + current_user = user + + except: + + print(Config.SECRET_KEY) + return make_response(jsonify({ + "Message": "This token is invalid" + }, 403)) + + return func(current_user, *args, **kwargs) + return decorated + +class SignUp(Resource): + def post(self): + data = request.get_json() + id = len(users) + 1 + name = data["name"] + email = data["email"] + password = data["password"] + role = data["role"] + + user = { + "id": id, + "name": name, + "email": email, + "password": password, + "role": role + } + users.append(user) + return make_response(jsonify({ + "Status": "ok", + "Message": "user successfully created", + "user": users + } + ), 201) + +class Login(Resource): + def post(self): + data = request.get_json() + if not data: + return make_response(jsonify({ + "Message": "Ensure you have inserted your credentials" + } + ), 401) + email = data["email"] + password = data["password"] + + for user in users: + if email == user["email"] and password == user["password"]: + token = jwt.encode({ + "email": email, + "exp": datetime.datetime.utcnow() + datetime.timedelta + (minutes=5) + }, Config.SECRET_KEY) + return make_response(jsonify({ + "token": token.decode("UTF-8")}), 200) + return make_response(jsonify({ + "Message": "Login failed, wrong entries" + } + ), 401) diff --git a/app/tests/__init__.py b/app/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/tests/v1/__init__.py b/app/tests/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/tests/v1/test_endpoints.py b/app/tests/v1/test_endpoints.py new file mode 100644 index 0000000..afca114 --- /dev/null +++ b/app/tests/v1/test_endpoints.py @@ -0,0 +1,94 @@ +import unittest +import json +from app import create_app +from instance.config import app_config +from app.api.v1.models import collapse + + +class TestEndpoints(unittest.TestCase): + """docstring for setting up testEndpoints.""" + def setUp(self): + self.app = create_app(app_config['testing']) + self.test_client = self.app.test_client() + self.app_context = self.app.app_context() + self.user_admin_details = json.dumps({ + "name": "kevin", + "email": "kevin@email.com", + "password": "kevin", + "role": "admin" + }) + admin_signup = self.test_client.post( + "/storemanager/api/v1/auth/signup", + data=self.user_admin_details, headers={ + 'content-type': 'application/json'}) + self.user_attendant_details = json.dumps({ + "name": "brian", + "email": "brian@email.com", + "password": "brian", + "role": "attendant" + }) + attendant_signup = self.test_client.post("/storemanager/api/v1/auth/signup", + data=self.user_attendant_details, + headers={ + 'content-type': 'application/json' + }) + self.login_admin = json.dumps({ + "email": "kevin@email.com", + "password": "kevin" + }) + admin_login = self.test_client.post("/storemanager/api/v1/auth/login", + data=self.login_admin, headers={ + 'content-type': 'application/json' + }) + self.token_for_admin = json.loads(admin_login.data.decode())["token"] + self.login_attendant = json.dumps({ + "email": "brian@email.com", + "password": "brian" + }) + attendant_login = self.test_client.post("/storemanager/api/v1/auth/login", + data=self.login_attendant, + headers={ + 'content-type': 'application/json' + }) + self.token_for_attendant = json.loads(attendant_login.data.decode())["token"] + + def tearDown(self): + """removes all the context and dicts""" + collapse() + # self.app_context.pop() + def test_signup(self): + response = self.test_client.post("/storemanager/api/v1/auth/signup", + data=self.user_admin_details, + content_type='application/json') + self.assertEqual(response.status_code, 201) + def test_empty_login(self): + data = json.dumps( + { + "email": "", + "password": "" + } + ) + response = self.test_client.post("storemanager/api/v1/auth/login", + data=data, + content_type='application/json') + self.assertEqual(response.status_code, 401) + + def test_wrong_login(self): + data = json.dumps({ + "email": "blah@email.com", + "password": "blahblah" + }) + response = self.test_client.post("storemanager/api/v1/auth/login", + data=data, + content_type='application/json') + + self.assertEqual(response.status_code, 401) + + def test_login_granted(self): + + response = self.test_client.post("/storemanager/api/v1/auth/login", + data=self.login_admin, + headers={ + 'content-type': 'application/json' + }) + self.assertEqual(response.status_code, 200) diff --git a/instance/__init__.py b/instance/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/instance/config.py b/instance/config.py new file mode 100644 index 0000000..3f20a36 --- /dev/null +++ b/instance/config.py @@ -0,0 +1,20 @@ +class Config(): + + debug = False + SECRET_KEY = "secretkey" + +class Develop(Config): + """Configuration for the development enviroment""" + debug = True + + +class Testing(Config): + """Configuration for the testing enviroment""" + WTF_CSRF_ENABLED = False + debug = True + + +app_config={ +"development": Develop, +"testing": Testing +} diff --git a/procfile b/procfile new file mode 100644 index 0000000..e69de29 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..57ffa2c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,22 @@ +aniso8601==3.0.2 +atomicwrites==1.2.1 +attrs==18.2.0 +Blueprints==2.3.0.2 +Click==7.0 +coverage==4.5.1 +Flask==1.0.2 +Flask-RESTful==0.3.6 +funcsigs==1.0.2 +itsdangerous==0.24 +Jinja2==2.10 +MarkupSafe==1.0 +more-itertools==4.3.0 +pathlib2==2.3.2 +pluggy==0.8.0 +py==1.7.0 +PyJWT==1.6.4 +pytest==3.9.1 +pytz==2018.5 +scandir==1.9.0 +six==1.11.0 +Werkzeug==0.14.1 diff --git a/run.py b/run.py new file mode 100644 index 0000000..24df9c5 --- /dev/null +++ b/run.py @@ -0,0 +1,6 @@ +from app import create_app + +app = create_app("development") + +if __name__ == '__main__': + app.run()