Skip to content

Commit ead70e9

Browse files
authored
Fix login form remember checkbox (#1253)
The login form remember checkbox didn't correctly display a tick mark if SECURITY_DEFAULT_REMEMBER_ME was set. This was due to the default being set in the form init() which doesn't work - changed to using a lambda for the default value. Also affected us-signin form, and wan-signin form. closes #1244
2 parents 81a99ea + f6d6877 commit ead70e9

10 files changed

Lines changed: 54 additions & 13 deletions

File tree

CHANGES.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,14 @@ Fixes
2626
+++++
2727
- (:issue:`1212`) Newly introduced :py:meth:`.UserMixin.is_locked` logic is inverted.
2828
- (:pr:`1234`) Fix for GHSA-f66q-9rf6-8795 - WebAuthn reauthentication freshness bypass. (tonghuaroot)
29+
- (:issue:`1244`) Fix login form remember me checkbox.
2930

3031

3132
Docs and Chores
3233
+++++++++++++++
3334
- (:issue:`1208`) Remove support for Pony ORM
3435
- (:pr:`1240`) Remove deprecated get_token_status() and convert uses to check_and_get_token_status()
35-
- Replace ``bleach`` (deprecated/unmaintained) with ``nh3`` for username sanitization
36+
- (:pr:`1252`) Replace ``bleach`` (deprecated/unmaintained) with ``nh3`` for username sanitization (tkfoss)
3637

3738
Backwards Compatibility Concerns
3839
+++++++++++++++++++++++++++++++++

flask_security/forms.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -533,14 +533,16 @@ class LoginForm(Form, PasswordFormMixin, NextFormMixin):
533533

534534
# username is added dynamically based on USERNAME_ENABLED.
535535
username: t.ClassVar[Field]
536-
remember = BooleanField(get_form_field_label("remember_me"))
536+
remember = BooleanField(
537+
get_form_field_label("remember_me"),
538+
default=lambda: cv("DEFAULT_REMEMBER_ME", app=current_app),
539+
)
537540
submit = SubmitField(get_form_field_label("login"))
538541

539542
def __init__(self, *args: t.Any, **kwargs: t.Any):
540543
super().__init__(*args, **kwargs)
541544
if request and not self.next.data:
542545
self.next.data = request.args.get("next", "")
543-
self.remember.default = cv("DEFAULT_REMEMBER_ME")
544546
if _security.recoverable and not self.password.description:
545547
html = Markup(
546548
f'<a href="{url_for_security("forgot_password")}">'

flask_security/unified_signin.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,11 +255,13 @@ class UnifiedSigninForm(_UnifiedPassCodeForm, NextFormMixin):
255255
get_form_field_label("identity"),
256256
validators=[RequiredLocalize()],
257257
)
258-
remember = BooleanField(get_form_field_label("remember_me"))
258+
remember = BooleanField(
259+
get_form_field_label("remember_me"),
260+
default=lambda: cv("DEFAULT_REMEMBER_ME", app=current_app),
261+
)
259262

260263
def __init__(self, *args, **kwargs):
261264
super().__init__(*args, **kwargs)
262-
self.remember.default = cv("DEFAULT_REMEMBER_ME")
263265
self.requires_confirmation = False
264266

265267
def validate(self, **kwargs: t.Any) -> bool:

flask_security/webauthn.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,10 @@ def validate(self, **kwargs: t.Any) -> bool:
223223

224224
class WebAuthnSigninForm(Form, NextFormMixin):
225225
identity = StringField(get_form_field_label("identity"))
226-
remember = BooleanField(get_form_field_label("remember_me"))
226+
remember = BooleanField(
227+
get_form_field_label("remember_me"),
228+
default=lambda: cv("DEFAULT_REMEMBER_ME", app=current_app),
229+
)
227230
submit = SubmitField(label=get_form_field_xlate(_("Start")), id="wan_signin")
228231

229232
user: UserMixin | None = None
@@ -232,7 +235,6 @@ class WebAuthnSigninForm(Form, NextFormMixin):
232235

233236
def __init__(self, *args, **kwargs):
234237
super().__init__(*args, **kwargs)
235-
self.remember.default = cv("DEFAULT_REMEMBER_ME")
236238

237239
def validate(self, **kwargs: t.Any) -> bool:
238240
if not super().validate(**kwargs):

tests/test_basic.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,10 +1019,12 @@ def identity_loaded_check(sender, identity):
10191019

10201020

10211021
def test_remember_token(client):
1022-
response = authenticate(client, follow_redirects=False)
1022+
response = authenticate(client, follow_redirects=False, remember=True)
10231023
client.delete_cookie("session")
1024-
response = client.get("/profile")
1025-
assert b"profile" in response.data
1024+
assert not client.get_cookie("session")
1025+
assert client.get_cookie("remember_token")
1026+
response = client.get("/profile", follow_redirects=True)
1027+
assert b"Profile Page" in response.data
10261028

10271029

10281030
def test_request_loader_does_not_fail_with_invalid_token(client):

tests/test_changeable.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ def authned(myapp, user, **extra_args):
207207

208208
def test_change_updates_remember(app, client):
209209
# Test that on change password - remember cookie updated
210-
authenticate(client)
210+
authenticate(client, remember=True)
211211

212212
response = client.post(
213213
"/change",

tests/test_misc.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1694,3 +1694,12 @@ def test_td_format_humanize_fr(app, humanizer):
16941694
assert "1 jour, 2 heures et 40 secondes" == td_format(
16951695
timedelta(hours=26, seconds=40)
16961696
)
1697+
1698+
1699+
@pytest.mark.settings(default_remember_me=True)
1700+
def test_remember_login_form(app, client):
1701+
# ensure form has the checkbox set if default set.
1702+
response = client.get("/login")
1703+
assert response.status_code == 200
1704+
r = get_form_input(response, "remember")
1705+
assert "checked" in r

tests/test_unified_signin.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
reset_fresh,
3838
reset_fresh_auth_token,
3939
setup_tf_sms,
40+
get_form_input,
4041
)
4142
from tests.test_webauthn import HackWebauthnUtil, reg_2_keys
4243

@@ -2482,3 +2483,12 @@ def test_override_user_locked_gr(app, client, get_message):
24822483
"GENERIC_AUTHN_FAILED"
24832484
)
24842485
assert "identity" not in response.json["response"]["field_errors"]
2486+
2487+
2488+
@pytest.mark.settings(default_remember_me=True)
2489+
def test_remember_login_form(app, client):
2490+
# ensure form has the checkbox set if default set.
2491+
response = client.get("/us-signin")
2492+
assert response.status_code == 200
2493+
r = get_form_input(response, "remember")
2494+
assert "checked" in r

