From d321726d62fb11be3f9958b7b50e2381fd3bccfd Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Sat, 11 Apr 2020 15:20:25 +0100 Subject: [PATCH] first commit --- .gitignore | 6 ++ LICENSE | 21 ++++ README.md | 87 ++++++++++++++++ data/config-default.ini | 39 +++++++ data/static/admin.js | 173 ++++++++++++++++++++++++++++++++ data/templates/404.html | 26 +++++ data/templates/admin.html | 99 ++++++++++++++++++ data/templates/form.html | 90 +++++++++++++++++ data/templates/invalidCode.html | 25 +++++ images/admin.png | Bin 0 -> 32899 bytes images/create.png | Bin 0 -> 38222 bytes jellyfin_accounts/__init__.py | 0 jellyfin_accounts/jf_api.py | 82 +++++++++++++++ jellyfin_accounts/login.py | 55 ++++++++++ jellyfin_accounts/web.py | 41 ++++++++ jellyfin_accounts/web_api.py | 150 +++++++++++++++++++++++++++ jf-accounts | 99 ++++++++++++++++++ setup.py | 42 ++++++++ 18 files changed, 1035 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 data/config-default.ini create mode 100644 data/static/admin.js create mode 100644 data/templates/404.html create mode 100644 data/templates/admin.html create mode 100644 data/templates/form.html create mode 100644 data/templates/invalidCode.html create mode 100644 images/admin.png create mode 100644 images/create.png create mode 100644 jellyfin_accounts/__init__.py create mode 100644 jellyfin_accounts/jf_api.py create mode 100644 jellyfin_accounts/login.py create mode 100644 jellyfin_accounts/web.py create mode 100644 jellyfin_accounts/web_api.py create mode 100755 jf-accounts create mode 100644 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bc7b66a --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +__pycache__ +notes.md +MANIFEST.in +dist/ +build/ +*.egg-info/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..dc91e2a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Harvey Tindall + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1e8ee6f --- /dev/null +++ b/README.md @@ -0,0 +1,87 @@ +# jellyfin-accounts +A simple, web-based invite system for [Jellyfin](https://github.com/jellyfin/jellyfin). +* Uses a basic python jellyfin API client for communication with the server. +* Uses [Flask](https://github.com/pallets/flask), [HTTPAuth](https://github.com/miguelgrinberg/Flask-HTTPAuth), [itsdangerous](https://github.com/pallets/itsdangerous), and [Waitress](https://github.com/Pylons/waitress) +* Frontend uses [Bootstrap](https://getbootstrap.com), [jQuery](https://jquery.com) and [jQuery-serialize-object](https://github.com/macek/jquery-serialize-object) +## Screenshots +

+ +

