@@ -13633,6 +13631,24 @@ def handle_get(handler, parsed) -> bool:
]
from urllib.parse import quote
from api.updates import WEBUI_VERSION
+ # #7056: only render the password input / submit / passkey controls
+ # when password auth is actually enabled. With native OIDC configured
+ # and ``HERMES_WEBUI_PASSWORD`` unset, the form previously still
+ # displayed the password prompt and accepted — silently 401-ing at
+ # the server — every submit. The OIDC SSO entry point stays the
+ # sole path. ``is_password_auth_enabled`` is the same predicate
+ # ``/api/auth/status`` reports as ``password_auth_enabled``.
+ from api.auth import is_password_auth_enabled
+ if is_password_auth_enabled():
+ _password_form_html = (
+ f''
+ f''
+ ''
+ )
+ else:
+ _password_form_html = ""
version_token = quote(WEBUI_VERSION, safe="")
_page = (
_LOGIN_PAGE_HTML.replace("{{BOT_NAME}}", _bn)
@@ -13641,14 +13657,7 @@ def handle_get(handler, parsed) -> bool:
.replace("{{LANG}}", _html.escape(_login_strings["lang"]))
.replace("{{LOGIN_TITLE}}", _html.escape(_login_strings["title"]))
.replace("{{LOGIN_SUBTITLE}}", _html.escape(_login_strings["subtitle"]))
- .replace(
- "{{LOGIN_PLACEHOLDER}}", _html.escape(_login_strings["placeholder"])
- )
- .replace("{{LOGIN_BTN}}", _html.escape(_login_strings["btn"]))
- .replace("{{LOGIN_INVALID_PW}}", _html.escape(_login_strings["invalid_pw"]))
- .replace(
- "{{LOGIN_CONN_FAILED}}", _html.escape(_login_strings["conn_failed"])
- )
+ .replace("{{PASSWORD_FORM_HTML}}", _password_form_html)
.replace("{{OIDC_LOGIN_HTML}}", _oidc_login_html(parsed))
)
return t(handler, _page, content_type="text/html; charset=utf-8")
diff --git a/tests/test_issue7056_login_hides_password_form.py b/tests/test_issue7056_login_hides_password_form.py
new file mode 100644
index 00000000..dd4b4586
--- /dev/null
+++ b/tests/test_issue7056_login_hides_password_form.py
@@ -0,0 +1,223 @@
+"""Regression tests for issue #7056 — login page must hide the password form
+when password auth is disabled (e.g. native OIDC configured and
+``HERMES_WEBUI_PASSWORD`` unset).
+
+The original bug: ``/api/auth/status`` correctly reports
+``password_auth_enabled: false`` when password auth is disabled, but
+``/login`` still rendered the password input, the "Sign in" button,
+and the "Continue with SSO" entry point — and silently 401-ed every
+submit. The fix routes the password input / submit / passkey controls
+through a single ``{{PASSWORD_FORM_HTML}}`` placeholder in the
+template, which the handler populates with the live markup only when
+``is_password_auth_enabled()`` is True.
+
+The OIDC SSO button stays the sole path when password auth is disabled.
+"""
+from __future__ import annotations
+
+import io
+from unittest import mock
+
+import pytest
+
+import api.routes as routes
+from api.routes import _LOGIN_PAGE_HTML
+
+
+# ---------------------------------------------------------------------------
+# Source-shape: the template + handler must agree on a single placeholder
+# for the password controls, and the handler must call the live predicate
+# ---------------------------------------------------------------------------
+
+
+def test_login_template_uses_password_form_placeholder() -> None:
+ """The login template MUST carry a single ``{{PASSWORD_FORM_HTML}}`` placeholder.
+
+ A silent reversion to the previous multi-line ```` +
+ ``