├── images ├── jfa.gif ├── admin.png ├── create.png ├── jellyfin-accounts-icon.png ├── jellyfin-accounts-social.png ├── jellyfin-accounts-icon.afdesign ├── jellyfin-accounts-banner.afdesign ├── jellyfin-accounts-banner-wide.afdesign └── README.md ├── jellyfin_accounts ├── data │ ├── static │ │ ├── favicon.ico │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── mstile-150x150.png │ │ ├── apple-touch-icon.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── browserconfig.xml │ │ ├── site.webmanifest │ │ ├── safari-pinned-tab.svg │ │ ├── serialize.js │ │ └── setup.js │ ├── services │ │ └── jf-accounts.service │ ├── templates │ │ ├── invalidCode.html │ │ ├── 404.html │ │ ├── form.html │ │ └── admin.html │ └── config-base.json ├── generate_ini.py ├── invite_daemon.py ├── validate_password.py ├── data_store.py ├── pw_reset.py ├── setup.py ├── web.py ├── login.py ├── config.py ├── email.py ├── jf_api.py ├── __init__.py └── web_api.py ├── mail ├── expired.txt ├── created.txt ├── invite-email.txt ├── email.txt ├── expired.mjml ├── invite-email.mjml ├── email.mjml ├── created.mjml └── generate.py ├── jf-accounts.service ├── README.md ├── .gitignore ├── scss ├── get_node_deps.py ├── README.md ├── compile.py ├── bs4 │ └── bs4-jf.scss └── bs5 │ └── bs5-jf.scss ├── package.json ├── Dockerfile ├── .github └── ISSUE_TEMPLATE │ └── bug_report.md ├── LICENSE ├── pyproject.toml ├── README.old.md ├── config-default.ini └── requirements.txt /images/jfa.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrfee/jellyfin-accounts/HEAD/images/jfa.gif -------------------------------------------------------------------------------- /images/admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrfee/jellyfin-accounts/HEAD/images/admin.png -------------------------------------------------------------------------------- /images/create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrfee/jellyfin-accounts/HEAD/images/create.png -------------------------------------------------------------------------------- /images/jellyfin-accounts-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrfee/jellyfin-accounts/HEAD/images/jellyfin-accounts-icon.png -------------------------------------------------------------------------------- /images/jellyfin-accounts-social.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrfee/jellyfin-accounts/HEAD/images/jellyfin-accounts-social.png -------------------------------------------------------------------------------- /images/jellyfin-accounts-icon.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrfee/jellyfin-accounts/HEAD/images/jellyfin-accounts-icon.afdesign -------------------------------------------------------------------------------- /images/jellyfin-accounts-banner.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrfee/jellyfin-accounts/HEAD/images/jellyfin-accounts-banner.afdesign -------------------------------------------------------------------------------- /jellyfin_accounts/data/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrfee/jellyfin-accounts/HEAD/jellyfin_accounts/data/static/favicon.ico -------------------------------------------------------------------------------- /images/jellyfin-accounts-banner-wide.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrfee/jellyfin-accounts/HEAD/images/jellyfin-accounts-banner-wide.afdesign -------------------------------------------------------------------------------- /jellyfin_accounts/data/static/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrfee/jellyfin-accounts/HEAD/jellyfin_accounts/data/static/favicon-16x16.png -------------------------------------------------------------------------------- /jellyfin_accounts/data/static/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrfee/jellyfin-accounts/HEAD/jellyfin_accounts/data/static/favicon-32x32.png -------------------------------------------------------------------------------- /jellyfin_accounts/data/static/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrfee/jellyfin-accounts/HEAD/jellyfin_accounts/data/static/mstile-150x150.png -------------------------------------------------------------------------------- /jellyfin_accounts/data/static/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrfee/jellyfin-accounts/HEAD/jellyfin_accounts/data/static/apple-touch-icon.png -------------------------------------------------------------------------------- /mail/expired.txt: -------------------------------------------------------------------------------- 1 | Invite expired. 2 | 3 | Code {{ code }} expired at {{ expiry }}. 4 | 5 | Note: Notification emails can be toggled on the admin dashboard. 6 | -------------------------------------------------------------------------------- /jellyfin_accounts/data/static/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrfee/jellyfin-accounts/HEAD/jellyfin_accounts/data/static/android-chrome-192x192.png -------------------------------------------------------------------------------- /jellyfin_accounts/data/static/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrfee/jellyfin-accounts/HEAD/jellyfin_accounts/data/static/android-chrome-512x512.png -------------------------------------------------------------------------------- /mail/created.txt: -------------------------------------------------------------------------------- 1 | A user was created using code {{ code }}. 2 | 3 | Name: {{ username }} 4 | Address: {{ address }} 5 | Time: {{ time }} 6 | 7 | Note: Notification emails can be toggled on the admin dashboard. 8 | -------------------------------------------------------------------------------- /jellyfin_accounts/data/services/jf-accounts.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=A basic account management system for Jellyfin. 3 | 4 | [Service] 5 | ExecStart={executable} 6 | 7 | [Install] 8 | WantedBy=default.target 9 | -------------------------------------------------------------------------------- /images/README.md: -------------------------------------------------------------------------------- 1 | # Images 2 | 3 | This holds any images on the main README, and the base files for the icons and banner. The font used, like Jellyfin, is [Quicksand](https://fonts.google.com/specimen/Quicksand) by Andrew Paglinawan. 4 | -------------------------------------------------------------------------------- /jf-accounts.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=A basic account management system for Jellyfin. 3 | 4 | [Service] 5 | ExecStart=/home/hrfee/.cache/pypoetry/virtualenvs/jellyfin-accounts-r2jcKHws-py3.8/bin/jf-accounts 6 | 7 | [Install] 8 | WantedBy=default.target 9 | -------------------------------------------------------------------------------- /mail/invite-email.txt: -------------------------------------------------------------------------------- 1 | Hi, 2 | You've been invited to Jellyfin. 3 | To join, follow the below link. 4 | This invite will expire on {{ expiry_date }}, at {{ expiry_time }}, which is in {{ expires_in }}, so act quick. 5 | 6 | {{ invite_link }} 7 | 8 | {{ message }} 9 | -------------------------------------------------------------------------------- /jellyfin_accounts/data/static/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #603cba 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 👀 ➡️: Try [jfa-go](https://github.com/hrfee/jfa-go), a rewrite in Go. It's faster and has more features. 2 | ###### I won't be updating this version any more, and switching should be easy. 3 | 4 | **please don't open any new issues.** 5 | 6 | You can find the old README [here](https://github.com/hrfee/jellyfin-accounts/blob/main/README.old.md). 7 | -------------------------------------------------------------------------------- /mail/email.txt: -------------------------------------------------------------------------------- 1 | Hi {{ username }}, 2 | 3 | Someone has recently requests a password reset on Jellyfin. 4 | If this was you, enter the below pin into the prompt. 5 | This code will expire on {{ expiry_date }}, at {{ expiry_time }} UTC, which is in {{ expires_in }}. 6 | If this wasn't you, please ignore this email. 7 | 8 | PIN: {{ pin }} 9 | 10 | {{ message }} 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | notes.md 3 | MANIFEST.in 4 | dist/ 5 | build/ 6 | test.txt 7 | node_modules/ 8 | jellyfin_accounts/data/config-default.ini 9 | *.egg-info/ 10 | pw-reset/ 11 | jfa/ 12 | colors.txt 13 | theme.css 14 | jellyfin_accounts/__pycache__/ 15 | jellyfin_accounts/data/static/*.css 16 | old/ 17 | .jf-accounts/ 18 | requirements.txt 19 | video/ 20 | scss/bs5/*.css* 21 | scss/bs4/*.css* 22 | mail/*.html 23 | jellyfin_accounts/data/*.html 24 | jellyfin_accounts/data/*.txt 25 | -------------------------------------------------------------------------------- /jellyfin_accounts/data/static/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jf-accounts", 3 | "short_name": "jf-accounts", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /scss/get_node_deps.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import subprocess 4 | import os 5 | from pathlib import Path 6 | 7 | 8 | def runcmd(cmd): 9 | if os.name == "nt": 10 | return subprocess.check_output(cmd, shell=True) 11 | proc = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE) 12 | return proc.communicate() 13 | 14 | 15 | print('Installing npm packages') 16 | 17 | root_path = Path(__file__).parents[1] 18 | if os.name == 'nt': 19 | root_path /= 'node_modules' 20 | runcmd(f'npm install') 21 | 22 | if (root_path / 'node_modules' / 'cleancss').exists(): 23 | print(f'Installed successfully in {str((root_path / "node_modules").resolve())}.') 24 | 25 | 26 | -------------------------------------------------------------------------------- /jellyfin_accounts/data/static/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /jellyfin_accounts/data/static/serialize.js: -------------------------------------------------------------------------------- 1 | function serializeForm(id) { 2 | var form = document.getElementById(id); 3 | var formData = {}; 4 | for (var i = 0; i < form.elements.length; i++) { 5 | var el = form.elements[i]; 6 | if (el.type != 'submit') { 7 | var name = el.name; 8 | if (name == '') { 9 | name = el.id; 10 | }; 11 | switch (el.type) { 12 | case 'checkbox': 13 | formData[name] = el.checked; 14 | break; 15 | case 'text': 16 | case 'password': 17 | case 'select-one': 18 | case 'email': 19 | case 'number': 20 | formData[name] = el.value; 21 | break; 22 | }; 23 | }; 24 | }; 25 | return formData; 26 | }; 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jellyfin-accounts", 3 | "version": "1.0.0", 4 | "description": "This is only used for grabbing scss build dependencies, and isn't a real package.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/hrfee/jellyfin-accounts.git" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/hrfee/jellyfin-accounts/issues" 17 | }, 18 | "homepage": "https://github.com/hrfee/jellyfin-accounts#readme", 19 | "dependencies": { 20 | "autoprefixer": "^9.8.5", 21 | "bootstrap": "^5.0.0-alpha1", 22 | "bootstrap4": "npm:bootstrap@^4.5.0", 23 | "clean-css-cli": "^4.3.0", 24 | "lodash": "^4.17.19", 25 | "mjml": "^4.6.3", 26 | "postcss-cli": "^7.1.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /scss/README.md: -------------------------------------------------------------------------------- 1 | ## SCSS 2 | 3 | * `bs<4/5>-jf.scss` contains the source for the customizations to bootstrap. To customize the UI, you can make modifications to this file and then compile it. 4 | 5 | **Note**: It is assumed that Bootstrap 5 is installed in `../../node_modules/bootstrap` relative to itself, and Bootstrap 4 in `../../node_modules/bootstrap4`. 6 | 7 | * Compilation requires dev dependencies (`poetry update`), bootstrap and some extra npm packages. 8 | * If you're buildings from source, you can simply run `poetry run task compile-css` before building to automatically get deps and compile CSS. 9 | * If you are creating custom css, run `poetry run task get-npm-deps` to only install the necessary dependencies. Follow along with the commands `scss/compile.py` runs to build your css and then set `custom_css` in your config as the path to your minified css and change the `theme` option to `Custom CSS`. 10 | 11 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8.2-buster AS build 2 | 3 | COPY . /opt/build 4 | 5 | RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python 6 | 7 | RUN curl -sL https://deb.nodesource.com/setup_14.x | bash - 8 | 9 | RUN cd /opt/build \ 10 | && rm -rf dist \ 11 | && apt install nodejs \ 12 | && ~/.poetry/bin/poetry update \ 13 | && pip install libsass \ 14 | && python scss/get_node_deps.py \ 15 | && python scss/compile.py -y \ 16 | && python mail/generate.py -y \ 17 | && ~/.poetry/bin/poetry build -f wheel 18 | 19 | FROM python:3.8.2-buster 20 | 21 | COPY --from=build /opt/build/dist /opt/dist 22 | 23 | RUN pip install /opt/dist/*.whl 24 | 25 | RUN sed -i 's#id="pwrJfPath" placeholder="Folder"#id="pwrJfPath" value="/jf" disabled#g' /usr/local/lib/python3.8/site-packages/jellyfin_accounts/data/templates/setup.html 26 | 27 | CMD [ "python3.8", "/usr/local/bin/jf-accounts", "-d", "/data" ] 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Template for bug reports. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | 12 | Describe the problem, and what you would expect if it isn't clear already. 13 | 14 | **To Reproduce** 15 | 16 | What to do to reproduce the problem. 17 | 18 | **Logs** 19 | 20 | When you notice the problem, check the output of `jf-accounts`. If the problem is not obvious (e.g an expection or 'ERROR' log), try enabling `debug` in your configuration's `[ui]` section, restarting and reproducing the problem. You should then take a screenshot of the output, or paste it here, preferably between \`\`\` tags (e.g \`\`\``Log here`\`\`\`). 21 | If nothing catches your eye in the log, access the admin page via your browser, go into the console (Right click > Inspect Element > Console), refresh, then paste the output here in the same way as above. 22 | 23 | **Platform** 24 | 25 | Include the platform jf-accounts is running on (e.g Windows, Linux, Docker), the python version, and if necessary the browser version and platform. 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Harvey Tindall 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /jellyfin_accounts/generate_ini.py: -------------------------------------------------------------------------------- 1 | # Generates config file 2 | import configparser 3 | import json 4 | from pathlib import Path 5 | 6 | 7 | def generate_ini(base_file, ini_file, version): 8 | """ 9 | Generates .ini file from config-base file. 10 | """ 11 | with open(Path(base_file), "r") as f: 12 | config_base = json.load(f) 13 | 14 | ini = configparser.RawConfigParser(allow_no_value=True) 15 | 16 | for section in config_base: 17 | ini.add_section(section) 18 | for entry in config_base[section]: 19 | if "description" in config_base[section][entry]: 20 | ini.set(section, "; " + config_base[section][entry]["description"]) 21 | if entry != "meta": 22 | value = config_base[section][entry]["value"] 23 | if isinstance(value, bool): 24 | value = str(value).lower() 25 | else: 26 | value = str(value) 27 | ini.set(section, entry, value) 28 | 29 | ini["jellyfin"]["version"] = version 30 | ini["jellyfin"]["device_id"] = ini["jellyfin"]["device_id"].replace( 31 | "{version}", version 32 | ) 33 | 34 | with open(Path(ini_file), "w") as config_file: 35 | ini.write(config_file) 36 | return True 37 | -------------------------------------------------------------------------------- /jellyfin_accounts/invite_daemon.py: -------------------------------------------------------------------------------- 1 | from threading import Timer 2 | import time 3 | from jellyfin_accounts import config, data_store 4 | from jellyfin_accounts.web_api import checkInvite 5 | 6 | 7 | class Repeat: 8 | def __init__(self, interval, function, *args, **kwargs): 9 | self._timer = None 10 | self.interval = interval 11 | self.function = function 12 | self.args = args 13 | self.kwargs = kwargs 14 | self.is_running = False 15 | self.next_call = time.time() 16 | self.start() 17 | 18 | def _run(self): 19 | self.is_running = False 20 | self.start() 21 | self.function(*self.args, **self.kwargs) 22 | 23 | def start(self): 24 | if not self.is_running: 25 | self.next_call += self.interval 26 | self._timer = Timer(self.next_call - time.time(), self._run) 27 | self._timer.start() 28 | self.is_running = True 29 | 30 | def stop(self): 31 | self._timer.cancel() 32 | self.is_running = False 33 | 34 | 35 | def checkInvites(): 36 | invites = dict(data_store.invites) 37 | # checkInvite already loops over everything, no point running it multiple times. 38 | if len(invites) != 0: 39 | checkInvite(list(invites.keys())[0]) 40 | 41 | 42 | if config.getboolean("notifications", "enabled"): 43 | inviteDaemon = Repeat(60, checkInvites) 44 | -------------------------------------------------------------------------------- /mail/expired.mjml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | jellyfin-accounts 18 | 19 | 20 | 21 | 22 | 23 | Invite Expired. 24 | Code {{ code }} expired at {{ expiry }}. 25 | 26 | 27 | 28 | 29 | 30 | 31 | Notification emails can be toggled on the admin dashboard. 32 | 33 | 34 | 35 |
Code {{ code }} expired at {{ expiry }}.