Repository: streamlit/streamlit

Issue:
Auto-Complete Password with Password Manager
### Summary

When filling a username/password combo from a password manager, it only fills the username, not the password, because the HTML password field has the property `autocomplete="new-password"`. Removing this property results in the expected behaviour.

### Steps to reproduce

Code snippet:

```python
import streamlit as st

sender_email = st.text_input('username', value='user', key='email')
sender_password = st.text_input('password', type='password', key='password')
if st.button('Login'):
    st.success('Logged in')
else:
    st.write('Not logged in')
```

1. Run the code
2. Notice that 1Password 7 displays for the user field the saved password and for the password field only suggests a new password
3. Fill saved password from the user field. Notice that the password field does not get filled.
4. Reload the page.
5. Remove the `autocomplete="new-password"` property with the web inspector.
6. Fill the password from the user field. Notice that both fields get filled (as expected).

**Expected behavior:**

The password manager fills both fields

**Actual behavior:**

The password manager only fills the user field.

### Is this a regression?

Don't know.

### Debug info

- Streamlit version: 0.79.0
- Python version: 3.9.2
- Using Conda? PipEnv? PyEnv? Pex? => Conda
- OS version: macOS 10.15.7
- Browser version: Tested with Safari 14.0.3 and Chrome 89.0.4389.90



Is there a workaround to remove this HTML property? Or does it required a code change within Streamlit?
For my tool the user needs to provide a API authentication for the backend (username/password), which will be stored in a password manager (1Password 7) in this case. It is inconvenient having to copy the password manually.

Thank you!



Discussion:
First-time contributor here who's willing to try and tackle this issue if no one else is currently working on it!
> First-time contributor here who's willing to try and tackle this issue if no one else is currently working on it!

Hi @hyerrakalva, thanks for volunteering for this!

I think we'll want to first check with our product team whether we want these password fields to be treated as new passwords or existing ones (or if we want to add some configurability so that users can specify which one they want) before starting work on this. @asaini may have some input on this. I'll also surface this at a team meeting tomorrow and try to get back to you afterwards.

My hunch is that setting `autocomplete="current-password"` here (assuming the decision isn't to have both) is probably what we want, but I want to double-check on this before giving you the green-light to tackle this.
Hi @hyerrakalva, wanted to give you an update on this (sorry for the delay!). I ran this by our product team, and our gut feeling is that we'll probably want to have password fields be configurable so a developer can specify whether the password is a new or existing one. Ideally, we'd want the default to be that it's treated as an existing password since that's certainly the more common use case, but we'll most likely have to default to the current behavior to preserve backwards compatibility.

We'll try to get a spec for this out sometime next week, at which point it'll be free to take if you still want to work on it!
Dropping this reference here in case it's useful: https://developer.apple.com/documentation/security/password_autofill/enabling_password_autofill_on_an_html_input_element
@asaini @tvst what do folks think about generally having an `options` param for all input widgets that lets you customize css, attributes, etc. This is the pattern [Rails uses](https://api.rubyonrails.org/v5.1.7/classes/ActionView/Helpers/FormHelper.html#method-i-password_field). So essentially for this case, one could do something like:

```python
sender_password = st.text_input('password', type='password', key='password', html_options={"autocomplete": "current-password"})
```

I also think we should just default it to being specified, but to future-proof the possibility of other HTML attributes in the future, seems useful to have this kind of flexibility.
We've decided to just do the quick and easy fix now: expose "auto_correct" as an optional param to TextInput. But @jroes' suggestion is a good one that we'll be considering later.

Resolving patch:
diff --git a/lib/streamlit/elements/text_widgets.py b/lib/streamlit/elements/text_widgets.py
--- a/lib/streamlit/elements/text_widgets.py
+++ b/lib/streamlit/elements/text_widgets.py
@@ -32,6 +32,7 @@ def text_input(
         key=None,
         type="default",
         help=None,
+        autocomplete=None,
         on_change=None,
         args=None,
         kwargs=None,
@@ -58,6 +59,11 @@ def text_input(
             masks the user's typed value). Defaults to "default".
         help : str
             An optional tooltip that gets displayed next to the input.
+        autocomplete : str
+            An optional value that will be passed to the <input> element's
+            autocomplete property. If unspecified, this value will be set to
+            "new-password" for "password" inputs, and the empty string for
+            "default" inputs. For more details, see https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete
         on_change : callable
             An optional callback invoked when this text_input's value changes.
         args : tuple
@@ -99,6 +105,12 @@ def text_input(
                 % type
             )
 
+        # Marshall the autocomplete param. If unspecified, this will be
+        # set to "new-password" for password inputs.
+        if autocomplete is None:
+            autocomplete = "new-password" if type == "password" else ""
+        text_input_proto.autocomplete = autocomplete
+
         def deserialize_text_input(ui_value) -> str:
             return str(ui_value if ui_value is not None else value)
 


Test patch:
diff --git a/frontend/src/components/widgets/TextInput/TextInput.test.tsx b/frontend/src/components/widgets/TextInput/TextInput.test.tsx
--- a/frontend/src/components/widgets/TextInput/TextInput.test.tsx
+++ b/frontend/src/components/widgets/TextInput/TextInput.test.tsx
@@ -63,6 +63,18 @@ describe("TextInput widget", () => {
     expect(uiInput.props().type).toBe("password")
   })
 
+  it("handles TextInputProto.autocomplete", () => {
+    let props = getProps()
+    let textInput = shallow(<TextInput {...props} />)
+    let uiInput = textInput.find(UIInput)
+    expect(uiInput.props().autoComplete).toBe("")
+
+    props = getProps({ autocomplete: "one-time-password" })
+    textInput = shallow(<TextInput {...props} />)
+    uiInput = textInput.find(UIInput)
+    expect(uiInput.props().autoComplete).toBe("one-time-password")
+  })
+
   it("sets widget value on mount", () => {
     const props = getProps()
     jest.spyOn(props.widgetMgr, "setStringValue")
diff --git a/lib/tests/streamlit/text_input_test.py b/lib/tests/streamlit/text_input_test.py
--- a/lib/tests/streamlit/text_input_test.py
+++ b/lib/tests/streamlit/text_input_test.py
@@ -104,6 +104,24 @@ def test_inside_column(self):
 
         self.assertEqual(text_input_proto.label, "foo")
 
+    def test_autocomplete_defaults(self):
+        """If 'autocomplete' is unspecified, it defaults to the empty string
+        for default inputs, and "new-password" for password inputs.
+        """
+        st.text_input("foo")
+        proto = self.get_delta_from_queue().new_element.text_input
+        self.assertEqual("", proto.autocomplete)
+
+        st.text_input("password", type="password")
+        proto = self.get_delta_from_queue().new_element.text_input
+        self.assertEqual("new-password", proto.autocomplete)
+
+    def test_autcomplete(self):
+        """Autocomplete should be marshalled if specified."""
+        st.text_input("foo", autocomplete="you-complete-me")
+        proto = self.get_delta_from_queue().new_element.text_input
+        self.assertEqual("you-complete-me", proto.autocomplete)
+
 
-class SomeObj(object):
+class SomeObj:
     pass