fix(login): restore i18n placeholders and keep passkey-only logins working
Gate follow-up for #7056. Two defects in the conditional password form: 1. The {{LOGIN_INVALID_PW}} / {{LOGIN_CONN_FAILED}} .replace() calls were deleted with the moved block, but those placeholders live on the <form> tag which ALWAYS renders. login.js reads data-invalid-pw back, and the literal is non-empty so its || fallback never fired: a wrong password displayed the raw text {{LOGIN_INVALID_PW}} in all 14 locales, even with password auth ENABLED. Proven by a clean-master control (test_login_locale 3/3 on master, 2/3 on the PR). 2. is_password_auth_enabled() also gated the PASSKEY button, but passkeys are an independent auth method (auth.py is_auth_enabled, and settings expose passwordless_enabled = passkeys AND not password). A passwordless-passkey instance lost its only login control. login.js additionally bailed at 'if (!form || !input) return' before ever wiring the passkey handler.
This commit is contained in:
1 parent
c53226dcc4
commit
e6592f41af
2 files changed
+34
-10
No files matched your search
+19
-3
@@ -13638,15 +13638,27 @@ def handle_get(handler, parsed) -> bool:
|
||||
# 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
|
||||
from api.auth import are_passkeys_enabled, is_password_auth_enabled
|
||||
|
||||
# The password INPUT is gated on a configured password, but the passkey
|
||||
# button must survive a passwordless-passkey deployment: settings expose
|
||||
# ``passwordless_enabled = passkeys registered AND not password_auth_enabled``
|
||||
# (routes.py ~14059) and ``is_auth_enabled()`` counts passkeys as an
|
||||
# independent auth method, so hiding the button when no password is set
|
||||
# would remove the ONLY working login affordance for those instances.
|
||||
_passkey_button_html = (
|
||||
'<button type="button" id="passkey-login" class="passkey-login" '
|
||||
'style="display:none">Sign in with passkey</button>'
|
||||
)
|
||||
if is_password_auth_enabled():
|
||||
_password_form_html = (
|
||||
f'<input type="password" id="pw" '
|
||||
f'placeholder="{_html.escape(_login_strings["placeholder"])}" autofocus>'
|
||||
f'<button type="submit">{_html.escape(_login_strings["btn"])}</button>'
|
||||
'<button type="button" id="passkey-login" class="passkey-login" '
|
||||
'style="display:none">Sign in with passkey</button>'
|
||||
f'{_passkey_button_html}'
|
||||
)
|
||||
elif are_passkeys_enabled():
|
||||
_password_form_html = _passkey_button_html
|
||||
else:
|
||||
_password_form_html = ""
|
||||
version_token = quote(WEBUI_VERSION, safe="")
|
||||
@@ -13658,6 +13670,10 @@ def handle_get(handler, parsed) -> bool:
|
||||
.replace("{{LOGIN_TITLE}}", _html.escape(_login_strings["title"]))
|
||||
.replace("{{LOGIN_SUBTITLE}}", _html.escape(_login_strings["subtitle"]))
|
||||
.replace("{{PASSWORD_FORM_HTML}}", _password_form_html)
|
||||
.replace("{{LOGIN_INVALID_PW}}", _html.escape(_login_strings["invalid_pw"]))
|
||||
.replace(
|
||||
"{{LOGIN_CONN_FAILED}}", _html.escape(_login_strings["conn_failed"])
|
||||
)
|
||||
.replace("{{OIDC_LOGIN_HTML}}", _oidc_login_html(parsed))
|
||||
)
|
||||
return t(handler, _page, content_type="text/html; charset=utf-8")
|
||||
|
||||
+15
-7
@@ -7,7 +7,11 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
var input = document.getElementById('pw');
|
||||
var passkeyBtn = document.getElementById('passkey-login');
|
||||
|
||||
if (!form || !input) return;
|
||||
// #7056: the password input is absent on a passwordless deployment (OIDC-only,
|
||||
// or passkey-only). The form itself still renders and carries the i18n data
|
||||
// attributes, and the passkey button below must still be wired up, so only the
|
||||
// form is required here — every password-specific path is guarded individually.
|
||||
if (!form) return;
|
||||
|
||||
var invalidPw = form.getAttribute('data-invalid-pw') || 'Invalid password';
|
||||
var connFailed = form.getAttribute('data-conn-failed') || 'Connection failed';
|
||||
@@ -59,6 +63,8 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
async function doLogin(e) {
|
||||
e.preventDefault();
|
||||
// No password input on a passwordless deployment: nothing to submit.
|
||||
if (!input) return;
|
||||
var pw = input.value;
|
||||
hideErr();
|
||||
try {
|
||||
@@ -147,12 +153,14 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
passkeyBtn.addEventListener('click', doPasskeyLogin);
|
||||
}
|
||||
|
||||
input.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
doLogin(e);
|
||||
}
|
||||
});
|
||||
if (input) {
|
||||
input.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
doLogin(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// On page load, probe the server so we can distinguish "can't reach server"
|
||||
// (Tailscale off, wrong network) from "session expired / need to log in".
|
||||
|
||||
Reference in new issue
Block a user