diff --git a/api/routes.py b/api/routes.py index 6afd81ad..1645084a 100644 --- a/api/routes.py +++ b/api/routes.py @@ -11088,9 +11088,7 @@ button:hover{background:rgba(124,185,255,.25)}

{{BOT_NAME}}

{{LOGIN_SUBTITLE}}

- - - + {{PASSWORD_FORM_HTML}} {{OIDC_LOGIN_HTML}}
@@ -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 ```` + + ``