From b9cefcc11169e82beae47f4bd654b9f0febf6351 Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Sat, 25 Apr 2020 17:20:46 +0100 Subject: [PATCH] Added ability to log in with jellyfin credentials The new jellyfin_login and admin_only allow anyone use their username and password from jellyfin to login to the admin page, and restrict this to jellyfin admins only, respectively. --- README.md | 9 +++- data/config-default.ini | 7 +++ jellyfin_accounts/login.py | 89 ++++++++++++++++++++++++++++++------ jellyfin_accounts/web_api.py | 4 +- 4 files changed, 93 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index cfdde77..b6357e3 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ Usually as simple as: ``` git clone https://github.com/hrfee/jellyfin-accounts.git cd jellyfin-accounts -pip3 install -r requirements.txt +pip3 install pyOpenSSL python3 setup.py install ``` If not, or if you want to use docker, see [install](https://github.com/hrfee/jellyfin-accounts/wiki/Install). @@ -102,8 +102,15 @@ device_id = jf-accounts-0.1 ; Set 0.0.0.0 to run localhost host = 0.0.0.0 port = 8056 +; Enable this to use Jellyfin users instead of the below username and pw. +jellyfin_login = true +; Allows only admin users on Jellyfin to access admin page. +admin_only = true +; Username to use on admin page... (leave blank if using jellyfin_login) username = your username +; ..and its corresponding password (leave blank if using jellyfin_login) password = your password + debug = false ; Displayed at the bottom of all pages except admin diff --git a/data/config-default.ini b/data/config-default.ini index c55319c..1218ed0 100644 --- a/data/config-default.ini +++ b/data/config-default.ini @@ -13,8 +13,15 @@ device_id = jf-accounts-0.1 ; Set 0.0.0.0 to run localhost host = 0.0.0.0 port = 8056 +; Enable this to use Jellyfin users instead of the below username and pw. +jellyfin_login = true +; Allows only admin users on Jellyfin to access admin page. +admin_only = true +; Username to use on admin page... (leave blank if using jellyfin_login) username = your username +; ..and its corresponding password (leave blank if using jellyfin_login) password = your password + debug = false ; Displayed at the bottom of all pages except admin diff --git a/jellyfin_accounts/login.py b/jellyfin_accounts/login.py index 3738ccd..e17a3e0 100644 --- a/jellyfin_accounts/login.py +++ b/jellyfin_accounts/login.py @@ -8,21 +8,46 @@ from passlib.apps import custom_app_context as pwd_context import uuid from __main__ import config, app, g from __main__ import auth_log as log +from jellyfin_accounts.jf_api import Jellyfin +from jellyfin_accounts.web_api import jf +def tempJF(): + return Jellyfin(config['jellyfin']['server'], + config['jellyfin']['client'], + config['jellyfin']['version'], + config['jellyfin']['device'] + '_temp', + config['jellyfin']['device_id'] + '_temp') + class Account(): - def __init__(self, username, password): + def __init__(self, username=None, password=None): self.username = username - self.password_hash = pwd_context.hash(password) - self.id = str(uuid.uuid4()) + if password is not None: + self.password_hash = pwd_context.hash(password) + self.id = str(uuid.uuid4()) + self.jf = False + elif username is not None: + jf.authenticate(config['jellyfin']['username'], + config['jellyfin']['password']) + self.id = jf.getUsers(self.username, public=False)['Id'] + self.jf = True def verify_password(self, password): - return pwd_context.verify(password, self.password_hash) + if not self.jf: + return pwd_context.verify(password, self.password_hash) + else: + temp_jf = tempJF() + try: + return temp_jf.authenticate(self.username, password) + except Jellyfin.AuthenticationError: + return False def generate_token(self, expiration=1200): s = Serializer(app.config['SECRET_KEY'], expires_in=expiration) + log.debug(self.id) return s.dumps({ 'id': self.id }) @staticmethod - def verify_token(token, account): + def verify_token(token, accounts): + log.debug(f'verifying token {token}') s = Serializer(app.config['SECRET_KEY']) try: data = s.loads(token) @@ -30,27 +55,65 @@ class Account(): return None except BadSignature: return None - if data['id'] == account.id: - return account + if config.getboolean('ui', 'jellyfin_login'): + for account in accounts: + if data['id'] == accounts[account].id: + return account + else: + return accounts['adminAccount'] + + auth = HTTPBasicAuth() +accounts = {} -adminAccount = Account(config['ui']['username'], config['ui']['password']) +if not config.getboolean('ui', 'jellyfin_login'): + accounts['adminAccount'] = Account(config['ui']['username'], + config['ui']['password']) @auth.verify_password def verify_password(username, password): - user = adminAccount.verify_token(username, adminAccount) - if not user: - if username == adminAccount.username and adminAccount.verify_password(password): - g.user = adminAccount + user = None + verified = False + log.debug('Verifying auth') + if config.getboolean('ui', 'jellyfin_login'): + try: + jf_user = jf.getUsers(username, public=False) + id = jf_user['Id'] + user = accounts[id] + except KeyError: + if config.getboolean('ui', 'admin_only'): + if jf_user['Policy']['IsAdministrator']: + user = Account(username) + accounts[id] = user + else: + log.debug(f'User {username} not admin.') + return False + else: + user = Account(username) + accounts[id] = user + except Jellyfin.UserNotFoundError: + user = Account().verify_token(username, accounts) + if user: + verified = True + if not user: + log.debug(f'User {username} not found on Jellyfin') + return False + else: + user = accounts['adminAccount'] + verified = Account().verify_token(username, accounts) + + if not verified: + if username == user.username and user.verify_password(password): + g.user = user log.debug("HTTPAuth Allowed") return True else: log.debug("HTTPAuth Denied") return False - g.user = adminAccount + g.user = user log.debug("HTTPAuth Allowed") return True diff --git a/jellyfin_accounts/web_api.py b/jellyfin_accounts/web_api.py index c0029cf..d3dae98 100644 --- a/jellyfin_accounts/web_api.py +++ b/jellyfin_accounts/web_api.py @@ -7,7 +7,6 @@ import secrets import time from __main__ import config, config_path, app, g from __main__ import web_log as log -from jellyfin_accounts.login import auth from jellyfin_accounts.validate_password import PasswordValidator def resp(success=True, code=500): @@ -51,6 +50,8 @@ jf = Jellyfin(config['jellyfin']['server'], config['jellyfin']['device'], config['jellyfin']['device_id']) +from jellyfin_accounts.login import auth + attempts = 0 success = False while attempts != 3: @@ -262,7 +263,6 @@ def deleteInvite(): @auth.login_required def get_token(): token = g.user.generate_token() - log.debug('Token generated') return jsonify({'token': token.decode('ascii')})