Merge pull request #799 from christianlouis/copilot/fix-greyed-out-toggles

fix(admin): fix greyed-out QR login toggle on /admin/connections
This commit is contained in:
Christian Krakau-Louis
2026-03-22 17:43:12 +01:00
committed by GitHub
5 changed files with 31 additions and 2 deletions
+16
View File
@@ -31,6 +31,7 @@ from pydantic import BaseModel, Field
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.auth import require_login from app.auth import require_login
from app.config import settings
from app.database import get_db from app.database import get_db
from app.middleware.audit_log import get_client_ip from app.middleware.audit_log import get_client_ip
from app.utils.session_manager import ( from app.utils.session_manager import (
@@ -151,6 +152,11 @@ async def create_challenge(
displayed to the user. The mobile app scans this QR code and displayed to the user. The mobile app scans this QR code and
calls the ``/claim`` endpoint. calls the ``/claim`` endpoint.
""" """
if not settings.qr_login_enabled:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="QR login feature is currently disabled. Please contact your administrator to enable it.",
)
ip = get_client_ip(request) ip = get_client_ip(request)
challenge = create_qr_challenge(db, owner_id, ip_address=ip) challenge = create_qr_challenge(db, owner_id, ip_address=ip)
@@ -187,6 +193,11 @@ async def poll_challenge_status(
The web UI calls this endpoint every few seconds to check if the The web UI calls this endpoint every few seconds to check if the
mobile app has scanned the QR code and claimed the challenge. mobile app has scanned the QR code and claimed the challenge.
""" """
if not settings.qr_login_enabled:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="QR login feature is currently disabled. Please contact your administrator to enable it.",
)
result = get_challenge_status(db, challenge_id, owner_id) result = get_challenge_status(db, challenge_id, owner_id)
if not result: if not result:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Challenge not found") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Challenge not found")
@@ -206,6 +217,11 @@ async def claim_challenge(
serves as proof that the user authorized this login from their web serves as proof that the user authorized this login from their web
session. session.
""" """
if not settings.qr_login_enabled:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="QR login feature is currently disabled. Please contact your administrator to enable it.",
)
ip = get_client_ip(request) ip = get_client_ip(request)
result = claim_qr_challenge(db, body.challenge_token, device_name=body.device_name, ip_address=ip) result = claim_qr_challenge(db, body.challenge_token, device_name=body.device_name, ip_address=ip)
+4
View File
@@ -244,6 +244,10 @@ class Settings(BaseSettings):
"Useful for admin-configured non-standard durations." "Useful for admin-configured non-standard durations."
), ),
) )
qr_login_enabled: bool = Field(
default=True,
description="Enable QR code-based login for mobile device authentication (default: True).",
)
qr_login_challenge_ttl_seconds: int = Field( qr_login_challenge_ttl_seconds: int = Field(
default=120, default=120,
description="Time-to-live in seconds for QR login challenges (default: 2 minutes).", description="Time-to-live in seconds for QR login challenges (default: 2 minutes).",
+8
View File
@@ -206,6 +206,14 @@ SETTING_METADATA = {
"required": False, "required": False,
"restart_required": True, "restart_required": True,
}, },
"qr_login_enabled": {
"category": "Authentication",
"description": "Enable QR code-based login for mobile device authentication.",
"type": "boolean",
"sensitive": False,
"required": False,
"restart_required": False,
},
"qr_login_challenge_ttl_seconds": { "qr_login_challenge_ttl_seconds": {
"category": "Authentication", "category": "Authentication",
"description": "Time-to-live in seconds for QR login challenges (default 120).", "description": "Time-to-live in seconds for QR login challenges (default 120).",
+1 -1
View File
@@ -464,7 +464,7 @@ async def connections_page(request: Request, db: Session = Depends(get_db)):
# Feature toggles # Feature toggles
sso_auto_login = _is_truthy(_get_effective("sso_auto_login")) sso_auto_login = _is_truthy(_get_effective("sso_auto_login"))
qr_login_enabled = _is_truthy(_get_effective("qr_login_challenge_ttl_seconds")) qr_login_enabled = _is_truthy(_get_effective("qr_login_enabled"))
frontend_url_configured = bool(_get_effective("public_base_url")) frontend_url_configured = bool(_get_effective("public_base_url"))
return templates.TemplateResponse( return templates.TemplateResponse(
+2 -1
View File
@@ -46,7 +46,8 @@
<div> <div>
<label class="relative inline-flex items-center cursor-pointer"> <label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" id="qr-upload-toggle" class="sr-only peer" <input type="checkbox" id="qr-upload-toggle" class="sr-only peer"
{% if qr_login_enabled %}checked{% endif %} disabled> {% if qr_login_enabled %}checked{% endif %}
onchange="toggleSetting('qr_login_enabled', this.checked)">
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-indigo-500 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-indigo-600" style="min-width:44px; min-height:24px;"></div> <div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-indigo-500 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-indigo-600" style="min-width:44px; min-height:24px;"></div>
<span class="ml-3 text-sm font-medium text-gray-700 dark:text-gray-300">{{ _("connections.qr_code_enabled") }}</span> <span class="ml-3 text-sm font-medium text-gray-700 dark:text-gray-300">{{ _("connections.qr_code_enabled") }}</span>
</label> </label>