This commit is contained in:
parent
6e3ba36851
commit
34bb3fcc25
14
docker-compose.yml
Normal file
14
docker-compose.yml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
networks:
|
||||||
|
redis:
|
||||||
|
external: false
|
||||||
|
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: redis
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:6379:6379"
|
||||||
|
networks:
|
||||||
|
- redis
|
@ -2,7 +2,6 @@ musicbrainzngs
|
|||||||
flask
|
flask
|
||||||
flask-restful
|
flask-restful
|
||||||
gunicorn
|
gunicorn
|
||||||
flask-jwt-extended
|
|
||||||
sentry-sdk[flask]
|
sentry-sdk[flask]
|
||||||
py-healthcheck
|
py-healthcheck
|
||||||
flask-redis
|
flask-redis
|
||||||
|
@ -10,48 +10,48 @@ __module_name__ = "aes_encrypt"
|
|||||||
__version__text__ = "1"
|
__version__text__ = "1"
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import json
|
import pickle
|
||||||
|
|
||||||
from Crypto.Cipher import AES
|
from Crypto.Cipher import AES
|
||||||
|
|
||||||
from fred import flaskred
|
from fred import flaskred
|
||||||
from schemas import UserSchema
|
|
||||||
|
|
||||||
|
|
||||||
class AESCrypto:
|
class AESCrypto:
|
||||||
def __init__(self, encoded_secret_key: str, padding_character: bytes = 'a'.encode('UFT-8')):
|
def __init__(self, encoded_secret_key: str, padding_character: bytes = '{'.encode('ascii')):
|
||||||
self.padding_character = padding_character
|
self.padding_character = padding_character
|
||||||
self.encoded_secret_key = encoded_secret_key
|
self.encoded_secret_key = encoded_secret_key
|
||||||
|
|
||||||
def encrypt_message(self, private_msg) -> bytes:
|
def encrypt_message(self, private_msg: str) -> tuple:
|
||||||
secret_key = base64.b64decode(self.encoded_secret_key)
|
secret_key = base64.b64decode(self.encoded_secret_key)
|
||||||
cipher = AES.new(secret_key, AES.MODE_EAX)
|
cipher = AES.new(secret_key, AES.MODE_EAX)
|
||||||
padded_private_msg = private_msg + (self.padding_character.decode('UFT-8') * ((16 - len(private_msg)) % 16))
|
ciphertext, tag = cipher.encrypt_and_digest(private_msg.encode('UTF-8'))
|
||||||
encrypted_msg = cipher.encrypt(padded_private_msg)
|
return cipher.nonce, ciphertext, tag
|
||||||
encoded_encrypted_msg = base64.b64encode(encrypted_msg)
|
|
||||||
return encoded_encrypted_msg
|
|
||||||
|
|
||||||
def decrypt_message(self, encoded_encrypted_msg) -> str:
|
def decrypt_message(self, nonce: bytes, encoded_encrypted_msg: bytes, tag: bytes) -> str:
|
||||||
secret_key = base64.b64decode(self.encoded_secret_key)
|
secret_key = base64.b64decode(self.encoded_secret_key)
|
||||||
encrypted_msg = base64.b64decode(encoded_encrypted_msg)
|
cipher = AES.new(secret_key, AES.MODE_EAX, nonce)
|
||||||
cipher = AES.new(secret_key, AES.MODE_EAX)
|
msg = cipher.decrypt_and_verify(encoded_encrypted_msg, tag).decode('UTF-8')
|
||||||
decrypted_msg = cipher.decrypt(encrypted_msg)
|
return msg
|
||||||
unpadded_private_msg = decrypted_msg.rstrip(self.padding_character)
|
|
||||||
return unpadded_private_msg.decode('UTF-8')
|
|
||||||
|
|
||||||
|
|
||||||
class EncryptedUserRedis:
|
class EncryptedUserRedis:
|
||||||
|
|
||||||
def __init__(self, encoded_secret_key: str):
|
def __init__(self, encoded_secret_key: str):
|
||||||
self.aes = AESCrypto(encoded_secret_key)
|
self.aes = AESCrypto(encoded_secret_key)
|
||||||
self.userschema = UserSchema(many=False)
|
|
||||||
|
|
||||||
def store(self, user: UserSchema) -> None:
|
def store(self, user: dict) -> None:
|
||||||
plaindict = self.userschema.dump(user)
|
nonce, ciphertext, tag = self.aes.encrypt_message(user['password'])
|
||||||
plaindict['password'] = self.aes.encrypt_message(user['password'])
|
user['nonce'] = nonce
|
||||||
flaskred.set(user['name'], json.dumps(plaindict).encode('UTF-8'))
|
user['ciphertext'] = ciphertext
|
||||||
|
user['tag'] = tag
|
||||||
|
user.pop('password', None)
|
||||||
|
flaskred.set(user['name'], pickle.dumps(user))
|
||||||
|
|
||||||
def load(self, username: str) -> UserSchema:
|
def load(self, username: str) -> dict:
|
||||||
encryptedstr = flaskred.get(username).decode('UTF-8')
|
encrypteddict = pickle.loads(flaskred.get(username))
|
||||||
encrypteddict = json.loads(encryptedstr)
|
|
||||||
user = UserSchema(name=encrypteddict['name'], password=self.aes.decrypt_message(encrypteddict['password']))
|
plaindict = {"name": encrypteddict['name'],
|
||||||
return user
|
"password": self.aes.decrypt_message(encrypteddict['nonce'], encrypteddict['ciphertext'],
|
||||||
|
encrypteddict['tag'])}
|
||||||
|
return plaindict
|
||||||
|
23
src/app.py
23
src/app.py
@ -1,19 +1,17 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import os
|
|
||||||
import logging
|
import logging
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask_restful import Api
|
from flask_restful import Api
|
||||||
import sentry_sdk
|
import sentry_sdk
|
||||||
from sentry_sdk.integrations.flask import FlaskIntegration
|
from sentry_sdk.integrations.flask import FlaskIntegration
|
||||||
from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration
|
|
||||||
from healthcheck import HealthCheck
|
from healthcheck import HealthCheck
|
||||||
|
|
||||||
from jwtman import jwtman
|
|
||||||
from marshm import ma
|
from marshm import ma
|
||||||
from fred import flaskred
|
from fred import flaskred
|
||||||
from config import SENTRY_DSN, JWT_SECRET_KEY, RELEASEMODE, RELEASE_ID, PORT, DEBUG, REDIS_URL
|
from config import SENTRY_DSN, RELEASEMODE, RELEASE_ID, PORT, DEBUG, REDIS_HOST
|
||||||
from errorhandlers import register_all_error_handlers
|
from errorhandlers import register_all_error_handlers
|
||||||
from resources import LoginApi
|
from resources import LoginApi, LogoffApi, MeApi
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Main Flask RESTful API
|
Main Flask RESTful API
|
||||||
@ -27,7 +25,7 @@ __version__text__ = "1"
|
|||||||
if SENTRY_DSN:
|
if SENTRY_DSN:
|
||||||
sentry_sdk.init(
|
sentry_sdk.init(
|
||||||
dsn=SENTRY_DSN,
|
dsn=SENTRY_DSN,
|
||||||
integrations=[FlaskIntegration(), SqlalchemyIntegration()],
|
integrations=[FlaskIntegration()],
|
||||||
traces_sample_rate=1.0,
|
traces_sample_rate=1.0,
|
||||||
send_default_pii=True,
|
send_default_pii=True,
|
||||||
release=RELEASE_ID,
|
release=RELEASE_ID,
|
||||||
@ -36,13 +34,13 @@ if SENTRY_DSN:
|
|||||||
)
|
)
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config['JWT_SECRET_KEY'] = JWT_SECRET_KEY
|
app.config['JWT_BLACKLIST_ENABLED'] = True
|
||||||
app.config['REDIS_URL'] = REDIS_URL
|
app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = ['access', 'refresh']
|
||||||
|
app.config['REDIS_URL'] = f"redis://{REDIS_HOST}:6379/0"
|
||||||
api = Api(app)
|
api = Api(app)
|
||||||
health = HealthCheck()
|
health = HealthCheck()
|
||||||
ma.init_app(app)
|
ma.init_app(app)
|
||||||
flaskred.init_app(app)
|
flaskred.init_app(app)
|
||||||
jwtman.init_app(app)
|
|
||||||
|
|
||||||
formatter = logging.Formatter(
|
formatter = logging.Formatter(
|
||||||
fmt="%(asctime)s - %(levelname)s - %(module)s - %(message)s"
|
fmt="%(asctime)s - %(levelname)s - %(module)s - %(message)s"
|
||||||
@ -58,15 +56,14 @@ logger.addHandler(handler)
|
|||||||
api.add_resource(LogoffApi, '/api/auth/logoff')
|
api.add_resource(LogoffApi, '/api/auth/logoff')
|
||||||
api.add_resource(LoginApi, '/api/auth/login')
|
api.add_resource(LoginApi, '/api/auth/login')
|
||||||
api.add_resource(MeApi, '/api/auth/me')
|
api.add_resource(MeApi, '/api/auth/me')
|
||||||
api.add_resource(ListsApi, '/api/lists')
|
# api.add_resource(ListsApi, '/api/lists')
|
||||||
api.add_resource(SingleListApi, '/api/lists/<listid>')
|
# api.add_resource(SingleListApi, '/api/lists/<listid>')
|
||||||
api.add_resource(TrackApi, '/api/lists/<listid>/<trackid>')
|
# api.add_resource(TrackApi, '/api/lists/<listid>/<trackid>')
|
||||||
|
|
||||||
app.add_url_rule("/healthz", "healthcheck", view_func=lambda: health.run())
|
app.add_url_rule("/healthz", "healthcheck", view_func=lambda: health.run())
|
||||||
|
|
||||||
register_all_error_handlers(app)
|
register_all_error_handlers(app)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(
|
app.run(
|
||||||
debug=bool(DEBUG),
|
debug=bool(DEBUG),
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import os
|
import os
|
||||||
from uuid import uuid4
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Configuration
|
Configuration
|
||||||
@ -20,8 +19,6 @@ SENTRY_DSN = os.environ.get("SENTRY_DSN")
|
|||||||
RELEASE_ID = os.environ.get("RELEASE_ID", "test")
|
RELEASE_ID = os.environ.get("RELEASE_ID", "test")
|
||||||
RELEASEMODE = os.environ.get("ONSPOT_RELEASEMODE", "dev")
|
RELEASEMODE = os.environ.get("ONSPOT_RELEASEMODE", "dev")
|
||||||
|
|
||||||
JWT_SECRET_KEY = os.getenv("ONSPOT_JWT_SECRET_KEY", str(uuid4()))
|
REDIS_HOST = os.getenv("ONSPOT_REDIS_HOST")
|
||||||
|
|
||||||
REDIS_URL = os.getenv("ONSPOT_REDIS_URL")
|
|
||||||
|
|
||||||
ENCODED_SECRET_KEY = os.getenv("ONSPOT_ENCODED_SECRET_KEY")
|
ENCODED_SECRET_KEY = os.getenv("ONSPOT_ENCODED_SECRET_KEY")
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from flask_jwt_extended import JWTManager
|
|
||||||
|
|
||||||
"""
|
|
||||||
JWTManager
|
|
||||||
"""
|
|
||||||
|
|
||||||
__author__ = '@tormakris'
|
|
||||||
__copyright__ = "Copyright 2020, onSpot Team"
|
|
||||||
__module_name__ = "jwtman"
|
|
||||||
__version__text__ = "1"
|
|
||||||
|
|
||||||
jwtman = JWTManager()
|
|
@ -1,10 +1,11 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import datetime
|
|
||||||
|
|
||||||
from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identity
|
import uuid
|
||||||
|
|
||||||
from flask_restful import Resource
|
from flask_restful import Resource
|
||||||
from flask import request, current_app, abort
|
from flask import request, current_app, abort, request
|
||||||
|
|
||||||
|
from fred import flaskred
|
||||||
from config import ENCODED_SECRET_KEY
|
from config import ENCODED_SECRET_KEY
|
||||||
from schemas import UserSchema, ListSchema, TrackSchema
|
from schemas import UserSchema, ListSchema, TrackSchema
|
||||||
from aes_encrypt import EncryptedUserRedis
|
from aes_encrypt import EncryptedUserRedis
|
||||||
@ -38,8 +39,32 @@ class LoginApi(Resource):
|
|||||||
current_app.logger.warning(e)
|
current_app.logger.warning(e)
|
||||||
abort(417, INVALID_JSON_SCHEMA_MSG)
|
abort(417, INVALID_JSON_SCHEMA_MSG)
|
||||||
|
|
||||||
self.encryptor.store(userobj)
|
self.encryptor.store(body)
|
||||||
|
|
||||||
expires = datetime.timedelta(days=7)
|
token = str(uuid.uuid4())
|
||||||
access_token = create_access_token(identity=str(userobj['name']), expires_delta=expires)
|
|
||||||
return {'token': access_token}, 200
|
flaskred.set(token, userobj['name'].encode('UTF-8'))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'token': token
|
||||||
|
}, 200
|
||||||
|
|
||||||
|
|
||||||
|
class LogoffApi(Resource):
|
||||||
|
"""
|
||||||
|
See: https://swagger.kmlabz.com/?urls.primaryName=onSpot%20Backend#/backend/logoff
|
||||||
|
"""
|
||||||
|
|
||||||
|
def delelete(self):
|
||||||
|
flaskred.delete(flaskred.get(request.headers.get('Authorization')).decode('UTF-8'))
|
||||||
|
flaskred.delete(request.headers.get('Authorization'))
|
||||||
|
return 204
|
||||||
|
|
||||||
|
|
||||||
|
class MeApi(Resource):
|
||||||
|
"""
|
||||||
|
See: https://swagger.kmlabz.com/?urls.primaryName=onSpot%20Backend#/backend/currentUser
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
return {"name": flaskred.get(request.headers.get('Authorization')).decode('UTF-8')}, 200
|
||||||
|
Loading…
Reference in New Issue
Block a user