diff --git a/vanth/api/session.py b/vanth/api/session.py new file mode 100644 index 0000000..c97ca1a --- /dev/null +++ b/vanth/api/session.py @@ -0,0 +1,36 @@ +import json + +import flask +import sepiida.endpoints +import sepiida.fields + +import vanth.auth +import vanth.errors +import vanth.platform.user +import vanth.user + + +class Session(sepiida.endpoints.APIEndpoint): + ENDPOINT = '/session/' + SIGNATURE = sepiida.fields.JSONObject(s={ + 'username' : sepiida.fields.String(), + 'password' : sepiida.fields.String(methods=['POST']), + }) + @staticmethod + def post(payload): + user = vanth.platform.user.by_credentials(payload['username'], payload['password']) + if not user: + raise vanth.errors.InvalidCredentials() + vanth.auth.set_session(user) + + @staticmethod + def get(uuid): # pylint: disable=unused-argument + user = vanth.auth.current_user() + del user['password'] + if not user: + raise vanth.errors.ResourceDoesNotExist("You are not currently authenticated and therefore do not have a session") + return user + + def list(self): + payload = self.get(None) + return flask.make_response(json.dumps(payload), 200, {'Content-Type': 'application/json'}) diff --git a/vanth/auth.py b/vanth/auth.py new file mode 100644 index 0000000..c43ca3f --- /dev/null +++ b/vanth/auth.py @@ -0,0 +1,57 @@ +import uuid + +import flask +import sepiida.routing + +import vanth.platform.user + +PUBLIC_ENDPOINTS = [ + 'session.post', + 'about.get', +] + +def register_auth_handlers(app): + app.before_request(require_user) + +def endpoint(): + if flask.request.endpoint and flask.request.method: + return "{}.{}".format(flask.request.endpoint.lower(), flask.request.method.lower()) + +def require_user(): + user = None + if flask.request.method == 'OPTIONS' and 'Access-Control-Request-Method' in flask.request.headers: + return + + if not endpoint(): + return flask.make_response('Resource not found', 404) + + if endpoint() in PUBLIC_ENDPOINTS: + return + + if 'user_uri' not in flask.session: + raise vanth.errors.AuthenticationException( + status_code = 403, + error_code = 'unauthorized', + title = 'You must provide a valid session cookie', + ) + + _, params = sepiida.routing.extract_parameters(flask.current_app, 'GET', flask.session['user_uri']) + user = vanth.platform.user.by_filter({'uuid': [str(params['uuid'])]}) + if not user: + raise vanth.errors.AuthenticationException( + status_code = 403, + error_code = 'invalid-user', + title = 'The user tied to your session does not exist. Figure that out', + ) + + flask.g.current_user = user[0] + +def current_user(): + return getattr(flask.g, 'current_user', None) + +def is_authenticated(): + return current_user() is not None + +def set_session(user): + flask.session['user_uri'] = user['uri'] + flask.session['uuid'] = str(uuid.uuid4()) diff --git a/vanth/errors.py b/vanth/errors.py new file mode 100644 index 0000000..e4b2ed2 --- /dev/null +++ b/vanth/errors.py @@ -0,0 +1,5 @@ +import sepiida.errors + +AuthenticationException = sepiida.errors.api_error(status_code=403, error_code='authentication-exception') +InvalidCredentials = sepiida.errors.api_error(status_code=401, error_code='invalid-credentials') +ResourceDoesNotExist = sepiida.errors.api_error(status_code=404, error_code='resource-does-not-exist') diff --git a/vanth/platform/user.py b/vanth/platform/user.py index 3ea3e77..d21f0f4 100644 --- a/vanth/platform/user.py +++ b/vanth/platform/user.py @@ -8,17 +8,32 @@ import sepiida.routing import vanth.tables +def _to_dict(result): + return { + 'username' : result[vanth.tables.User.c.username], + 'password' : result[vanth.tables.User.c.password], + 'name' : result[vanth.tables.User.c.name], + 'uri' : sepiida.routing.uri('user', result[vanth.tables.User.c.uuid]), + } + def by_filter(filters): engine = chryso.connection.get() query = vanth.tables.User.select() query = chryso.queryadapter.map_and_filter(vanth.tables.User, filters, query) results = engine.execute(query).fetchall() - return [{ - 'username' : result[vanth.tables.User.c.username], - 'password' : result[vanth.tables.User.c.password], - 'name' : result[vanth.tables.User.c.name], - } for result in results] + return [_to_dict(result) for result in results] + +def by_credentials(username, password): + engine = chryso.connection.get() + + query = vanth.tables.User.select().where(vanth.tables.User.c.username == username) + result = engine.execute(query).first() + + if not (result and passlib.apps.custom_app_context.verify(password, result[vanth.tables.User.c.password])): + return None + + return _to_dict(result) def create(name, username, password): engine = chryso.connection.get() diff --git a/vanth/server.py b/vanth/server.py index 422d842..7fdf962 100644 --- a/vanth/server.py +++ b/vanth/server.py @@ -9,6 +9,7 @@ import sepiida.endpoints import vanth.api.about import vanth.api.session import vanth.api.user +import vanth.auth import vanth.user EXPOSE_HEADERS = [ @@ -56,6 +57,7 @@ def create_app(config): supports_credentials=True, expose_headers=EXPOSE_HEADERS, ) + vanth.auth.register_auth_handlers(app) app.route('/', methods=['GET'])(index) app.route('/login/', methods=['GET', 'POST', 'DELETE'])(login) @@ -63,5 +65,6 @@ def create_app(config): sepiida.endpoints.add_resource(app, vanth.api.about.About, endpoint='about') sepiida.endpoints.add_resource(app, vanth.api.user.User, endpoint='user') + sepiida.endpoints.add_resource(app, vanth.api.session.Session, endpoint='session') return app