diff --git a/requirements.txt b/requirements.txt index da09b77..ff01cfa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,4 @@ bcrypt flask +pyjwt +tinydb diff --git a/src/stream_auth/database/user.py b/src/stream_auth/database/user.py index 4eb09e5..71f39eb 100644 --- a/src/stream_auth/database/user.py +++ b/src/stream_auth/database/user.py @@ -1,3 +1,33 @@ ''' For managing users ''' + +from tinydb import TinyDB, Query +from stream_auth.models.user import User +from stream_auth import settings + +db = TinyDB(settings.USER_DATABASE) +query = Query() + + +def create_user(username: str, hash_pass: str): + ''' + Creates a new unique user, with a unique stream_key + ''' + + if len(db.search(query.username == username)) != 0: + raise ValueError("Username already exists.") + + user = User(username, hash_pass) + while db.search(query.stream_key == user.stream_key): + user.regenerate_stream_key() + + db.insert(user.__dict__) + return user + + +def search_stream_key(stream_key: str): + ''' + Search for a specific stream key + ''' + return db.search(query.stream_key == stream_key) diff --git a/src/stream_auth/middlewares/__init__.py b/src/stream_auth/middlewares/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/stream_auth/middlewares/auth.py b/src/stream_auth/middlewares/auth.py new file mode 100644 index 0000000..d3e2ade --- /dev/null +++ b/src/stream_auth/middlewares/auth.py @@ -0,0 +1,23 @@ +''' +Middleware for checking if access is authorized +''' + +from functools import wraps +from flask import request, abort +from stream_auth.middlewares import jwt + + +def auth(route_func): + ''' + Decorator to verify JWT in http header + ''' + @wraps(route_func) + def wrapper(*args, **kwargs): + + token = request.headers.get('Token') + if token is None or jwt.verify(token): + abort(401) + + return route_func(*args, **kwargs) + + return wrapper diff --git a/src/stream_auth/middlewares/jwt.py b/src/stream_auth/middlewares/jwt.py new file mode 100644 index 0000000..b6de10d --- /dev/null +++ b/src/stream_auth/middlewares/jwt.py @@ -0,0 +1,32 @@ +import time +import jwt +from stream_auth import settings + + +def read_key(path): + with open(path, 'r', encoding='utf-8') as file: + content = file.read() + return content + + +JWT_PRIV_KEY = read_key(settings.JWT_PRIV_PATH) +JWT_PUB_KEY = read_key(settings.JWT_PUB_PATH) + + +def create_token(username: str, stream_key: str): + exp = time.time() + settings.JWT_EXP_TIME + payload = {'username': username, 'stream_key': stream_key, 'exp': exp} + return jwt.encode(payload, JWT_PRIV_KEY, algorithm="RS256") + + +def verify(token: str): + try: + jwt.decode(token, JWT_PUB_KEY, algorithms=["RS256"]) + except (jwt.exceptions.ExpiredSignatureError, jwt.InvalidTokenError): + return False + + return True + + +def decode_token(token: str): + return jwt.decode(token, JWT_PUB_KEY, algorithms=["RS256"]) diff --git a/src/stream_auth/models/stream.py b/src/stream_auth/models/stream.py index e69de29..d04c901 100644 --- a/src/stream_auth/models/stream.py +++ b/src/stream_auth/models/stream.py @@ -0,0 +1,21 @@ + + +class Stream: + ''' + A single stream + ''' + + def __init__(self, name: str, description: str, username: str): + self.name = name + self.description = description + self.user = username + + def start(self): + ''' + To be called when a stream starts + ''' + + def stop(self): + ''' + To be called when a stream stops + ''' diff --git a/src/stream_auth/models/user.py b/src/stream_auth/models/user.py index 48ddea8..e966ed4 100644 --- a/src/stream_auth/models/user.py +++ b/src/stream_auth/models/user.py @@ -25,18 +25,12 @@ class User: def __init__(self, username: str, password: str): salt = bcrypt.gensalt() self.username = username - self.password = bcrypt.hashpw(password.encode('utf-8'), salt) - self.stream_key = generate_stream_key(STREAM_KEY_LENGTH) - - def regenerate_stream_key(self): - ''' - Recreate stream_key - ''' + self.password = str(bcrypt.hashpw(password.encode('utf-8'), salt), 'utf-8') self.stream_key = generate_stream_key(STREAM_KEY_LENGTH) def check_passwrod(self, password: str): input_pass = password.encode('utf-8') - result = bcrypt.checkpw(input_pass, self.password) + result = bcrypt.checkpw(input_pass, self.password.encode('utf-8')) return result def change_password(self, current_password: str, new_password: str): @@ -45,9 +39,15 @@ class User: ''' ecpass = current_password.encode('utf-8') enpass = new_password.encode('utf-8') - result = bcrypt.checkpw(ecpass, self.password) + result = bcrypt.checkpw(ecpass, self.password.encode('utf-8')) if not result: logging.warning("PASSWORDS DO NOT MATCH") salt = bcrypt.gensalt() self.password = bcrypt.hashpw(enpass, salt) + + def regenerate_stream_key(self): + ''' + Recreate stream_key + ''' + self.stream_key = generate_stream_key(STREAM_KEY_LENGTH) diff --git a/src/stream_auth/routes/user.py b/src/stream_auth/routes/user.py index 6d5dba5..5341218 100644 --- a/src/stream_auth/routes/user.py +++ b/src/stream_auth/routes/user.py @@ -44,3 +44,22 @@ def login(): # new_user.username, new_user.stream_key) return Response('OK', 200) + + +@user.route("/logout", methods=["POST"]) +def logout(): + ''' + Create a new user + ''' + + json = request.get_json() + username = json['username'] + password = json['password'] + + # TODO: actully do this + new_user = User(username, password) + + # logging.info('User %s created with stream key %s', + # new_user.username, new_user.stream_key) + + return Response('OK', 200) diff --git a/src/stream_auth/settings.py b/src/stream_auth/settings.py index db41b71..66e3c94 100644 --- a/src/stream_auth/settings.py +++ b/src/stream_auth/settings.py @@ -1,6 +1,17 @@ ''' Settings file, where all globals should be ''' +import os HOST = '0.0.0.0' PORT = 8080 + +APP_DIR = os.path.dirname(os.path.realpath(__file__)) +KEY_DIR = os.path.join(APP_DIR, 'keys') + +JWT_PRIV_PATH = os.path.join(KEY_DIR, 'jwtRS256.key') +JWT_PUB_PATH = os.path.join(KEY_DIR, 'jwtRS256.key.pub') +JWT_EXP_TIME = 2592000 + +USER_DATABASE = '/home/gabriel/db.json' +STREAM_KEY_LENGTH = 32