luci: Make password reveal work with passsword managers
authorAndrew Dodd <[email protected]>
Tue, 17 Dec 2024 01:37:35 +0000 (12:37 +1100)
committerPaul Donald <[email protected]>
Fri, 21 Mar 2025 13:57:31 +0000 (14:57 +0100)
Password managers (like LastPass etc) tend to add additional elements
into the DOM for their own context menus. If this happens between the
hide/reveal button and the password input, then the logic to reveal the
password breaks.

This change updates the onclick handler to look for the first `<input>`
element with the class `password-input` that is under the parent of the
toggle button, and then to toggle the password/text type on that
element.

This change deliberately only updates the main ui.js file, not any
application files.

Signed-off-by: Andrew Dodd <[email protected]>
modules/luci-base/htdocs/luci-static/resources/ui.js

index f0ea264bd4434bb09e59c21d5fd08c6abd87dda1..def3eb13d30f3f05f9c2d829b3044a6ce368a066 100644 (file)
@@ -368,7 +368,7 @@ const UITextfield = UIElement.extend(/** @lends LuCI.ui.Textfield.prototype */ {
                        'id': this.options.id ? `widget.${this.options.id}` : null,
                        'name': this.options.name,
                        'type': 'text',
-                       'class': this.options.password ? 'cbi-input-password' : 'cbi-input-text',
+                       'class': `password-input ${this.options.password ? 'cbi-input-password' : 'cbi-input-text'}`,
                        'readonly': this.options.readonly ? '' : null,
                        'disabled': this.options.disabled ? '' : null,
                        'maxlength': this.options.maxlength,
@@ -384,8 +384,15 @@ const UITextfield = UIElement.extend(/** @lends LuCI.ui.Textfield.prototype */ {
                                        'title': _('Reveal/hide password'),
                                        'aria-label': _('Reveal/hide password'),
                                        'click': function(ev) {
-                                               const e = this.previousElementSibling;
-                                               e.type = (e.type === 'password') ? 'text' : 'password';
+                                               // DOM manipulation (e.g. by password managers) may have inserted other
+                                               // elements between the reveal button and the input. This searches for
+                                               // the first <input> inside the parent of the <button> to use for toggle.
+                                               const e = this.parentElement.querySelector('input.password-input')
+                                               if (e) {
+                                                       e.type = (e.type === 'password') ? 'text' : 'password';
+                                               } else {
+                                                       console.error('unable to find input corresponding to reveal/hide button');
+                                               }
                                                ev.preventDefault();
                                        }
                                }, '∗')