+ +## Install +``` +git clone https://github.com/hrfee/jellyfin-accounts.git +cd jellyfin-accounts +python3 setup.py install +``` + +### Usage +* Passing no arguments will run the server +``` +usage: jf-accounts [-h] [-c CONFIG] [-d DATA] [--host HOST] [-p PORT] [-g] + +jellyfin-accounts + +optional arguments: + -h, --help show this help message and exit + -c CONFIG, --config CONFIG + specifies path to configuration file. + -d DATA, --data DATA specifies directory to store data in. defaults to + ~/.jf-accounts. + --host HOST address to host web ui on. + -p PORT, --port PORT port to host web ui on. + -g, --get_policy tool to grab a JF users policy (access, perms, etc.) + and output as json to be used as a user template. +``` +### Setup +#### Policy template +* You may want to restrict from accessing certain libraries (e.g 4K Movies), or display their account on the login screen by default. Jellyfin stores these settings as a user's policy. +* Make a temporary account and change its settings, then run `jf-accounts --get_policy`. Choose your user, and the policy will be stored at the location you set in `user_template`, and used for all subsequent new accounts. + +### Configuration +On first run, the default configuration is copied to `~/.jf-accounts/config.ini`. +``` +; It is reccommended to create a limited admin account for this program. +[jellyfin] +username = username +password = password +; Server will also be used in the invite form, so make sure it's publicly accessible. +server = https://jellyf.in:443 +client = jf-accounts +version = 0.1 +device = jf-accounts +device_id = jf-accounts-0.1 + +[ui] +host = 127.0.0.1 +port = 8056 +username = your username +password = your password +debug = false +; Enable to store request email address and store. Useful for sending password reset emails. +emails_enabled = false + +; Displayed at the bottom of all pages except admin. +contact_message = Need help? contact me. +; Displayed at top of form page. +help_message = Enter your details to create an account. +; Displayed when an account is created. +success_message = Your account has been created. Click below to continue to Jellyfin. + + +[files] +; When the below paths are left blank, files are stored in ~/.jf-accounts/. + +; Path to store valid invites. +invites = +; Path to store emails in JSON +emails = +; Path to the user policy template. Can be acquired with get-template. +user_template = +``` + +### Todo +* Fix pip install (possible related to using `data_files` over `package_data`?) +* Properly integrate with a janky password reset email system i've written. +* Improve `generateInvites` in admin.js to refresh each invite's data, instead of deleting and recreating them. +* Fix weird alignment of invite codes and the generate button (I know, i'm very new to web development) diff --git a/data/config-default.ini b/data/config-default.ini new file mode 100644 index 0000000..25bfeb7 --- /dev/null +++ b/data/config-default.ini @@ -0,0 +1,39 @@ +[jellyfin] +; It is reccommended to create a limited admin account for this program. +username = username +password = password +; Server will also be used in the invite form, so make sure it's publicly accessible. +server = https://jellyf.in:443 +client = jf-accounts +version = 0.1 +device = jf-accounts +device_id = jf-accounts-0.1 + +[ui] +host = 127.0.0.1 +port = 8056 +username = your username +password = your password +debug = false +; Enable to store request email address and store. Useful for sending password reset emails. +emails_enabled = false + +; Displayed at the bottom of all pages except admin. +contact_message = Need help? contact me. +; Displayed at top of form page. +help_message = Enter your details to create an account. +; Displayed when an account is created. +success_message = Your account has been created. Click below to continue to Jellyfin. + + +[files] +; When the below paths are left blank, files are stored in ~/.jf-accounts/. + +; Path to store valid invites. +invites = +; Path to store emails in JSON +emails = +; Path to the user policy template. Can be acquired with get-template. +user_template = + + diff --git a/data/static/admin.js b/data/static/admin.js new file mode 100644 index 0000000..96225b5 --- /dev/null +++ b/data/static/admin.js @@ -0,0 +1,173 @@ +function addItem(invite) { + var links = document.getElementById('invites'); + var listItem = document.createElement('li'); + listItem.classList.add('list-group-item'); + listItem.classList.add('align-middle'); + var listCode = document.createElement('div'); + listCode.classList.add('float-left'); + var codeLink = document.createElement('a'); + codeLink.appendChild(document.createTextNode(invite[0])); + listCode.appendChild(codeLink); + listItem.appendChild(listCode); + var listRight = document.createElement('div'); + listRight.classList.add('float-right'); + listRight.appendChild(document.createTextNode(invite[1])); + if (invite[2] == 0) { + var inviteCode = window.location.href + 'invite/' + invite[0]; + codeLink.href = inviteCode; + listCode.appendChild(document.createTextNode(" ")); + var codeCopy = document.createElement('i'); + codeCopy.onclick = function(){toClipboard(inviteCode)}; + codeCopy.classList.add('fa'); + codeCopy.classList.add('fa-clipboard'); + listCode.appendChild(codeCopy); + var listDelete = document.createElement('button'); + listDelete.onclick = function(){deleteInvite(invite[0])}; + listDelete.classList.add('btn'); + listDelete.classList.add('btn-outline-danger'); + listDelete.appendChild(document.createTextNode('Delete')); + listRight.appendChild(listDelete); + }; + listItem.appendChild(listRight); + links.appendChild(listItem); +}; +function generateInvites(empty = false) { + document.getElementById('invites').textContent = ''; + if (empty === false) { + $.ajax('/getInvites', { + type : 'GET', + dataType : 'json', + contentType: 'json', + xhrFields : { + withCredentials: true + }, + beforeSend : function (xhr) { + xhr.setRequestHeader("Authorization", "Basic " + btoa(window.token + ":")); + }, + data: { get_param: 'value' }, + complete: function(response) { + var data = JSON.parse(response['responseText']); + if (data['invites'].length == 0) { + addItem(["None", "", "1"]); + } else { + data['invites'].forEach(function(item) { + var i = ["", "", "0"]; + i[0] = item['code']; + if (item['hours'] == 0) { + i[1] = item['minutes'] + 'm'; + } else if (item['minutes'] == 0) { + i[1] = item['hours'] + 'h'; + } else { + i[1] = item['hours'] + 'h ' + item['minutes'] + 'm'; + } + i[1] = "Expires in " + i[1] + " "; + addItem(i) + }); + } + } + }); + } else if (empty === true) { + addItem(["None", "", "1"]); + }; +}; +function deleteInvite(code) { + var send = JSON.stringify({ "code": code }); + $.ajax('/deleteInvite', { + data : send, + contentType : 'application/json', + type : 'POST', + xhrFields : { + withCredentials: true + }, + beforeSend : function (xhr) { + xhr.setRequestHeader("Authorization", "Basic " + btoa(window.token + ":")); + }, + success: function() { generateInvites(); }, + }); +}; +function addOptions(le, sel) { + for (v = 0; v <= le; v++) { + var opt = document.createElement('option'); + opt.appendChild(document.createTextNode(v)) + opt.value = v + sel.appendChild(opt) + } +}; +function toClipboard(str) { + const el = document.createElement('textarea'); + el.value = str; + el.setAttribute('readonly', ''); + el.style.position = 'absolute'; + el.style.left = '-9999px'; + document.body.appendChild(el); + const selected = + document.getSelection().rangeCount > 0 + ? document.getSelection().getRangeAt(0) + : false; + el.select(); + document.execCommand('copy'); + document.body.removeChild(el); + if (selected) { + document.getSelection().removeAllRanges(); + document.getSelection().addRange(selected); + } +}; +$("form#inviteForm").submit(function() { + var send = $("form#inviteForm").serializeJSON(); + $.ajax('/generateInvite', { + data : send, + contentType : 'application/json', + type : 'POST', + xhrFields : { + withCredentials: true + }, + beforeSend : function (xhr) { + xhr.setRequestHeader("Authorization", "Basic " + btoa(window.token + ":")); + }, + success: function() { generateInvites(); }, + + }); + return false; +}); +$("form#loginForm").submit(function() { + window.token = ""; + var details = $("form#loginForm").serializeObject(); + $.ajax('/getToken', { + type : 'GET', + dataType : 'json', + contentType: 'json', + xhrFields : { + withCredentials: true + }, + beforeSend : function (xhr) { + xhr.setRequestHeader("Authorization", "Basic " + btoa(details['username'] + ":" + details['password'])); + }, + data: { get_param: 'value' }, + complete: function(data) { + if (data['status'] == 401) { + var formBody = document.getElementById('formBody'); + var wrongPassword = document.createElement('div'); + wrongPassword.classList.add('alert'); + wrongPassword.classList.add('alert-danger'); + wrongPassword.setAttribute('role', 'alert'); + wrongPassword.appendChild(document.createTextNode('Incorrect username or password.')); + formBody.appendChild(wrongPassword); + } else { + window.token = JSON.parse(data['responseText'])['token']; + generateInvites(); + var interval = setInterval(function() { generateInvites(); }, 60 * 1000); + var hour = document.getElementById('hours'); + addOptions(24, hour); + hour.selected = "0"; + var minutes = document.getElementById('minutes'); + addOptions(59, minutes); + minutes.selected = "30"; + $('#login').modal('hide'); + } + } + }); + return false; +}); +generateInvites(empty = true); +$("#login").modal('show'); + diff --git a/data/templates/404.html b/data/templates/404.html new file mode 100644 index 0000000..e84af4d --- /dev/null +++ b/data/templates/404.html @@ -0,0 +1,26 @@ + + + + + +Create Jellyfin Account + + + + + + + + +
+