tests/test_utils.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,15 @@ def authenticate(
4444
password="password",
4545
endpoint=None,
4646
csrf=False,
47+
remember=False,
4748
**kwargs,
4849
):
49-
data = dict(email=email, password=password, remember="y")
50+
data = dict(email=email, password=password)
5051
if csrf:
5152
response = client.get(endpoint or "/login")
5253
data["csrf_token"] = get_form_input_value(response, "csrf_token")
54+
if remember:
55+
data["remember"] = "y"
5356
return client.post(endpoint or "/login", data=data, **kwargs)
5457

5558

tests/test_webauthn.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
setup_tf_sms,
3535
verify_token,
3636
check_signals,
37+
get_form_input,
3738
)
3839

3940
from flask_security import (
@@ -1590,7 +1591,7 @@ def test_remember_token(client):
15901591
assert client.get_cookie("remember_token")
15911592
client.delete_cookie("session")
15921593
response = client.get("/profile")
1593-
assert b"profile" in response.data
1594+
assert b"Profile Page" in response.data
15941595

15951596

15961597
@pytest.mark.two_factor()
@@ -1869,3 +1870,12 @@ def test_csrf(app, client, get_message):
18691870
data["csrf_token"] = csrf_token
18701871
response = client.post(response_url, data=data)
18711872
assert check_location(app, response.location, "/post-login")
1873+
1874+
1875+
@pytest.mark.settings(default_remember_me=True)
1876+
def test_remember_login_form(app, client):
1877+
# ensure form has the checkbox set if default set.
1878+
response = client.get("/wan-signin")
1879+
assert response.status_code == 200
1880+
r = get_form_input(response, "remember")
1881+
assert "checked" in r

0 commit comments

Comments
 (0)