From 89d686ec1fc4cc643f7226dd4ae461cca5217fb7 Mon Sep 17 00:00:00 2001 From: bobokun <12660469+bobokun@users.noreply.github.com> Date: Sat, 13 Sep 2025 09:54:10 -0400 Subject: [PATCH] 4.6.2 (#950) # Improvements - Adds better validation for security passwords # Bug Fixes - Conditionally skip permission validation and setting on Windows systems - Improve cross-platform compatibility for authentication settings handling - Fixes bug where users cannot set up initial security through the webUI **Full Changelog**: https://github.com/StuffAnThings/qbit_manage/compare/v4.6.1...v4.6.2 --------- Signed-off-by: dependabot[bot] Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Actionbot --- CHANGELOG | 21 +++-------- Makefile | 1 + VERSION | 2 +- desktop/tauri/src-tauri/Cargo.lock | 2 +- desktop/tauri/src-tauri/Cargo.toml | 2 +- desktop/tauri/src-tauri/tauri.conf.json | 2 +- docs/Commands.md | 2 +- docs/Config-Setup.md | 5 ++- modules/auth.py | 49 ++++++++++++++----------- modules/web_api.py | 15 ++++++-- pyproject.toml | 2 +- uv.lock | 48 ++++++++++++------------ web-ui/js/components/security.js | 11 ++++++ 13 files changed, 91 insertions(+), 71 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 83990ca..998a30e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,20 +1,9 @@ -# Requirements Updated -- "argon2-cffi==25.1.0" -- "slowapi==0.1.9" -- "ruff==0.12.12" - -# New Features -- Adds authentication support for the webUI and webAPI (Fixes #867) - # Improvements -- Enhanced `--web-server` option to support disabling with `--web-server=False` while maintaining backward compatibility -- The `schedule.yml` is now renamed to `qbm_settings.yml` in order to support security features (Automatic migration) -- Makes hyperlinks clickable in the webUI (Fixes #938) +- Adds better validation for security passwords # Bug Fixes -- Better support for windows paths when using remote_dir -- Fix `QBT_CONFIG_DIR` not supporting folders with subdirectories (Fixes #934) -- Fixes webUI not being packaged with PyPi builds -- Fix bug in remove_orphaned where it's not able to start a new thread in concurrent runs +- Conditionally skip permission validation and setting on Windows systems +- Improve cross-platform compatibility for authentication settings handling +- Fixes bug where users cannot set up initial security through the webUI -**Full Changelog**: https://github.com/StuffAnThings/qbit_manage/compare/v4.6.0...v4.6.1 +**Full Changelog**: https://github.com/StuffAnThings/qbit_manage/compare/v4.6.1...v4.6.2 diff --git a/Makefile b/Makefile index 5e59a1c..63ebbb2 100644 --- a/Makefile +++ b/Makefile @@ -300,6 +300,7 @@ prep-release: patch=$$(echo "$$new_version" | cut -d. -f3); \ prev_patch=$$((patch - 1)); \ prev_version="$$major.$$minor.$$prev_patch"; \ + git fetch origin master:master \ updated_deps=$$(git diff master..HEAD -- pyproject.toml | grep '^+' | grep '==' | sed 's/^+//' | sed 's/^ *//' | sed 's/,$$//' | sed 's/^/- /'); \ echo "# Requirements Updated" > CHANGELOG; \ if [ -n "$$updated_deps" ]; then \ diff --git a/VERSION b/VERSION index 8ac28bf..c78c496 100755 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -4.6.1 +4.6.2 diff --git a/desktop/tauri/src-tauri/Cargo.lock b/desktop/tauri/src-tauri/Cargo.lock index 7a6d755..5331626 100644 --- a/desktop/tauri/src-tauri/Cargo.lock +++ b/desktop/tauri/src-tauri/Cargo.lock @@ -3003,7 +3003,7 @@ dependencies = [ [[package]] name = "qbit-manage-desktop" -version = "4.6.0" +version = "4.6.2" dependencies = [ "glib 0.20.12", "libc", diff --git a/desktop/tauri/src-tauri/Cargo.toml b/desktop/tauri/src-tauri/Cargo.toml index d8b6630..ca83500 100644 --- a/desktop/tauri/src-tauri/Cargo.toml +++ b/desktop/tauri/src-tauri/Cargo.toml @@ -43,7 +43,7 @@ license = "MIT" name = "qbit-manage-desktop" repository = "" rust-version = "1.70" -version = "4.6.1" +version = "4.6.2" [target."cfg(unix)".dependencies] glib = "0.20.0" diff --git a/desktop/tauri/src-tauri/tauri.conf.json b/desktop/tauri/src-tauri/tauri.conf.json index 1887a94..4a65fc2 100644 --- a/desktop/tauri/src-tauri/tauri.conf.json +++ b/desktop/tauri/src-tauri/tauri.conf.json @@ -68,5 +68,5 @@ }, "identifier": "com.qbitmanage.desktop", "productName": "qBit Manage", - "version": "4.6.1" + "version": "4.6.2" } \ No newline at end of file diff --git a/docs/Commands.md b/docs/Commands.md index 3c883a2..789ac82 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -17,7 +17,7 @@ | `-ru` or `--rem-unregistered` | QBT_REM_UNREGISTERED | rem_unregistered | Use this if you would like to remove unregistered torrents. (It will the delete data & torrent if it is not being cross-seeded, otherwise it will just remove the torrent without deleting data). Trackers that have an error and not covered by the remove unregistered logic will also be tagged as `issue` for manual review. | False | | `-tte` or `--tag-tracker-error` | QBT_TAG_TRACKER_ERROR | tag_tracker_error | Use this if you would like to tag torrents that do not have a working tracker. | False | | `-ro` or `--rem-orphaned` | QBT_REM_ORPHANED | rem_orphaned | Use this if you would like to remove orphaned files from your `root_dir` directory that are not referenced by any torrents. It will scan your `root_dir` directory and compare it with what is in qBittorrent. Any data not referenced in qBittorrent will be moved into `/data/torrents/orphaned_data` folder for you to review/delete. | False | -| `-tnhl` or `--tag-nohardlinks` | QBT_TAG_NOHARDLINKS | tag_nohardlinks | Use this to tag any torrents that do not have any hard links associated with any of the files. This is useful for those that use Sonarr/Radarr that hard links your media files with the torrents for seeding. When files get upgraded they no longer become linked with your media therefore will be tagged with a new tag noHL. You can then safely delete/remove these torrents to free up any extra space that is not being used by your media folder. | False | +| `-tnhl` or `--tag-nohardlinks` | QBT_TAG_NOHARDLINKS | tag_nohardlinks | Use this to tag any torrents where the torrent's largest file does not have any hardlinks associated with any of the files outside of your Qbit_Manage root directory. This is useful for those that use Sonarr/Radarr that hard links your media files with the torrents for seeding. When files get upgraded they no longer become linked with your media therefore will be tagged with a new tag noHL. You can then safely delete/remove these torrents to free up any extra space that is not being used by your media folder. Refer to the qbm hardlinks functionality documentation for details. | False | | `-sl` or `--share-limits` | QBT_SHARE_LIMITS | share_limits | Control how torrent share limits are set depending on the priority of your grouping. Each torrent will be matched with the share limit group with the highest priority that meets the group filter criteria. Each torrent can only be matched with one share limit group. | False | | `-sc` or `--skip-cleanup` | QBT_SKIP_CLEANUP | skip_cleanup | Use this to skip emptying the Recycle Bin folder (`/root_dir/.RecycleBin`) and Orphaned directory. (`/root_dir/orphaned_data`) | False | | `-dr` or `--dry-run` | QBT_DRY_RUN | dry_run | If you would like to see what is gonna happen but not actually move/delete or tag/categorize anything. | False | diff --git a/docs/Config-Setup.md b/docs/Config-Setup.md index c1756d6..57571f4 100644 --- a/docs/Config-Setup.md +++ b/docs/Config-Setup.md @@ -144,7 +144,7 @@ This section defines the tags used based upon the tracker's URL. | `cat` | Set the category based on tracker URL. This category option takes priority over the category defined in [cat](#cat) | None |
| | `notifiarr` | Set this to the notifiarr react name. This is used to add indexer reactions to the notifications sent by Notifiarr | None |
| -If you are unsure what key word to use. Simply select a torrent within qB and down at the bottom you should see a tab that says `Trackers` within the list that is populated there are ea list of trackers that are associated with this torrent, select a keyword from there and add it to the config file. Make sure this key word is unique enough that the script will not get confused with any other tracker. +If you are unsure what key word to use. Simply select a torrent within qB and down at the bottom you should see a tab that says `Trackers` within the list that is populated there are ea list of trackers that are associated with this torrent, select a keyword from there and add it to the config file. Make sure this keyword is unique enough that the script will not get confused with any other tracker. >[!TIP] > The `other` key is a special keyword and if defined will tag any other trackers that don't match the above trackers into this tag. @@ -168,6 +168,9 @@ If you're needing information regarding hardlinks here are some excellent resour > Mandatory to fill out [directory parameter](#directory) above to use this function (root_dir/remote_dir) Beyond this you'll need to use one of the [categories](#cat) above as the key. +This functionality will tag any torrent's whose file (or largest file if multi-file) does not have any hardlinks outside the qbm root_dir. +Note that `ignore_root_dir` (Default: True) will ignore any hardlinks detected in the same root_dir. + | Configuration | Definition | Required | | :------------ | :-------------------------------------------------------- | :----------------- | | `key` | Category name to check for nohardlinked torrents in qbit. |
| diff --git a/modules/auth.py b/modules/auth.py index 43f529b..c28324d 100644 --- a/modules/auth.py +++ b/modules/auth.py @@ -4,6 +4,7 @@ import base64 import hashlib import ipaddress import os +import platform import re import secrets from collections import defaultdict @@ -338,22 +339,25 @@ class AuthenticationMiddleware(BaseHTTPMiddleware): try: if self.settings_path.exists(): - # Check file permissions for security - try: - stat_info = self.settings_path.stat() - # Check if file is readable/writable by group or others (should be 600 or similar) - if stat_info.st_mode & 0o077: # Check if group/other has any permissions - logger.warning( - f"Settings file {self.settings_path} has overly permissive permissions. " - f"Fixing to 600 (owner read/write only)." - ) - try: - os.chmod(str(self.settings_path), 0o600) - logger.info(f"Fixed permissions for settings file {self.settings_path} to 600.") - except OSError as e: - logger.error(f"Could not fix permissions for settings file: {e}") - except (OSError, AttributeError) as e: - logger.warning(f"Could not check permissions for settings file: {e}") + # Check file permissions for security (skip on Windows as it uses different permission model) + if platform.system() != "Windows": + try: + stat_info = self.settings_path.stat() + # Check if file is readable/writable by group or others (should be 600 or similar) + if stat_info.st_mode & 0o077: # Check if group/other has any permissions + logger.warning( + f"Settings file {self.settings_path} has overly permissive permissions. " + f"Fixing to 600 (owner read/write only)." + ) + try: + os.chmod(str(self.settings_path), 0o600) + logger.info(f"Fixed permissions for settings file {self.settings_path} to 600.") + except OSError as e: + logger.error(f"Could not fix permissions for settings file: {e}") + except (OSError, AttributeError) as e: + logger.warning(f"Could not check permissions for settings file: {e}") + else: + logger.debug("Skipping file permission check on Windows (uses different permission model)") with open(self.settings_path, encoding="utf-8") as f: yaml_loader = ruamel.yaml.YAML(typ="safe") @@ -574,11 +578,14 @@ def save_auth_settings(settings_path: Path, settings: AuthSettings) -> bool: with open(settings_path, "w", encoding="utf-8") as f: yaml.dump(data, f) - # Set restrictive permissions (600 - owner read/write only) - try: - os.chmod(str(settings_path), 0o600) - except OSError as e: - logger.error(f"Could not set permissions for settings file: {e}") + # Set restrictive permissions (600 - owner read/write only) - skip on Windows + if platform.system() != "Windows": + try: + os.chmod(str(settings_path), 0o600) + except OSError as e: + logger.error(f"Could not set permissions for settings file: {e}") + else: + logger.debug("Skipping file permission setting on Windows (uses different permission model)") return True except Exception as e: diff --git a/modules/web_api.py b/modules/web_api.py index e5566cb..c7b14b2 100755 --- a/modules/web_api.py +++ b/modules/web_api.py @@ -1725,17 +1725,26 @@ class WebAPI: f"has_username={bool(current_settings.username)}" ) + # Check if this is initial setup (no authentication currently configured) + is_initial_setup = not current_settings.enabled or ( + current_settings.method == "none" and not current_settings.username and not current_settings.api_key + ) + # Check if client is local and bypass_auth_for_local is enabled if current_settings.bypass_auth_for_local and is_local_ip(req, current_settings.trusted_proxies): logger.trace("Local client with bypass_auth_for_local enabled, skipping credential verification") auth_verified = True + elif is_initial_setup: + # Allow initial setup without credentials when no authentication is configured + logger.trace("Initial authentication setup detected, skipping credential verification") + auth_verified = True else: # Verify current credentials for reauthentication auth_verified = False # First, try credentials provided in the request body # Try API key verification first - if request.current_api_key and current_settings.api_key: + if not auth_verified and request.current_api_key and current_settings.api_key: logger.trace("Attempting API key verification from request body") if verify_api_key(request.current_api_key, current_settings.api_key): auth_verified = True @@ -1786,8 +1795,8 @@ class WebAPI: except Exception as e: logger.warning(f"Error parsing Basic auth header: {e}") - # If neither method worked, require authentication - if not auth_verified: + # If neither method worked and it's not initial setup, require authentication + if not auth_verified and not is_initial_setup: logger.warning("No valid current credentials provided for security settings update") return {"success": False, "message": "Current credentials required to update security settings"} diff --git a/pyproject.toml b/pyproject.toml index db04825..7a7276d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ Repository = "https://github.com/StuffAnThings/qbit_manage" [project.optional-dependencies] dev = [ "pre-commit==4.3.0", - "ruff==0.12.12", + "ruff==0.13.0", ] [tool.ruff] diff --git a/uv.lock b/uv.lock index 4705e5a..e026cb8 100644 --- a/uv.lock +++ b/uv.lock @@ -547,7 +547,7 @@ wheels = [ [[package]] name = "pydantic" -version = "2.11.7" +version = "2.11.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -555,9 +555,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/5d/09a551ba512d7ca404d785072700d3f6727a02f6f3c24ecfd081c7cf0aa8/pydantic-2.11.9.tar.gz", hash = "sha256:6b8ffda597a14812a7975c90b82a8a2e777d9257aba3453f973acd3c032a18e2", size = 788495, upload-time = "2025-09-13T11:26:39.325Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d3/108f2006987c58e76691d5ae5d200dd3e0f532cb4e5fa3560751c3a1feba/pydantic-2.11.9-py3-none-any.whl", hash = "sha256:c42dd626f5cfc1c6950ce6205ea58c93efa406da65f479dcb4029d5934857da2", size = 444855, upload-time = "2025-09-13T11:26:36.909Z" }, ] [[package]] @@ -791,7 +791,7 @@ requires-dist = [ { name = "requests", specifier = "==2.32.5" }, { name = "retrying", specifier = "==1.4.2" }, { name = "ruamel-yaml", specifier = "==0.18.15" }, - { name = "ruff", marker = "extra == 'dev'", specifier = "==0.12.12" }, + { name = "ruff", marker = "extra == 'dev'", specifier = "==0.13.0" }, { name = "slowapi", specifier = "==0.1.9" }, { name = "uvicorn", specifier = "==0.35.0" }, ] @@ -903,28 +903,28 @@ wheels = [ [[package]] name = "ruff" -version = "0.12.12" +version = "0.13.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a8/f0/e0965dd709b8cabe6356811c0ee8c096806bb57d20b5019eb4e48a117410/ruff-0.12.12.tar.gz", hash = "sha256:b86cd3415dbe31b3b46a71c598f4c4b2f550346d1ccf6326b347cc0c8fd063d6", size = 5359915, upload-time = "2025-09-04T16:50:18.273Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/1a/1f4b722862840295bcaba8c9e5261572347509548faaa99b2d57ee7bfe6a/ruff-0.13.0.tar.gz", hash = "sha256:5b4b1ee7eb35afae128ab94459b13b2baaed282b1fb0f472a73c82c996c8ae60", size = 5372863, upload-time = "2025-09-10T16:25:37.917Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/09/79/8d3d687224d88367b51c7974cec1040c4b015772bfbeffac95face14c04a/ruff-0.12.12-py3-none-linux_armv6l.whl", hash = "sha256:de1c4b916d98ab289818e55ce481e2cacfaad7710b01d1f990c497edf217dafc", size = 12116602, upload-time = "2025-09-04T16:49:18.892Z" }, - { url = "https://files.pythonhosted.org/packages/c3/c3/6e599657fe192462f94861a09aae935b869aea8a1da07f47d6eae471397c/ruff-0.12.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7acd6045e87fac75a0b0cdedacf9ab3e1ad9d929d149785903cff9bb69ad9727", size = 12868393, upload-time = "2025-09-04T16:49:23.043Z" }, - { url = "https://files.pythonhosted.org/packages/e8/d2/9e3e40d399abc95336b1843f52fc0daaceb672d0e3c9290a28ff1a96f79d/ruff-0.12.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:abf4073688d7d6da16611f2f126be86523a8ec4343d15d276c614bda8ec44edb", size = 12036967, upload-time = "2025-09-04T16:49:26.04Z" }, - { url = "https://files.pythonhosted.org/packages/e9/03/6816b2ed08836be272e87107d905f0908be5b4a40c14bfc91043e76631b8/ruff-0.12.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:968e77094b1d7a576992ac078557d1439df678a34c6fe02fd979f973af167577", size = 12276038, upload-time = "2025-09-04T16:49:29.056Z" }, - { url = "https://files.pythonhosted.org/packages/9f/d5/707b92a61310edf358a389477eabd8af68f375c0ef858194be97ca5b6069/ruff-0.12.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42a67d16e5b1ffc6d21c5f67851e0e769517fb57a8ebad1d0781b30888aa704e", size = 11901110, upload-time = "2025-09-04T16:49:32.07Z" }, - { url = "https://files.pythonhosted.org/packages/9d/3d/f8b1038f4b9822e26ec3d5b49cf2bc313e3c1564cceb4c1a42820bf74853/ruff-0.12.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b216ec0a0674e4b1214dcc998a5088e54eaf39417327b19ffefba1c4a1e4971e", size = 13668352, upload-time = "2025-09-04T16:49:35.148Z" }, - { url = "https://files.pythonhosted.org/packages/98/0e/91421368ae6c4f3765dd41a150f760c5f725516028a6be30e58255e3c668/ruff-0.12.12-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:59f909c0fdd8f1dcdbfed0b9569b8bf428cf144bec87d9de298dcd4723f5bee8", size = 14638365, upload-time = "2025-09-04T16:49:38.892Z" }, - { url = "https://files.pythonhosted.org/packages/74/5d/88f3f06a142f58ecc8ecb0c2fe0b82343e2a2b04dcd098809f717cf74b6c/ruff-0.12.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ac93d87047e765336f0c18eacad51dad0c1c33c9df7484c40f98e1d773876f5", size = 14060812, upload-time = "2025-09-04T16:49:42.732Z" }, - { url = "https://files.pythonhosted.org/packages/13/fc/8962e7ddd2e81863d5c92400820f650b86f97ff919c59836fbc4c1a6d84c/ruff-0.12.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:01543c137fd3650d322922e8b14cc133b8ea734617c4891c5a9fccf4bfc9aa92", size = 13050208, upload-time = "2025-09-04T16:49:46.434Z" }, - { url = "https://files.pythonhosted.org/packages/53/06/8deb52d48a9a624fd37390555d9589e719eac568c020b27e96eed671f25f/ruff-0.12.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afc2fa864197634e549d87fb1e7b6feb01df0a80fd510d6489e1ce8c0b1cc45", size = 13311444, upload-time = "2025-09-04T16:49:49.931Z" }, - { url = "https://files.pythonhosted.org/packages/2a/81/de5a29af7eb8f341f8140867ffb93f82e4fde7256dadee79016ac87c2716/ruff-0.12.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:0c0945246f5ad776cb8925e36af2438e66188d2b57d9cf2eed2c382c58b371e5", size = 13279474, upload-time = "2025-09-04T16:49:53.465Z" }, - { url = "https://files.pythonhosted.org/packages/7f/14/d9577fdeaf791737ada1b4f5c6b59c21c3326f3f683229096cccd7674e0c/ruff-0.12.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a0fbafe8c58e37aae28b84a80ba1817f2ea552e9450156018a478bf1fa80f4e4", size = 12070204, upload-time = "2025-09-04T16:49:56.882Z" }, - { url = "https://files.pythonhosted.org/packages/77/04/a910078284b47fad54506dc0af13839c418ff704e341c176f64e1127e461/ruff-0.12.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b9c456fb2fc8e1282affa932c9e40f5ec31ec9cbb66751a316bd131273b57c23", size = 11880347, upload-time = "2025-09-04T16:49:59.729Z" }, - { url = "https://files.pythonhosted.org/packages/df/58/30185fcb0e89f05e7ea82e5817b47798f7fa7179863f9d9ba6fd4fe1b098/ruff-0.12.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5f12856123b0ad0147d90b3961f5c90e7427f9acd4b40050705499c98983f489", size = 12891844, upload-time = "2025-09-04T16:50:02.591Z" }, - { url = "https://files.pythonhosted.org/packages/21/9c/28a8dacce4855e6703dcb8cdf6c1705d0b23dd01d60150786cd55aa93b16/ruff-0.12.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:26a1b5a2bf7dd2c47e3b46d077cd9c0fc3b93e6c6cc9ed750bd312ae9dc302ee", size = 13360687, upload-time = "2025-09-04T16:50:05.8Z" }, - { url = "https://files.pythonhosted.org/packages/c8/fa/05b6428a008e60f79546c943e54068316f32ec8ab5c4f73e4563934fbdc7/ruff-0.12.12-py3-none-win32.whl", hash = "sha256:173be2bfc142af07a01e3a759aba6f7791aa47acf3604f610b1c36db888df7b1", size = 12052870, upload-time = "2025-09-04T16:50:09.121Z" }, - { url = "https://files.pythonhosted.org/packages/85/60/d1e335417804df452589271818749d061b22772b87efda88354cf35cdb7a/ruff-0.12.12-py3-none-win_amd64.whl", hash = "sha256:e99620bf01884e5f38611934c09dd194eb665b0109104acae3ba6102b600fd0d", size = 13178016, upload-time = "2025-09-04T16:50:12.559Z" }, - { url = "https://files.pythonhosted.org/packages/28/7e/61c42657f6e4614a4258f1c3b0c5b93adc4d1f8575f5229d1906b483099b/ruff-0.12.12-py3-none-win_arm64.whl", hash = "sha256:2a8199cab4ce4d72d158319b63370abf60991495fb733db96cd923a34c52d093", size = 12256762, upload-time = "2025-09-04T16:50:15.737Z" }, + { url = "https://files.pythonhosted.org/packages/ac/fe/6f87b419dbe166fd30a991390221f14c5b68946f389ea07913e1719741e0/ruff-0.13.0-py3-none-linux_armv6l.whl", hash = "sha256:137f3d65d58ee828ae136a12d1dc33d992773d8f7644bc6b82714570f31b2004", size = 12187826, upload-time = "2025-09-10T16:24:39.5Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/c92296b1fc36d2499e12b74a3fdb230f77af7bdf048fad7b0a62e94ed56a/ruff-0.13.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:21ae48151b66e71fd111b7d79f9ad358814ed58c339631450c66a4be33cc28b9", size = 12933428, upload-time = "2025-09-10T16:24:43.866Z" }, + { url = "https://files.pythonhosted.org/packages/44/cf/40bc7221a949470307d9c35b4ef5810c294e6cfa3caafb57d882731a9f42/ruff-0.13.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:64de45f4ca5441209e41742d527944635a05a6e7c05798904f39c85bafa819e3", size = 12095543, upload-time = "2025-09-10T16:24:46.638Z" }, + { url = "https://files.pythonhosted.org/packages/f1/03/8b5ff2a211efb68c63a1d03d157e924997ada87d01bebffbd13a0f3fcdeb/ruff-0.13.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b2c653ae9b9d46e0ef62fc6fbf5b979bda20a0b1d2b22f8f7eb0cde9f4963b8", size = 12312489, upload-time = "2025-09-10T16:24:49.556Z" }, + { url = "https://files.pythonhosted.org/packages/37/fc/2336ef6d5e9c8d8ea8305c5f91e767d795cd4fc171a6d97ef38a5302dadc/ruff-0.13.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4cec632534332062bc9eb5884a267b689085a1afea9801bf94e3ba7498a2d207", size = 11991631, upload-time = "2025-09-10T16:24:53.439Z" }, + { url = "https://files.pythonhosted.org/packages/39/7f/f6d574d100fca83d32637d7f5541bea2f5e473c40020bbc7fc4a4d5b7294/ruff-0.13.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dcd628101d9f7d122e120ac7c17e0a0f468b19bc925501dbe03c1cb7f5415b24", size = 13720602, upload-time = "2025-09-10T16:24:56.392Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c8/a8a5b81d8729b5d1f663348d11e2a9d65a7a9bd3c399763b1a51c72be1ce/ruff-0.13.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:afe37db8e1466acb173bb2a39ca92df00570e0fd7c94c72d87b51b21bb63efea", size = 14697751, upload-time = "2025-09-10T16:24:59.89Z" }, + { url = "https://files.pythonhosted.org/packages/57/f5/183ec292272ce7ec5e882aea74937f7288e88ecb500198b832c24debc6d3/ruff-0.13.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f96a8d90bb258d7d3358b372905fe7333aaacf6c39e2408b9f8ba181f4b6ef2", size = 14095317, upload-time = "2025-09-10T16:25:03.025Z" }, + { url = "https://files.pythonhosted.org/packages/9f/8d/7f9771c971724701af7926c14dab31754e7b303d127b0d3f01116faef456/ruff-0.13.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b5e3d883e4f924c5298e3f2ee0f3085819c14f68d1e5b6715597681433f153", size = 13144418, upload-time = "2025-09-10T16:25:06.272Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a6/7985ad1778e60922d4bef546688cd8a25822c58873e9ff30189cfe5dc4ab/ruff-0.13.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03447f3d18479df3d24917a92d768a89f873a7181a064858ea90a804a7538991", size = 13370843, upload-time = "2025-09-10T16:25:09.965Z" }, + { url = "https://files.pythonhosted.org/packages/64/1c/bafdd5a7a05a50cc51d9f5711da704942d8dd62df3d8c70c311e98ce9f8a/ruff-0.13.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:fbc6b1934eb1c0033da427c805e27d164bb713f8e273a024a7e86176d7f462cf", size = 13321891, upload-time = "2025-09-10T16:25:12.969Z" }, + { url = "https://files.pythonhosted.org/packages/bc/3e/7817f989cb9725ef7e8d2cee74186bf90555279e119de50c750c4b7a72fe/ruff-0.13.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a8ab6a3e03665d39d4a25ee199d207a488724f022db0e1fe4002968abdb8001b", size = 12119119, upload-time = "2025-09-10T16:25:16.621Z" }, + { url = "https://files.pythonhosted.org/packages/58/07/9df080742e8d1080e60c426dce6e96a8faf9a371e2ce22eef662e3839c95/ruff-0.13.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d2a5c62f8ccc6dd2fe259917482de7275cecc86141ee10432727c4816235bc41", size = 11961594, upload-time = "2025-09-10T16:25:19.49Z" }, + { url = "https://files.pythonhosted.org/packages/6a/f4/ae1185349197d26a2316840cb4d6c3fba61d4ac36ed728bf0228b222d71f/ruff-0.13.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b7b85ca27aeeb1ab421bc787009831cffe6048faae08ad80867edab9f2760945", size = 12933377, upload-time = "2025-09-10T16:25:22.371Z" }, + { url = "https://files.pythonhosted.org/packages/b6/39/e776c10a3b349fc8209a905bfb327831d7516f6058339a613a8d2aaecacd/ruff-0.13.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:79ea0c44a3032af768cabfd9616e44c24303af49d633b43e3a5096e009ebe823", size = 13418555, upload-time = "2025-09-10T16:25:25.681Z" }, + { url = "https://files.pythonhosted.org/packages/46/09/dca8df3d48e8b3f4202bf20b1658898e74b6442ac835bfe2c1816d926697/ruff-0.13.0-py3-none-win32.whl", hash = "sha256:4e473e8f0e6a04e4113f2e1de12a5039579892329ecc49958424e5568ef4f768", size = 12141613, upload-time = "2025-09-10T16:25:28.664Z" }, + { url = "https://files.pythonhosted.org/packages/61/21/0647eb71ed99b888ad50e44d8ec65d7148babc0e242d531a499a0bbcda5f/ruff-0.13.0-py3-none-win_amd64.whl", hash = "sha256:48e5c25c7a3713eea9ce755995767f4dcd1b0b9599b638b12946e892123d1efb", size = 13258250, upload-time = "2025-09-10T16:25:31.773Z" }, + { url = "https://files.pythonhosted.org/packages/e1/a3/03216a6a86c706df54422612981fb0f9041dbb452c3401501d4a22b942c9/ruff-0.13.0-py3-none-win_arm64.whl", hash = "sha256:ab80525317b1e1d38614addec8ac954f1b3e662de9d59114ecbf771d00cf613e", size = 12312357, upload-time = "2025-09-10T16:25:35.595Z" }, ] [[package]] diff --git a/web-ui/js/components/security.js b/web-ui/js/components/security.js index a93fe08..0563487 100644 --- a/web-ui/js/components/security.js +++ b/web-ui/js/components/security.js @@ -512,6 +512,17 @@ export class SecurityComponent { return; } + // For basic authentication, password is required + if (method === 'basic') { + const hasExistingPassword = this.currentSettings.password_hash && this.currentSettings.password_hash !== ''; + const hasNewPassword = password && password.trim() !== ''; + + if (!hasExistingPassword && !hasNewPassword) { + showToast('Password is required for basic authentication', 'error'); + return; + } + } + if (password && password !== confirmPassword) { showToast('Passwords do not match', 'error'); return;