Page not found.

+

+ {{ contactMessage }} +

+
+ + diff --git a/data/templates/admin.html b/data/templates/admin.html new file mode 100644 index 0000000..624acda --- /dev/null +++ b/data/templates/admin.html @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + Admin + + + +

+ Accounts admin +

+
+ Current Invites +
+
+
+
+ Generate Invite +
+
+
+
+ + + + + +
+
+
+
+
+
+

{{ contactMessage }}

+
+ + + diff --git a/data/templates/form.html b/data/templates/form.html new file mode 100644 index 0000000..eac866b --- /dev/null +++ b/data/templates/form.html @@ -0,0 +1,90 @@ + + + + + + + + + + + + + Create Jellyfin Account + + + +
+

+ Create Account +

+

{{ helpMessage }}

+
+
+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+

{{ contactMessage }}

+
+ + + diff --git a/data/templates/invalidCode.html b/data/templates/invalidCode.html new file mode 100644 index 0000000..118a60b --- /dev/null +++ b/data/templates/invalidCode.html @@ -0,0 +1,25 @@ + + + + + +Create Jellyfin Account + + + + + + + + +
+

Invalid Code.

+

The above code is either incorrect, or has expired.

+

{{ contactMessage }}

+
+ + diff --git a/images/admin.png b/images/admin.png new file mode 100644 index 0000000000000000000000000000000000000000..281446297b674e371284be1bbbd4b43b10878b43 GIT binary patch literal 32899 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 baKm<0jCJ{qZB-3$j|3*8_%cuG&kz3tB)^g9 literal 0 HcmV?d00001 diff --git a/images/create.png b/images/create.png new file mode 100644 index 0000000000000000000000000000000000000000..485646bf669ebd9b4857602e1a8b3f8be9b2b774 GIT binary patch literal 38222 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 literal 0 HcmV?d00001 diff --git a/jellyfin_accounts/__init__.py b/jellyfin_accounts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/jellyfin_accounts/jf_api.py b/jellyfin_accounts/jf_api.py new file mode 100644 index 0000000..0176f2c --- /dev/null +++ b/jellyfin_accounts/jf_api.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +import requests + + +class Error(Exception): + pass + +class Jellyfin: + class UserExistsError(Error): + pass + class UserNotFoundError(Error): + pass + class AuthenticationError(Error): + pass + class AuthenticationRequiredError(Error): + pass + def __init__(self, server, client, version, device, deviceId): + self.server = server + self.client = client + self.version = version + self.device = device + self.deviceId = deviceId + self.useragent = f"{self.client}/{self.version}" + self.auth = "MediaBrowser " + self.auth += f"Client={self.client}, " + self.auth += f"Device={self.device}, " + self.auth += f"DeviceId={self.deviceId}, " + self.auth += f"Version={self.version}" + self.header = { + "Accept": "application/json", + "Content-type": "application/x-www-form-urlencoded; charset=UTF-8", + "X-Application": f"{self.client}/{self.version}", + "Accept-Charset": "UTF-8,*", + "Accept-encoding": "gzip", + "User-Agent": self.useragent, + "X-Emby-Authorization": self.auth + } + def getUsers(self, username="all"): + response = requests.get(self.server+"/emby/Users/Public").json() + if username == "all": + return response + else: + match = False + for user in response: + if user['Name'] == username: + match = True + return user + if not match: + raise self.UserNotFoundError + def authenticate(self, username, password): + self.username = username + self.password = password + response = requests.post(self.server+"/emby/Users/AuthenticateByName", + headers=self.header, + params={'Username': self.username, + 'Pw': self.password}) + if response.status_code == 200: + json = response.json() + self.userId = json['User']['Id'] + self.accessToken = json['AccessToken'] + self.auth += f", Token={self.accessToken}" + self.header['X-Emby-Authorization'] = self.auth + return True + else: + raise self.AuthenticationError + def setPolicy(self, userId, policy): + return requests.post(self.server+"/Users/"+userId+"/Policy", + headers=self.header, + params=policy) + def newUser(self, username, password): + for user in self.getUsers(): + if user['Name'] == username: + raise self.UserExistsError + response = requests.post(self.server+"/emby/Users/New", + headers=self.header, + params={'Name': username, + 'Password': password}) + if response.status_code == 401: + raise self.AuthenticationRequiredError + return response + +# template user's policies should be copied to each new account. diff --git a/jellyfin_accounts/login.py b/jellyfin_accounts/login.py new file mode 100644 index 0000000..7a213a1 --- /dev/null +++ b/jellyfin_accounts/login.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# from flask import g + +from flask_httpauth import HTTPBasicAuth +from itsdangerous import (TimedJSONWebSignatureSerializer + as Serializer, BadSignature, SignatureExpired) +from passlib.apps import custom_app_context as pwd_context +import uuid +from __main__ import config, app, g + + +class Account(): + def __init__(self, username, password): + self.username = username + self.password_hash = pwd_context.hash(password) + self.id = str(uuid.uuid4()) + def verify_password(self, password): + return pwd_context.verify(password, self.password_hash) + def generate_token(self, expiration=1200): + s = Serializer(app.config['SECRET_KEY'], expires_in=expiration) + return s.dumps({ 'id': self.id }) + + @staticmethod + def verify_token(token, account): + s = Serializer(app.config['SECRET_KEY']) + try: + data = s.loads(token) + except SignatureExpired: + return None + except BadSignature: + return None + if data['id'] == account.id: + return account + +auth = HTTPBasicAuth() + + +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 + print(g) + return True + else: + return False + g.user = adminAccount + return True + + + diff --git a/jellyfin_accounts/web.py b/jellyfin_accounts/web.py new file mode 100644 index 0000000..f8a9059 --- /dev/null +++ b/jellyfin_accounts/web.py @@ -0,0 +1,41 @@ +from pathlib import Path +from flask import Flask, send_from_directory, render_template +from __main__ import config, app, g + + +@app.errorhandler(404) +def page_not_found(e): + return render_template('404.html', + contactMessage=config['ui']['contact_message']), 404 + + +@app.route('/', methods=['GET', 'POST']) +def admin(): + # return app.send_static_file('admin.html') + return render_template('admin.html', + contactMessage='') + + +@app.route('/') +def static_proxy(path): + if 'form.html' not in path and 'admin.html' not in path: + return app.send_static_file(path) + return render_template('404.html', + contactMessage=config['ui']['contact_message']), 404 + +from jellyfin_accounts.web_api import checkInvite + + +@app.route('/invite/') +def inviteProxy(path): + if checkInvite(path): + return render_template('form.html', + contactMessage=config['ui']['contact_message'], + helpMessage=config['ui']['help_message'], + successMessage=config['ui']['success_message'], + jfLink=config['jellyfin']['server']) + elif 'admin.html' not in path and 'admin.html' not in path: + return app.send_static_file(path) + else: + return render_template('invalidCode.html', + contactMessage=config['ui']['contact_message']) diff --git a/jellyfin_accounts/web_api.py b/jellyfin_accounts/web_api.py new file mode 100644 index 0000000..6135198 --- /dev/null +++ b/jellyfin_accounts/web_api.py @@ -0,0 +1,150 @@ +from flask import request, jsonify +from jellyfin_accounts.jf_api import Jellyfin +import json +import datetime +import secrets +from __main__ import config, app, g +from jellyfin_accounts.login import auth + +def resp(success=True, code=500): + if success: + r = jsonify({'success': True}) + r.status_code = 200 + else: + r = jsonify({'success': False}) + r.status_code = code + return r + + +def checkInvite(code, delete=False): + current_time = datetime.datetime.now() + try: + with open(config['files']['invites'], 'r') as f: + invites = json.load(f) + except (FileNotFoundError, json.decoder.JSONDecodeError): + invites = {'invites': []} + valid = False + for index, i in enumerate(invites['invites']): + expiry = datetime.datetime.strptime(i['valid_till'], + '%Y-%m-%dT%H:%M:%S.%f') + if current_time >= expiry: + del invites['invites'][index] + else: + if i['code'] == code: + valid = True + if delete: + del invites['invites'][index] + with open(config['files']['invites'], 'w') as f: + f.write(json.dumps(invites, indent=4, default=str)) + return valid + + +jf = Jellyfin(config['jellyfin']['server'], + config['jellyfin']['client'], + config['jellyfin']['version'], + config['jellyfin']['device'], + config['jellyfin']['device_id']) + +jf.authenticate(config['jellyfin']['username'], + config['jellyfin']['password']) + + +@app.route('/newUser', methods=['GET', 'POST']) +def newUser(): + data = request.get_json() + if checkInvite(data['code'], delete=True): + user = jf.newUser(data['username'], data['password']) + if user.status_code == 200: + try: + with open(config['files']['user_template'], 'r') as f: + default_policy = json.load(f) + jf.setPolicy(user.json()['Id'], default_policy) + except: + pass + if config['ui']['emails_enabled'] == 'true': + try: + with open(config['files']['emails'], 'r') as f: + emails = json.load(f) + except (FileNotFoundError, json.decoder.JSONDecodeError): + emails = {} + emails[data['username']] = data['email'] + with open(config['files']['emails'], 'w') as f: + f.write(json.dumps(emails, indent=4)) + return resp() + else: + return resp(False) + else: + return resp(False, code=401) + + +@app.route('/generateInvite', methods=['GET', 'POST']) +@auth.login_required +def generateInvite(): + current_time = datetime.datetime.now() + data = request.get_json() + delta = datetime.timedelta(hours=int(data['hours']), + minutes=int(data['minutes'])) + invite = {'code': secrets.token_urlsafe(16)} + invite['valid_till'] = (current_time + + delta).strftime('%Y-%m-%dT%H:%M:%S.%f') + try: + with open(config['files']['invites'], 'r') as f: + invites = json.load(f) + except (FileNotFoundError, json.decoder.JSONDecodeError): + invites = {'invites': []} + invites['invites'].append(invite) + with open(config['files']['invites'], 'w') as f: + f.write(json.dumps(invites, indent=4, default=str)) + return resp() + + +@app.route('/getInvites', methods=['GET']) +@auth.login_required +def getInvites(): + current_time = datetime.datetime.now() + try: + with open(config['files']['invites'], 'r') as f: + invites = json.load(f) + except (FileNotFoundError, json.decoder.JSONDecodeError): + invites = {'invites': []} + response = {'invites': []} + for index, i in enumerate(invites['invites']): + expiry = datetime.datetime.strptime(i['valid_till'], '%Y-%m-%dT%H:%M:%S.%f') + if current_time >= expiry: + del invites['invites'][index] + else: + valid_for = expiry - current_time + response['invites'].append({ + 'code': i['code'], + 'hours': valid_for.seconds//3600, + 'minutes': (valid_for.seconds//60) % 60}) + with open(config['files']['invites'], 'w') as f: + f.write(json.dumps(invites, indent=4, default=str)) + return jsonify(response) + + +@app.route('/deleteInvite', methods=['POST']) +@auth.login_required +def deleteInvite(): + code = request.get_json()['code'] + try: + with open(config['files']['invites'], 'r') as f: + invites = json.load(f) + except (FileNotFoundError, json.decoder.JSONDecodeError): + invites = {'invites': []} + for index, i in enumerate(invites['invites']): + if i['code'] == code: + del invites['invites'][index] + with open(config['files']['invites'], 'w') as f: + f.write(json.dumps(invites, indent=4, default=str)) + return resp() + + +@app.route('/getToken') +@auth.login_required +def get_token(): + token = g.user.generate_token() + return jsonify({'token': token.decode('ascii')}) + + + diff --git a/jf-accounts b/jf-accounts new file mode 100755 index 0000000..3a5acba --- /dev/null +++ b/jf-accounts @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 + +import secrets +import configparser +import shutil +import argparse +from pathlib import Path +from flask import Flask, g + +parser = argparse.ArgumentParser(description="jellyfin-accounts") + +parser.add_argument("-c", "--config", + help="specifies path to configuration file.") +parser.add_argument("-d", "--data", + help=("specifies directory to store data in. " + + "defaults to ~/.jf-accounts.")) +parser.add_argument("--host", + help="address to host web ui on.") +parser.add_argument("-p", "--port", + help="port to host web ui on.") +parser.add_argument("-g", "--get_policy", + help=("tool to grab a JF users " + + "policy (access, perms, etc.) and " + + "output as json to be used as a user template."), + action='store_true') + +args, leftovers = parser.parse_known_args() + +if args.data is not None: + data_dir = Path(args.data) +else: + data_dir = Path.home() / '.jf-accounts' + +local_dir = (Path(__file__).parents[2] / 'data').resolve() + +if not data_dir.exists(): + Path.mkdir(data_dir) + print(f'Config dir not found, so created at {str(data_dir)}') + if args.config is None: + config_path = data_dir / 'config.ini' + shutil.copy(str(local_dir / 'config-default.ini'), + str(config_path)) + print("Edit the configuration and restart.") + raise SystemExit + else: + config_path = Path(args.config) + print(f'config.ini can be found at {str(config_path)}') +else: + config_path = data_dir / 'config.ini' + +config = configparser.ConfigParser() +config.read(config_path) + +if args.host is not None: + config['ui']['host'] = args.host +if args.port is not None: + config['ui']['port'] = args.port + +for key in config['files']: + if config['files'][key] == '': + config['files'][key] = str(data_dir / (key + '.json')) + +if args.get_policy: + import json + from jellyfin_accounts.jf_api import Jellyfin + jf = Jellyfin(config['jellyfin']['server'], + config['jellyfin']['client'], + config['jellyfin']['version'], + config['jellyfin']['device'], + config['jellyfin']['device_id']) + # No auth needed. + print("Make sure the user is publicly visible!") + users = jf.getUsers() + for index, user in enumerate(users): + print(f'{index+1}) {user["Name"]}') + success = False + while not success: + try: + policy = users[int(input(">: "))-1]['Policy'] + success = True + except (ValueError, IndexError): + pass + with open(config['files']['user_template'], 'w') as f: + f.write(json.dumps(policy, indent=4)) + print(f'Policy written to "{config["files"]["user_template"]}".') + print('In future, this policy will be copied to all new users.') +else: + app = Flask(__name__, root_path=str(local_dir)) + app.config['DEBUG'] = config.getboolean('ui', 'debug') + app.config['SECRET_KEY'] = secrets.token_urlsafe(16) + + if __name__ == '__main__': + import jellyfin_accounts.web_api + import jellyfin_accounts.web + print(jellyfin_accounts.web.__file__) + from waitress import serve + serve(app, + host=config['ui']['host'], + port=int(config['ui']['port'])) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..5f920f8 --- /dev/null +++ b/setup.py @@ -0,0 +1,42 @@ +from setuptools import find_packages, setup + +with open('README.md', 'r') as f: + long_description = f.read() + +setup( + name='jellyfin-accounts', + version='0.1', + scripts=['jf-accounts'], + author="Harvey Tindall", + author_email="hrfee@protonmail.ch", + description="A simple invite system for Jellyfin", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/hrfee/jellyfin-accounts", + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], + packages=find_packages(), + # include_package_data=True, + data_files=[('data', ['data/config-default.ini']), + ('data/static', ['data/static/admin.js']), + ('data/templates', [ + 'data/templates/404.html', + 'data/templates/invalidCode.html', + 'data/templates/admin.html', + 'data/templates/form.html'])], + zip_safe=False, + install_requires=[ + 'flask', + 'flask_httpauth', + 'requests', + 'itsdangerous', + 'passlib', + 'secrets', + 'configparser', + 'waitress', + ], +) +