From c1300fdb5f6bb5af46286928989fce022ec6d78b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Y=C3=BBki=20Vachot?= Date: Sat, 29 Jan 2022 17:18:43 +0100 Subject: [PATCH] Test: Not finished --- angular.json | 2 +- backend/Dockerfile | 6 + backend/app.py | 8 + backend/application/__init__.py | 35 +++ backend/application/responses.py | 35 +++ backend/application/routes.py | 375 +++++++++++++++++++++++++++ backend/application/sessionJWT.py | 39 +++ backend/config.py | 31 +++ backend/requirements.txt | 13 + package.json | 4 +- server.js | 13 - src/environments/environment.prod.ts | 2 +- 12 files changed, 546 insertions(+), 17 deletions(-) create mode 100644 backend/Dockerfile create mode 100644 backend/app.py create mode 100644 backend/application/__init__.py create mode 100644 backend/application/responses.py create mode 100644 backend/application/routes.py create mode 100644 backend/application/sessionJWT.py create mode 100644 backend/config.py create mode 100644 backend/requirements.txt delete mode 100644 server.js diff --git a/angular.json b/angular.json index 99c397a..a21c621 100644 --- a/angular.json +++ b/angular.json @@ -20,7 +20,7 @@ "build": { "builder": "@angular-devkit/build-angular:browser", "options": { - "outputPath": "dist/frontend", + "outputPath": "backend/static", "index": "src/index.html", "main": "src/main.ts", "polyfills": "src/polyfills.ts", diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..2987276 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,6 @@ +FROM python:latest +WORKDIR /data/backend +COPY requirements.txt requirements.txt +RUN pip install --upgrade pip +RUN pip install -r requirements.txt +COPY . . \ No newline at end of file diff --git a/backend/app.py b/backend/app.py new file mode 100644 index 0000000..50e795c --- /dev/null +++ b/backend/app.py @@ -0,0 +1,8 @@ +from application import create_app +import os + +app = create_app(os.environ.get('FLASK_ENV', None)) + +if __name__ == "__main__": + PORT = os.environ.get('PORT', 33507) + app.run(host='0.0.0.0', port=PORT, DEBUG=True) diff --git a/backend/application/__init__.py b/backend/application/__init__.py new file mode 100644 index 0000000..b68cd09 --- /dev/null +++ b/backend/application/__init__.py @@ -0,0 +1,35 @@ +from flask import Flask +from flask_sqlalchemy import SQLAlchemy +from flask_cors import CORS +import sys + +db = SQLAlchemy() + + +def create_app(flask_env='development'): + app = Flask(__name__, instance_relative_config=False) + origin = app.config.get('ALLOW_ORIGIN') + if origin is None: + origin = ['http://127.0.0.1:4200', 'http://localhost:4200'] + CORS(app, supports_credentials=True, origins=origin) + if flask_env == 'production': + app.config.from_object("config.ProductionConfig") + elif flask_env == 'testing': + app.config.from_object("config.TestingConfig") + elif flask_env == 'development': + app.config.from_object("config.DevelopmentConfig") + else: + app.config.from_object("config.Config") + + if app.config['SQLALCHEMY_DATABASE_URI_1'] is None or app.config['SQLALCHEMY_DATABASE_URI_2'] is None: + print('No ENV Variable for DATABASE_URL_USERS or DATABASE_URL_LOGS') + sys.exit(1) + else: + print('ENV Variables passed : ', app.config['SQLALCHEMY_BINDS']) + + db.init_app(app) + with app.app_context(): + from . import routes + app.register_blueprint(routes.bp) + db.create_all() + return app diff --git a/backend/application/responses.py b/backend/application/responses.py new file mode 100644 index 0000000..03e11a6 --- /dev/null +++ b/backend/application/responses.py @@ -0,0 +1,35 @@ +from flask import current_app as app +import json + + +def send_error(status_code, message, token=None): + data_json = { + 'status': 'error', + 'message': message + } + res = app.response_class( + response=json.dumps(data_json), + status=status_code, + mimetype='application/json' + ) + if token is not None: + res.set_cookie('SESSIONID', token) + return res + + +def send_message(message, data, token=None, token_delete=False): + data_json = { + 'status': 'success', + 'message': message, + 'data': data + } + res = app.response_class( + response=json.dumps(data_json), + status=200, + mimetype='application/json' + ) + if token is not None: + res.set_cookie('SESSIONID', token) + if token_delete: + res.delete_cookie('SESSIONID') + return res diff --git a/backend/application/routes.py b/backend/application/routes.py new file mode 100644 index 0000000..ae9445e --- /dev/null +++ b/backend/application/routes.py @@ -0,0 +1,375 @@ +from flask import request, Blueprint, render_template, current_app as app +from werkzeug.exceptions import HTTPException +from .responses import send_message, send_error +import requests +from .sessionJWT import create_auth_token, check_auth_token + + +# Request Post +def request_post(url, data_json): + return requests.post(app.config['SQLALCHEMY_BINDS'] + url, json=data_json) + + +# Request Put +def request_put(url, data_json): + return requests.put(app.config['SQLALCHEMY_BINDS'] + url, json=data_json) + + +# Request Get +def request_get(url): + return requests.get(app.config['SQLALCHEMY_BINDS'] + url) + + +# Request Delete +def request_delete(url, data_json): + return requests.delete(app.config['SQLALCHEMY_BINDS'] + url, json=data_json) + + +bp = Blueprint('myapp', __name__) + + +@bp.app_errorhandler(HTTPException) +def handle_exception(e): + return send_error(e.code, e.name) + + +@bp.route('/', methods=['GET']) +def root(): + return render_template('index.html') + + +# Login +@bp.route('/api/login', methods=['POST']) +def login(): + post_json = request.json + try: + post_email = str(post_json['email']) + post_password = str(post_json['password']) + if post_email != '' and post_password != '': + ip = request.remote_addr + # res = db_login(ip, post_email, post_password) + res = request_post('login', {'ip': ip, 'email': post_email, 'password': post_password}).json() + if res['status'] == 0: + user = res['data'] + token = create_auth_token(user) + return send_message(res['message'], user, token) + elif res['status'] == 1: + user = None + token = create_auth_token(user) + return send_error(400, res['message'], token) + else: + return send_error(400, 'Empty email and/or password fields.') + except KeyError as e: + return send_error(400, 'Need email, password fields.') + + +# Register +@bp.route('/api/register', methods=['POST']) +def register(): + post_json = request.json + try: + post_email = str(post_json['email']) + post_nickname = str(post_json['nickname']) + post_password = str(post_json['password']) + if post_email != '' and post_password != '' and post_nickname != '': + ip = request.remote_addr + # res = db_register(ip, post_email, post_nickname, post_password) + res = request_post('register', {'ip': ip, 'email': post_email, 'nickname': post_nickname, 'password': post_password}).json() + if res['status'] == 1: + return send_error(500, res['message']) + elif res['status'] == 0: + return send_message(res['message'], res['data']) + else: + return send_error(400, 'Empty email and/or password and/or nickname fields.') + except KeyError as e: + return send_error(400, 'Need ' + str(e) + 'field.') + + +# Logout +@bp.route('/api/logout', methods=['DELETE']) +def logout(): + token = check_auth_token(request) + if token['success']: + ip = request.remote_addr + message = 'User disconnected.' + request_post('log', { + 'ip': ip, + 'action': 'logout', + 'message': message, + 'has_succeeded': True, + 'status_code': 0, + 'table': 'users', + 'id_user': token['payload']['id'] + }) + return send_message(message, None, token_delete=True) + else: + return send_error(500, token['message']) + + +# Update User (Nickname, Password) +@bp.route('/api/user/update', methods=['PUT']) +def user_update(): + token = check_auth_token(request) + if token['success']: + post_json = request.json + post_nickname = None + post_password = None + fields = '' + if 'nickname' in post_json: + post_nickname = str(post_json['nickname']) + else: + fields += 'nickname ' + + if 'password' in post_json: + post_password = str(post_json['password']) + else: + fields += 'password ' + + if post_nickname is not None or post_password is not None: + if post_nickname != '' and post_password != '': + ip = request.remote_addr + user_id = token['payload']['id'] + # res = db_user_update(ip, user_id, post_nickname, post_password) + res = request_post('user/update', {'ip': ip, 'user_id': user_id, 'nickname': post_nickname, 'password': post_password}).json() + if res['status'] == 1: + return send_error(500, res['message']) + elif res['status'] == 0: + return send_message(res['message'], res['data']) + else: + return send_error(400, 'Empty nickname and/or password fields.') + else: + return send_error(400, 'Need ' + fields + 'field.') + else: + return send_error(500, token['message']) + + +# Delete User +@bp.route('/api/user/delete', methods=['DELETE']) +def user_delete(): + token = check_auth_token(request) + if token['success']: + ip = request.remote_addr + user_id = token['payload']['id'] + # res = db_user_delete(ip, user_id) + res = request_post('user/update', {'ip': ip, 'user_id': user_id}).json() + if res['status'] != 0: + return send_error(500, res['message']) + else: + request_post('log', { + 'ip': ip, + 'action': 'user/delete', + 'message': 'User deleted.', + 'has_succeeded': True, + 'status_code': 0, + 'table': 'users', + 'id_user': token['payload']['id'] + }) + return send_message(res['message'], None, token_delete=True) + else: + return send_error(500, token['message']) + + +# Admin : Create User +@bp.route('/api/admin/create/user', methods=['POST']) +def admin_create_user(): + token = check_auth_token(request) + if token['success']: + ip = request.remote_addr + user_id = token['payload']['id'] + is_admin = token['payload']['is_admin'] + if is_admin: + post_json = request.json + post_email = None + post_nickname = None + post_password = None + post_is_admin = None + fields = '' + if 'email' in post_json: + post_email = str(post_json['email']) + else: + fields += 'email ' + + if 'nickname' in post_json: + post_nickname = str(post_json['nickname']) + else: + fields += 'nickname ' + + if 'password' in post_json: + post_password = str(post_json['password']) + else: + fields += 'password ' + + if 'is_admin' in post_json: + post_is_admin = bool(post_json['is_admin']) + else: + fields += 'is_admin ' + + if post_email is not None or post_nickname is not None or post_password is not None or post_is_admin is not None: + if post_email != '' and post_nickname != '' and post_password != '' and str(post_is_admin) != '': + res = db_register(ip, post_email, post_nickname, post_password, is_admin=post_is_admin) + if res['status'] == 1: + db_create_log( + ip=ip, + action='admin/create/user', + message=res['message'], + has_succeeded=False, + status_code=res['status'], + table='users', + id_user=user_id + ) + return send_error(500, res['message']) + elif res['status'] == 0: + db_create_log( + ip=ip, + action='admin/create/user', + message=res['message'], + has_succeeded=True, + status_code=res['status'], + table='users', + id_user=user_id + ) + return send_message(res['message'], res['data']) + else: + return send_error(400, 'Empty email and/or nickname and/or password and/or is_admin fields.') + else: + return send_error(400, 'Need ' + fields + 'field.') + else: + return send_error(500, 'User does not have permission.') + else: + return send_error(500, token['message']) + + +# Admin : Change User password and/or role +@bp.route('/api/admin/update/user', methods=['PUT']) +def admin_update_user(): + token = check_auth_token(request) + if token['success']: + user_id = token['payload']['id'] + is_admin = token['payload']['is_admin'] + if is_admin: + post_json = request.json + post_is_admin = None + post_password = None + post_user_id_delete = None + fields = '' + if 'id' in post_json: + post_user_id_delete = int(post_json['id']) + else: + fields += 'id ' + + if 'is_admin' in post_json: + post_is_admin = bool(post_json['is_admin']) + else: + fields += 'is_admin ' + + if 'password' in post_json: + post_password = str(post_json['password']) + else: + fields += 'password ' + + if post_user_id_delete is not None and (post_is_admin is not None or post_password is not None): + if str(post_is_admin) != '' and post_password != '' and str(post_user_id_delete) != '': + ip = request.remote_addr + res = db_admin_update_user(ip, post_user_id_delete, post_is_admin, post_password) + if res['status'] == 1: + db_create_log( + ip=ip, + action='admin/update/user', + message=res['message'], + has_succeeded=False, + status_code=res['status'], + table='users', + id_user=user_id + ) + return send_error(500, res['message']) + elif res['status'] == 0: + db_create_log( + ip=ip, + action='admin/update/user', + message=res['message'], + has_succeeded=True, + status_code=res['status'], + table='users', + id_user=user_id + ) + return send_message(res['message'], res['data']) + else: + return send_error(400, 'Empty is_admin and/or password fields.') + else: + return send_error(400, 'Need ' + fields + 'field.') + else: + return send_error(500, 'User does not have permission.') + else: + return send_error(500, token['message']) + + +# Admin : Delete User +@bp.route('/api/admin/delete/user/', methods=['DELETE']) +def admin_delete_user(id): + token = check_auth_token(request) + if token['success']: + ip = request.remote_addr + user_id = token['payload']['id'] + is_admin = token['payload']['is_admin'] + if is_admin: + post_json = {'id': id} + post_user_id_delete = None + fields = '' + if 'id' in post_json: + post_user_id_delete = int(post_json['id']) + else: + fields += 'id' + if post_user_id_delete is not None: + if str(post_user_id_delete) != '': + res = db_user_delete(ip, int(post_user_id_delete)) + if res['status'] == 1: + db_create_log( + ip=ip, + action='admin/delete/user', + message=res['message'], + has_succeeded=False, + status_code=res['status'], + table='users', + id_user=user_id + ) + return send_error(500, res['message']) + else: + db_create_log( + ip=ip, + action='admin/delete/user', + message=res['message'], + has_succeeded=True, + status_code=res['status'], + table='users', + id_user=user_id + ) + return send_message(res['message'], None) + else: + return send_error(400, 'Empty id field.') + else: + return send_error(400, 'Need ' + fields + 'field.') + else: + return send_error(500, 'User does not have permission.') + else: + return send_error(500, token['message']) + + +# List of User (must be authenticated) & Search +@bp.route('/api/users', methods=['GET']) +def users(): + token = check_auth_token(request) + if token['success']: + ip = request.remote_addr + user_id = token['payload']['id'] + get_query = request.args.get('q') + get_by = request.args.get('by') + get_id = request.args.get('id') + get_is_admin = request.args.get('is_admin') + get_order_by = request.args.get('order_by') + res = db_users(ip, user_id, get_query, get_by, get_id, get_is_admin, get_order_by) + if res['status'] == 1: + return send_error(500, res['message']) + else: + return send_message(res['message'], res['data']) + else: + return send_error(500, token['message']) diff --git a/backend/application/sessionJWT.py b/backend/application/sessionJWT.py new file mode 100644 index 0000000..ef228fb --- /dev/null +++ b/backend/application/sessionJWT.py @@ -0,0 +1,39 @@ +from datetime import datetime, timedelta +from flask import current_app as app +import jwt + + +def create_auth_token(user, time_second=1800): + try: + time = datetime.now() + payload = { + 'exp': time + timedelta(days=0, seconds=time_second), + 'iat': time, + 'user': user + } + return jwt.encode( + payload, + app.config.get('SECRET_KEY'), + algorithm='HS256' + ) + except Exception as e: + return e + + +def decode_auth_token(auth_token): + try: + payload = jwt.decode( + auth_token, + app.config.get('SECRET_KEY'), + algorithms='HS256' + ) + return {'success': True, 'payload': payload['user']} + except jwt.ExpiredSignatureError: + return {'success': False, 'message': 'Signature expired . Please log in again.'} + except jwt.InvalidTokenError as e: + return {'success': False, 'message': 'User not authenticated.'} + + +def check_auth_token(request): + token = request.cookies.get('SESSIONID') + return decode_auth_token(token) diff --git a/backend/config.py b/backend/config.py new file mode 100644 index 0000000..7bd3251 --- /dev/null +++ b/backend/config.py @@ -0,0 +1,31 @@ +import os + +basedir = os.path.abspath(os.path.dirname(__file__)) + + +class Config(object): + DEBUG = False + TESTING = False + CSRF_ENABLED = True + + FLASK_APP = os.environ.get('FLASK_APP', None) + FLASK_ENV = os.environ.get('FLASK_ENV', None) + + API_URL = os.environ.get('API_URL', 'http://127.0.0.1:5000/api/') + + SECRET_KEY = os.environ.get('SECRET_KEY', 'default_secret_key') + ALLOW_ORIGIN = os.environ.get('ALLOW_ORIGIN', None) + + +class ProductionConfig(Config): + DEBUG = False + API_URL = os.environ.get('API_URL', 'http://10.1.2.10:5000/api/') + + +class TestingConfig(Config): + TESTING = True + + +class DevelopmentConfig(Config): + DEVELOPMENT = True + DEBUG = True diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..6317cbe --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,13 @@ +alembic==1.7.5 +Flask==2.0.2 +Flask-Migrate==3.1.0 +Flask-Script==2.0.6 +Flask-Testing==0.8.1 +Flask-SQLAlchemy==2.5.1 +Flask-WTF==0.15.1 +pipreqs==0.4.10 +PyJWT==2.3.0 +pytest==6.2.5 +SQLAlchemy==1.4.27 +psycopg2==2.9.2 +Flask-Cors==3.0.10 \ No newline at end of file diff --git a/package.json b/package.json index 2f177c9..5d8ce6c 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,8 @@ "version": "0.0.0", "scripts": { "ng": "ng", - "start": "ng build && node server.js", - "build": "ng build", + "start": "ng build --prod --build-optimizer --baseHref=”/static/” && node server.js", + "build": "ng build --prod --build-optimizer --baseHref=”/static/”", "watch": "ng build --watch --configuration development", "test": "ng test" }, diff --git a/server.js b/server.js deleted file mode 100644 index 7398651..0000000 --- a/server.js +++ /dev/null @@ -1,13 +0,0 @@ -const path = require('path'); -const express = require('express'); -const app = express(); -const port = process.env.PORT || 4200; - -app.use(express.static(__dirname + '/dist/frontend')); -app.get('/*', function(req,res) { - res.sendFile(path.join(__dirname+ '/dist/frontend/index.html')); -}); - -app.listen(port, '0.0.0.0',() => { - console.log (`listening on port ${port}`); -}); diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index 92c0bcc..b5bca68 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -1,4 +1,4 @@ export const environment = { production: true, - debutUrl: 'http://10.1.2.10:5000/api/' + debutUrl: 'http://127.0.0.1:5000/api/' };