From 78c30e28d4a14724a5c72686ceef871fc8f5d0c8 Mon Sep 17 00:00:00 2001 From: Dan Cojocaru Date: Tue, 30 Nov 2021 14:06:41 +0200 Subject: [PATCH 1/6] Add root directory to VS Code workspace --- .vscode/settings.json | 6 ++++++ FoxBank.code-workspace | 3 +++ 2 files changed, 9 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..23c7673 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "files.exclude": { + "client": true, + "server": true + } +} \ No newline at end of file diff --git a/FoxBank.code-workspace b/FoxBank.code-workspace index ee2854e..512a324 100644 --- a/FoxBank.code-workspace +++ b/FoxBank.code-workspace @@ -5,6 +5,9 @@ }, { "path": "server" + }, + { + "path": "." } ], "settings": {} From 188cca8c39dbcf0218592126b3189e860a417ffd Mon Sep 17 00:00:00 2001 From: Dan Cojocaru Date: Tue, 30 Nov 2021 14:13:57 +0200 Subject: [PATCH 2/6] Added initial server database work --- server/.dockerignore | 2 ++ server/.gitignore | 2 ++ server/.vscode/extensions.json | 4 +-- server/.vscode/settings.json | 3 ++ server/Dockerfile | 13 ++++++++ server/Pipfile | 1 + server/Pipfile.lock | 18 +++++++++- server/db.py | 36 ++++++++++++++++++++ server/docker-compose.yml | 9 +++++ server/init.sql | 61 ++++++++++++++++++++++++++++++++++ server/server.py | 10 ++++++ 11 files changed, 155 insertions(+), 4 deletions(-) create mode 100644 server/.dockerignore create mode 100644 server/.gitignore create mode 100644 server/.vscode/settings.json create mode 100644 server/Dockerfile create mode 100644 server/db.py create mode 100644 server/docker-compose.yml create mode 100644 server/init.sql create mode 100644 server/server.py diff --git a/server/.dockerignore b/server/.dockerignore new file mode 100644 index 0000000..54e6782 --- /dev/null +++ b/server/.dockerignore @@ -0,0 +1,2 @@ +__pycache__/ +data/ \ No newline at end of file diff --git a/server/.gitignore b/server/.gitignore new file mode 100644 index 0000000..54e6782 --- /dev/null +++ b/server/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +data/ \ No newline at end of file diff --git a/server/.vscode/extensions.json b/server/.vscode/extensions.json index f2e0e83..73cca8d 100644 --- a/server/.vscode/extensions.json +++ b/server/.vscode/extensions.json @@ -1,5 +1,3 @@ { - "recommendations": [ - "ms-python.python" - ] + "recommendations": [] } \ No newline at end of file diff --git a/server/.vscode/settings.json b/server/.vscode/settings.json new file mode 100644 index 0000000..c3bfabb --- /dev/null +++ b/server/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.pythonPath": "/home/kbruen/.local/share/virtualenvs/server-4otskhbj/bin/python" +} \ No newline at end of file diff --git a/server/Dockerfile b/server/Dockerfile new file mode 100644 index 0000000..a3a4096 --- /dev/null +++ b/server/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.10-slim + +RUN pip3 install pipenv + +WORKDIR /app + +COPY Pipfile Pipfile.lock ./ +RUN pipenv install + +COPY . . + +EXPOSE 5000 +CMD [ "pipenv", "run", "gunicorn", "-b", "0.0.0.0:5000", "server:app" ] \ No newline at end of file diff --git a/server/Pipfile b/server/Pipfile index 0f41118..c3a312f 100644 --- a/server/Pipfile +++ b/server/Pipfile @@ -5,6 +5,7 @@ name = "pypi" [packages] flask = "*" +gunicorn = "*" [dev-packages] diff --git a/server/Pipfile.lock b/server/Pipfile.lock index dc6f3cf..593ee26 100644 --- a/server/Pipfile.lock +++ b/server/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "295fa60b4ad3b19ec29744ec2dfafba79ad5ee9a0b9ff095ac626e3d3981f117" + "sha256": "8886b8a6c0d31987ddea5b9e25bd02f7891650c967351486fd3cf0fd4d16271e" }, "pipfile-spec": 6, "requires": { @@ -32,6 +32,14 @@ "index": "pypi", "version": "==2.0.2" }, + "gunicorn": { + "hashes": [ + "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e", + "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8" + ], + "index": "pypi", + "version": "==20.1.0" + }, "itsdangerous": { "hashes": [ "sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c", @@ -123,6 +131,14 @@ "markers": "python_version >= '3.6'", "version": "==2.0.1" }, + "setuptools": { + "hashes": [ + "sha256:b4c634615a0cf5b02cf83c7bedffc8da0ca439f00e79452699454da6fbd4153d", + "sha256:feb5ff19b354cde9efd2344ef6d5e79880ce4be643037641b49508bbb850d060" + ], + "markers": "python_version >= '3.6'", + "version": "==59.4.0" + }, "werkzeug": { "hashes": [ "sha256:63d3dc1cf60e7b7e35e97fa9861f7397283b75d765afcaefd993d6046899de8f", diff --git a/server/db.py b/server/db.py new file mode 100644 index 0000000..aaa55de --- /dev/null +++ b/server/db.py @@ -0,0 +1,36 @@ +import sqlite3 + +from flask import current_app, g + +DB_FILE = './data/db.sqlite' + +def get(): + if 'db' not in g: + g.db = sqlite3.connect( + DB_FILE, + detect_types=sqlite3.PARSE_DECLTYPES, + ) + g.db.row_factory = sqlite3.Row + + return g.db + +def close(e=None): + db = g.pop('db', None) + + if db: + db.close() + +def init(): + db = get() + + with current_app.open_resource('init.sql') as f: + db.executescript(f.read().decode('utf8')) + db.commit() + +def init_app(app): + app.teardown_appcontext(close) + + import os.path + if not os.path.exists(DB_FILE): + with app.app_context(): + init() diff --git a/server/docker-compose.yml b/server/docker-compose.yml new file mode 100644 index 0000000..87ef756 --- /dev/null +++ b/server/docker-compose.yml @@ -0,0 +1,9 @@ +version: '3.9' +services: + web: + build: . + image: foxbank-server + ports: + - ${PORT:-5000}:5000 + volumes: + - ./data:/app/data diff --git a/server/init.sql b/server/init.sql new file mode 100644 index 0000000..c241a9d --- /dev/null +++ b/server/init.sql @@ -0,0 +1,61 @@ +drop table if exists users; +drop table if exists accounts; +drop table if exists users_accounts; +drop table if exists transactions; +drop table if exists accounts_transactions; +drop table if exists notifications; +drop table if exists users_notifications; + +create table users ( + id integer primary key autoincrement, + username text unique not null, + email text unique not null, + otp text not null, + fullname text not null +); + +create table accounts ( + id integer primary key autoincrement, + iban text unique not null, -- RO16 FOXB 0000 0000 0000 0000 + currency text not null, -- EUR, RON, USD, ? + account_type text not null, -- checking, savings, ? + custom_name text -- 'Car Savings'; name set by user +); + +create table users_accounts ( + user_id integer not null, -- one user can have multiple accounts + account_id integer UNIQUE not null, -- one account can only have one user + foreign key (user_id) references users (id), + foreign key (account_id) references accounts (id) +); + +create table transactions ( + id integer primary key autoincrement, + datetime text not null, + other_party text not null, -- JSON data describing sender/recipient/etc + -- depending on transaction type + status text not null, -- processed, failed, reverted, pending, etc + type text not null, -- send_transfer, receive_transfer, card_payment, fee, ... + extra text -- depending on type, JSON data describing extra info +); + +create table accounts_transactions ( + account_id integer not null, + transaction_id integer UNIQUE not null, + foreign key (account_id) references accounts (id), + foreign key (transaction_id) references transactions (id) +); + +create table notifications ( + id integer primary key autoincrement, + body text not null, + datetime text not null, + read integer not null +); + +create table users_notifications ( + user_id integer not null, + notification_id integer UNIQUE not null, + foreign key (user_id) references users (id), + foreign key (notification_id) references notifications (id) +); \ No newline at end of file diff --git a/server/server.py b/server/server.py new file mode 100644 index 0000000..e03bd00 --- /dev/null +++ b/server/server.py @@ -0,0 +1,10 @@ +from flask import Flask + +import db + +app = Flask(__name__) +db.init_app(app) + +@app.get('/') +def root(): + return 'Hello from FoxBank!' From a41137cf0b4aa2ff4baef67393648dcf5f9740fa Mon Sep 17 00:00:00 2001 From: Dan Cojocaru Date: Tue, 30 Nov 2021 14:24:17 +0200 Subject: [PATCH 3/6] Added GitHub Actions workflow --- .github/workflows/build-image.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/build-image.yml diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml new file mode 100644 index 0000000..bcd7ca4 --- /dev/null +++ b/.github/workflows/build-image.yml @@ -0,0 +1,27 @@ +name: Build Docker image +on: + push: + branches: + - master + - Backend +jobs: + push_to_ghcr: + name: Push to ghcr.io + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v2 + - name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Publish + env: + tags: ${{ format('{0},{1}', format('ghcr.io/{0}:latest', github.repository), format('ghcr.io/{0}:{1}', github.repository, github.ref_name)) }} + uses: docker/build-push-action@v2 + with: + context: ./server + tags: ${{ env.tags }} + push: true From be0b22cbede9948201d6ea549768e8b5c6f53c57 Mon Sep 17 00:00:00 2001 From: Dan Cojocaru Date: Mon, 6 Dec 2021 01:34:00 +0200 Subject: [PATCH 4/6] Implement login --- server/.vscode/launch.json | 23 ++++++++++++++++ server/Dockerfile | 2 +- server/Pipfile | 2 ++ server/Pipfile.lock | 32 +++++++++++++++++++--- server/db.py | 4 ++- server/db_utils.py | 24 +++++++++++++++++ server/login.py | 54 ++++++++++++++++++++++++++++++++++++++ server/models.py | 25 ++++++++++++++++++ server/ram_db.py | 30 +++++++++++++++++++++ server/returns.py | 46 ++++++++++++++++++++++++++++++++ server/server.py | 10 ++++--- 11 files changed, 243 insertions(+), 9 deletions(-) create mode 100644 server/.vscode/launch.json create mode 100644 server/db_utils.py create mode 100644 server/login.py create mode 100644 server/models.py create mode 100644 server/ram_db.py create mode 100644 server/returns.py diff --git a/server/.vscode/launch.json b/server/.vscode/launch.json new file mode 100644 index 0000000..a893403 --- /dev/null +++ b/server/.vscode/launch.json @@ -0,0 +1,23 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Flask", + "type": "python", + "request": "launch", + "module": "flask", + "env": { + "FLASK_APP": "server.py", + "FLASK_ENV": "development" + }, + "args": [ + "run", + "--no-debugger" + ], + "jinja": true + } + ] +} \ No newline at end of file diff --git a/server/Dockerfile b/server/Dockerfile index a3a4096..39c332b 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -10,4 +10,4 @@ RUN pipenv install COPY . . EXPOSE 5000 -CMD [ "pipenv", "run", "gunicorn", "-b", "0.0.0.0:5000", "server:app" ] \ No newline at end of file +CMD [ "pipenv", "run", "gunicorn", "-b", "0.0.0.0:5000", "--access-logfile", "-", "server:app" ] \ No newline at end of file diff --git a/server/Pipfile b/server/Pipfile index c3a312f..e7e4b40 100644 --- a/server/Pipfile +++ b/server/Pipfile @@ -6,6 +6,8 @@ name = "pypi" [packages] flask = "*" gunicorn = "*" +pyotp = "*" +flask-cors = "*" [dev-packages] diff --git a/server/Pipfile.lock b/server/Pipfile.lock index 593ee26..24d16de 100644 --- a/server/Pipfile.lock +++ b/server/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "8886b8a6c0d31987ddea5b9e25bd02f7891650c967351486fd3cf0fd4d16271e" + "sha256": "2ba252d63658abd009170d14705593521c57c99f82b643fcf232eeb51be35d10" }, "pipfile-spec": 6, "requires": { @@ -32,6 +32,14 @@ "index": "pypi", "version": "==2.0.2" }, + "flask-cors": { + "hashes": [ + "sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438", + "sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de" + ], + "index": "pypi", + "version": "==3.0.10" + }, "gunicorn": { "hashes": [ "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e", @@ -131,13 +139,29 @@ "markers": "python_version >= '3.6'", "version": "==2.0.1" }, + "pyotp": { + "hashes": [ + "sha256:9d144de0f8a601d6869abe1409f4a3f75f097c37b50a36a3bf165810a6e23f28", + "sha256:d28ddfd40e0c1b6a6b9da961c7d47a10261fb58f378cb00f05ce88b26df9c432" + ], + "index": "pypi", + "version": "==2.6.0" + }, "setuptools": { "hashes": [ - "sha256:b4c634615a0cf5b02cf83c7bedffc8da0ca439f00e79452699454da6fbd4153d", - "sha256:feb5ff19b354cde9efd2344ef6d5e79880ce4be643037641b49508bbb850d060" + "sha256:6d10741ff20b89cd8c6a536ee9dc90d3002dec0226c78fb98605bfb9ef8a7adf", + "sha256:d144f85102f999444d06f9c0e8c737fd0194f10f2f7e5fdb77573f6e2fa4fad0" ], "markers": "python_version >= '3.6'", - "version": "==59.4.0" + "version": "==59.5.0" + }, + "six": { + "hashes": [ + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.16.0" }, "werkzeug": { "hashes": [ diff --git a/server/db.py b/server/db.py index aaa55de..9602532 100644 --- a/server/db.py +++ b/server/db.py @@ -4,7 +4,9 @@ from flask import current_app, g DB_FILE = './data/db.sqlite' -def get(): +get_return = sqlite3.Connection + +def get() -> get_return: if 'db' not in g: g.db = sqlite3.connect( DB_FILE, diff --git a/server/db_utils.py b/server/db_utils.py new file mode 100644 index 0000000..2d3ca5c --- /dev/null +++ b/server/db_utils.py @@ -0,0 +1,24 @@ +from functools import wraps + +import db +import models + +def get_db(fn): + @wraps(fn) + def wrapper(*args, **kargs): + return fn(db.get(), *args, **kargs) + return wrapper + +@get_db +def get_user(db: db.get_return, username: str|None = None, user_id: int|None = None) -> models.User | None: + cur = db.cursor() + if username is not None: + cur.execute('select * from users where username=?', (username,)) + elif user_id is not None: + cur.execute('select * from users where id=?', (user_id,)) + else: + raise Exception('Neither username or user_id passed') + result = cur.fetchone() + if result is None: + return None + return models.User.from_query(result) diff --git a/server/login.py b/server/login.py new file mode 100644 index 0000000..1ea39de --- /dev/null +++ b/server/login.py @@ -0,0 +1,54 @@ +from functools import wraps +from flask import Blueprint, request + +from pyotp import TOTP + +import db_utils +import models +import ram_db +import returns + +login = Blueprint('login', __name__) + +@login.post('/') +def make_login(): + try: + username = request.json['username'] + code = request.json['code'] + except (TypeError, KeyError): + return returns.INVALID_REQUEST + + user: models.User | None = db_utils.get_user(username=username) + if user is None: + return returns.INVALID_DETAILS + + otp = TOTP(user.otp) + if not otp.verify(code, valid_window=1): + return returns.INVALID_DETAILS + + token = ram_db.login_user(user.id) + return returns.success(token=token) + +def ensure_logged_in(fn): + @wraps(fn) + def wrapper(*args, **kargs): + token = request.headers.get('Authorization', None) + if token is None: + return returns.NO_AUTHORIZATION + if not token.startswith('Bearer '): + return returns.INVALID_AUTHORIZATION + token = token[7:] + user_id = ram_db.get_user(token) + if user_id is None: + return returns.INVALID_AUTHORIZATION + return fn(user_id=user_id, *args, **kargs) + return wrapper + +@login.get('/whoami') +@ensure_logged_in +def whoami(user_id): + user: models.User | None = db_utils.get_user(user_id=user_id) + if user is not None: + user = user.to_json() + + return returns.success(user=user) diff --git a/server/models.py b/server/models.py new file mode 100644 index 0000000..163e599 --- /dev/null +++ b/server/models.py @@ -0,0 +1,25 @@ +from dataclasses import dataclass + +@dataclass +class User: + id: int + username: str + email: str + otp: str + fullname: str + + def to_json(self, include_otp=False, include_id=False): + result = { + 'username': self.username, + 'email': self.email, + 'fullname': self.fullname, + } + if include_id: + result['id'] = self.id + if include_otp: + result['otp'] = self.otp + return result + + @classmethod + def from_query(cls, query_result): + return cls(*query_result) \ No newline at end of file diff --git a/server/ram_db.py b/server/ram_db.py new file mode 100644 index 0000000..b11ace0 --- /dev/null +++ b/server/ram_db.py @@ -0,0 +1,30 @@ +from datetime import datetime, timedelta +from types import TracebackType +from uuid import uuid4 + +USED_TOKENS = set() +LOGGED_IN_USERS: dict[str, (int, datetime)] = {} + +def login_user(user_id: int) -> str: + ''' + Creates token for user + ''' + token = str(uuid4()) + while token in USED_TOKENS: + token = str(uuid4()) + if len(USED_TOKENS) > 10_000_000: + USED_TOKENS.clear() + USED_TOKENS.add(token) + LOGGED_IN_USERS[token] = user_id, datetime.now() + return token + +def get_user(token: str) -> int | None: + if token not in LOGGED_IN_USERS: + return None + + user_id, login_date = LOGGED_IN_USERS[token] + time_since_login: timedelta = datetime.now() - login_date + if time_since_login.total_seconds() > (60 * 30): # 30 mins + del LOGGED_IN_USERS[token] + return None + return user_id diff --git a/server/returns.py b/server/returns.py new file mode 100644 index 0000000..a3d212b --- /dev/null +++ b/server/returns.py @@ -0,0 +1,46 @@ +from http import HTTPStatus as _HTTPStatus + +def _make_error(http_status, code: str): + try: + http_status = http_status[0] + except Exception: + pass + + return { + 'status': 'error', + 'code': code, + }, http_status + +# General + +INVALID_REQUEST = _make_error( + _HTTPStatus.BAD_REQUEST, + 'general/invalid_request', +) + +# Login + +INVALID_DETAILS = _make_error( + _HTTPStatus.UNAUTHORIZED, + 'login/invalid_details', +) + +NO_AUTHORIZATION = _make_error( + _HTTPStatus.UNAUTHORIZED, + 'login/no_authorization', +) + +INVALID_AUTHORIZATION = _make_error( + _HTTPStatus.UNAUTHORIZED, + 'login/invalid_authorization', +) + +# Success + +def success(http_status=_HTTPStatus.OK, /, **kargs): + try: + http_status = http_status[0] + except Exception: + pass + + return dict(kargs, status='success'), http_status diff --git a/server/server.py b/server/server.py index e03bd00..9e437d5 100644 --- a/server/server.py +++ b/server/server.py @@ -1,10 +1,14 @@ from flask import Flask +from flask_cors import CORS import db app = Flask(__name__) +CORS(app) db.init_app(app) -@app.get('/') -def root(): - return 'Hello from FoxBank!' +from login import login +app.register_blueprint(login, url_prefix='/login') + +if __name__ == '__main__': + app.run() From b390bfc5c47008e582988142304d74965608f569 Mon Sep 17 00:00:00 2001 From: Dan Cojocaru Date: Mon, 6 Dec 2021 02:04:13 +0200 Subject: [PATCH 5/6] Add logout --- server/decorators.py | 12 ++++++++++++ server/login.py | 9 ++++++++- server/ram_db.py | 6 ++++++ 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 server/decorators.py diff --git a/server/decorators.py b/server/decorators.py new file mode 100644 index 0000000..9376615 --- /dev/null +++ b/server/decorators.py @@ -0,0 +1,12 @@ +from http import HTTPStatus +from functools import wraps + +def no_content(fn): + @wraps(fn) + def wrapper(*args, **kargs): + result = fn(*args, **kargs) + if result is None: + return None, HTTPStatus.NO_CONTENT + else: + return result + return wrapper diff --git a/server/login.py b/server/login.py index 1ea39de..1ca14da 100644 --- a/server/login.py +++ b/server/login.py @@ -4,6 +4,7 @@ from flask import Blueprint, request from pyotp import TOTP import db_utils +from decorators import no_content import models import ram_db import returns @@ -41,9 +42,15 @@ def ensure_logged_in(fn): user_id = ram_db.get_user(token) if user_id is None: return returns.INVALID_AUTHORIZATION - return fn(user_id=user_id, *args, **kargs) + return fn(user_id=user_id, token=token, *args, **kargs) return wrapper +@login.post('/logout') +@ensure_logged_in +@no_content +def logout(token: str): + ram_db.logout_user(token) + @login.get('/whoami') @ensure_logged_in def whoami(user_id): diff --git a/server/ram_db.py b/server/ram_db.py index b11ace0..631422c 100644 --- a/server/ram_db.py +++ b/server/ram_db.py @@ -14,10 +14,16 @@ def login_user(user_id: int) -> str: token = str(uuid4()) if len(USED_TOKENS) > 10_000_000: USED_TOKENS.clear() + for token in LOGGED_IN_USERS: + USED_TOKENS.add(token) USED_TOKENS.add(token) LOGGED_IN_USERS[token] = user_id, datetime.now() return token +def logout_user(token: str): + if token in LOGGED_IN_USERS: + del LOGGED_IN_USERS[token] + def get_user(token: str) -> int | None: if token not in LOGGED_IN_USERS: return None From 24ea0844341649824068e34dc8612cc6051438fb Mon Sep 17 00:00:00 2001 From: Dan Cojocaru Date: Mon, 6 Dec 2021 02:49:24 +0200 Subject: [PATCH 6/6] Fixed bug in ensure_logged_in decorator --- server/login.py | 46 +++++++++++++++++++++++++++++----------------- server/run.sh | 3 +++ 2 files changed, 32 insertions(+), 17 deletions(-) create mode 100755 server/run.sh diff --git a/server/login.py b/server/login.py index 1ca14da..e836249 100644 --- a/server/login.py +++ b/server/login.py @@ -30,30 +30,42 @@ def make_login(): token = ram_db.login_user(user.id) return returns.success(token=token) -def ensure_logged_in(fn): - @wraps(fn) - def wrapper(*args, **kargs): - token = request.headers.get('Authorization', None) - if token is None: - return returns.NO_AUTHORIZATION - if not token.startswith('Bearer '): - return returns.INVALID_AUTHORIZATION - token = token[7:] - user_id = ram_db.get_user(token) - if user_id is None: - return returns.INVALID_AUTHORIZATION - return fn(user_id=user_id, token=token, *args, **kargs) - return wrapper +def ensure_logged_in(token=False, user_id=False): + def decorator(fn): + pass_token = token + pass_user_id = user_id + @wraps(fn) + def wrapper(*args, **kargs): + token = request.headers.get('Authorization', None) + if token is None: + return returns.NO_AUTHORIZATION + if not token.startswith('Bearer '): + return returns.INVALID_AUTHORIZATION + token = token[7:] + user_id = ram_db.get_user(token) + if user_id is None: + return returns.INVALID_AUTHORIZATION + + if pass_user_id and pass_token: + return fn(user_id=user_id, token=token, *args, **kargs) + elif pass_user_id: + return fn(user_id=user_id, *args, **kargs) + elif pass_token: + return fn(token=token, *args, **kargs) + else: + return fn(*args, **kargs) + return wrapper + return decorator @login.post('/logout') -@ensure_logged_in +@ensure_logged_in(token=True) @no_content def logout(token: str): ram_db.logout_user(token) @login.get('/whoami') -@ensure_logged_in -def whoami(user_id): +@ensure_logged_in(user_id=True) +def whoami(user_id: int): user: models.User | None = db_utils.get_user(user_id=user_id) if user is not None: user = user.to_json() diff --git a/server/run.sh b/server/run.sh new file mode 100755 index 0000000..acefda6 --- /dev/null +++ b/server/run.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env sh +docker-compose stop +PORT=14000 docker-compose up -d --build \ No newline at end of file