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.
This commit is contained in:
Harvey Tindall 2020-04-25 17:20:46 +01:00
parent 9b8204eb12
commit b9cefcc111
4 changed files with 93 additions and 16 deletions

View File

@ -46,7 +46,7 @@ Usually as simple as:
``` ```
git clone https://github.com/hrfee/jellyfin-accounts.git git clone https://github.com/hrfee/jellyfin-accounts.git
cd jellyfin-accounts cd jellyfin-accounts
pip3 install -r requirements.txt pip3 install pyOpenSSL
python3 setup.py install python3 setup.py install
``` ```
If not, or if you want to use docker, see [install](https://github.com/hrfee/jellyfin-accounts/wiki/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 ; Set 0.0.0.0 to run localhost
host = 0.0.0.0 host = 0.0.0.0
port = 8056 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 username = your username
; ..and its corresponding password (leave blank if using jellyfin_login)
password = your password password = your password
debug = false debug = false
; Displayed at the bottom of all pages except admin ; Displayed at the bottom of all pages except admin

View File

@ -13,8 +13,15 @@ device_id = jf-accounts-0.1
; Set 0.0.0.0 to run localhost ; Set 0.0.0.0 to run localhost
host = 0.0.0.0 host = 0.0.0.0
port = 8056 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 username = your username
; ..and its corresponding password (leave blank if using jellyfin_login)
password = your password password = your password
debug = false debug = false
; Displayed at the bottom of all pages except admin ; Displayed at the bottom of all pages except admin

View File

@ -8,21 +8,46 @@ from passlib.apps import custom_app_context as pwd_context
import uuid import uuid
from __main__ import config, app, g from __main__ import config, app, g
from __main__ import auth_log as log 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(): class Account():
def __init__(self, username, password): def __init__(self, username=None, password=None):
self.username = username self.username = username
self.password_hash = pwd_context.hash(password) if password is not None:
self.id = str(uuid.uuid4()) 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): 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): def generate_token(self, expiration=1200):
s = Serializer(app.config['SECRET_KEY'], expires_in=expiration) s = Serializer(app.config['SECRET_KEY'], expires_in=expiration)
log.debug(self.id)
return s.dumps({ 'id': self.id }) return s.dumps({ 'id': self.id })
@staticmethod @staticmethod
def verify_token(token, account): def verify_token(token, accounts):
log.debug(f'verifying token {token}')
s = Serializer(app.config['SECRET_KEY']) s = Serializer(app.config['SECRET_KEY'])
try: try:
data = s.loads(token) data = s.loads(token)
@ -30,27 +55,65 @@ class Account():
return None return None
except BadSignature: except BadSignature:
return None return None
if data['id'] == account.id: if config.getboolean('ui', 'jellyfin_login'):
return account for account in accounts:
if data['id'] == accounts[account].id:
return account
else:
return accounts['adminAccount']
auth = HTTPBasicAuth() 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 @auth.verify_password
def verify_password(username, password): def verify_password(username, password):
user = adminAccount.verify_token(username, adminAccount) user = None
if not user: verified = False
if username == adminAccount.username and adminAccount.verify_password(password): log.debug('Verifying auth')
g.user = adminAccount 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") log.debug("HTTPAuth Allowed")
return True return True
else: else:
log.debug("HTTPAuth Denied") log.debug("HTTPAuth Denied")
return False return False
g.user = adminAccount g.user = user
log.debug("HTTPAuth Allowed") log.debug("HTTPAuth Allowed")
return True return True

View File

@ -7,7 +7,6 @@ import secrets
import time import time
from __main__ import config, config_path, app, g from __main__ import config, config_path, app, g
from __main__ import web_log as log from __main__ import web_log as log
from jellyfin_accounts.login import auth
from jellyfin_accounts.validate_password import PasswordValidator from jellyfin_accounts.validate_password import PasswordValidator
def resp(success=True, code=500): def resp(success=True, code=500):
@ -51,6 +50,8 @@ jf = Jellyfin(config['jellyfin']['server'],
config['jellyfin']['device'], config['jellyfin']['device'],
config['jellyfin']['device_id']) config['jellyfin']['device_id'])
from jellyfin_accounts.login import auth
attempts = 0 attempts = 0
success = False success = False
while attempts != 3: while attempts != 3:
@ -262,7 +263,6 @@ def deleteInvite():
@auth.login_required @auth.login_required
def get_token(): def get_token():
token = g.user.generate_token() token = g.user.generate_token()
log.debug('Token generated')
return jsonify({'token': token.decode('ascii')}) return jsonify({'token': token.decode('ascii')})