# 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] <support@github.com>
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 <actions@github.com>
This commit is contained in:
bobokun 2025-09-13 09:54:10 -04:00 committed by GitHub
parent 28f1a3b027
commit 89d686ec1f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 91 additions and 71 deletions

View file

@ -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

View file

@ -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 \

View file

@ -1 +1 @@
4.6.1
4.6.2

View file

@ -3003,7 +3003,7 @@ dependencies = [
[[package]]
name = "qbit-manage-desktop"
version = "4.6.0"
version = "4.6.2"
dependencies = [
"glib 0.20.12",
"libc",

View file

@ -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"

View file

@ -68,5 +68,5 @@
},
"identifier": "com.qbitmanage.desktop",
"productName": "qBit Manage",
"version": "4.6.1"
"version": "4.6.2"
}

View file

@ -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 |

View file

@ -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 | <center></center> |
| `notifiarr` | Set this to the notifiarr react name. This is used to add indexer reactions to the notifications sent by Notifiarr | None | <center></center> |
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. | <center></center> |

View file

@ -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:

View file

@ -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"}

View file

@ -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]

48
uv.lock generated
View file

@ -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]]

View file

@ -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;