Skip to content

Commit 9b4e7c4

Browse files
committed
user: disable the javascript requirement for login view
* Closes: rero#3826. * Uses a new login form. Co-Authored-by: Johnny Mariéthoz <[email protected]>
1 parent 171dc7d commit 9b4e7c4

File tree

6 files changed

+116
-82
lines changed

6 files changed

+116
-82
lines changed

rero_ils/config.py

+3
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@
170170
i18n_terms_filter,
171171
or_terms_filter_by_criteria,
172172
)
173+
from .theme.forms import LoginForm
173174
from .utils import TranslatedList, get_current_language
174175

175176
APP_THEME = ["bootstrap3"]
@@ -308,6 +309,8 @@ def _(x):
308309
#: Email subjects for password reset
309310
SECURITY_EMAIL_SUBJECT_PASSWORD_RESET = _("RERO ID password reset")
310311
SECURITY_EMAIL_SUBJECT_PASSWORD_NOTICE = _("Your RERO ID password has been reset")
312+
#: Custom login form to support username login
313+
SECURITY_LOGIN_FORM = LoginForm
311314
#: Redis session storage URL.
312315
ACCOUNTS_SESSION_REDIS_URL = "redis://localhost:6379/1"
313316
#: Enable session/user id request tracing. This feature will add X-Session-ID

rero_ils/theme/assets/js/reroils/login.js

-79
This file was deleted.

rero_ils/theme/assets/js/reroils/public.js

-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
1919
import 'bootstrap';
2020
import './tooltip';
2121
import './toast';
22-
import './login';
2322
import './toggle';
2423
import './ills_request';
2524
import './show_more';

rero_ils/theme/forms.py

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# RERO ILS
4+
# Copyright (C) 2024 RERO
5+
#
6+
# This program is free software: you can redistribute it and/or modify
7+
# it under the terms of the GNU Affero General Public License as published by
8+
# the Free Software Foundation, version 3 of the License.
9+
#
10+
# This program is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU Affero General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU Affero General Public License
16+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
18+
"""RERO ILS forms."""
19+
20+
from flask_security.confirmable import requires_confirmation
21+
from flask_security.forms import Form
22+
from flask_security.forms import LoginForm as BaseLoginForm
23+
from flask_security.utils import get_message, hash_password, verify_and_update_password
24+
25+
from rero_ils.modules.users.api import User
26+
27+
28+
class LoginForm(BaseLoginForm):
29+
"""The login form (/signin)."""
30+
31+
def validate(self, extra_validators=None):
32+
"""Validate the form.
33+
34+
Copied from invenio-flask-security.
35+
"""
36+
if not super(Form, self).validate(extra_validators=extra_validators):
37+
return False
38+
39+
# uses our own manner to retrieve the user, the rest is identical to flask-security
40+
self.user = None
41+
user = User.get_by_username_or_email(self.email.data)
42+
if user:
43+
self.user = user.user
44+
45+
if self.user is None:
46+
self.email.errors.append(get_message("USER_DOES_NOT_EXIST")[0])
47+
# Reduce timing variation between existing and non-existung users
48+
hash_password(self.password.data)
49+
return False
50+
if not self.user.password:
51+
self.password.errors.append(get_message("PASSWORD_NOT_SET")[0])
52+
# Reduce timing variation between existing and non-existung users
53+
hash_password(self.password.data)
54+
return False
55+
if not verify_and_update_password(self.password.data, self.user):
56+
self.password.errors.append(get_message("INVALID_PASSWORD")[0])
57+
return False
58+
if requires_confirmation(self.user):
59+
self.email.errors.append(get_message("CONFIRMATION_REQUIRED")[0])
60+
return False
61+
if not self.user.is_active:
62+
self.email.errors.append(get_message("DISABLED_ACCOUNT")[0])
63+
return False
64+
return True

rero_ils/theme/templates/rero_ils/login_user.html

+6-2
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,12 @@ <h3 class="card-title my-4">{{_('Log in to account') }}</h3>
4343
</button>
4444
</div>
4545

46-
<form action="{{ url_for_security('login') }}" data-action="/api/login" method="POST" id="login-user"
47-
name="login_user_form">
46+
<form
47+
action="{{ url_for_security('login') }}"
48+
method="POST"
49+
id="login-user"
50+
name="login_user_form"
51+
>
4852
{{form.hidden_tag()}}
4953
{{form_errors(form)}}
5054
{{ render_field(form.email, icon="fa fa-user", autofocus=True, errormsg=False, placeholder=_("Username or e-mail")) }}

tests/ui/test_views.py

+43
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import pytest
2323
from flask import session, url_for
2424
from flask_login import login_user, logout_user
25+
from flask_security import url_for_security
26+
from invenio_accounts.testutils import login_user_via_view
2527
from utils import postdata
2628

2729
from rero_ils.modules.users.api import user_formatted_name
@@ -183,3 +185,44 @@ def test_google_analytics(client, app):
183185
app.config["RERO_ILS_GOOGLE_ANALYTICS_TAG_ID"] = "GA-Foo"
184186
result = client.get(url_for("rero_ils.index"))
185187
assert "gtag" in result.text
188+
189+
190+
def test_login(client, app, user_with_profile):
191+
"""Testing the frontend login view."""
192+
193+
## bad password
194+
# be sure that no one is logged
195+
client.get(url_for_security("logout"))
196+
res = login_user_via_view(
197+
client=client, email=user_with_profile.username, password="bad password"
198+
)
199+
assert "Invalid user or password" in res.text
200+
assert res.status_code == 200
201+
202+
## bad email
203+
client.get(url_for_security("logout"))
204+
res = login_user_via_view(
205+
client=client,
206+
207+
password=user_with_profile.password_plaintext,
208+
)
209+
assert "Invalid user or password" in res.text
210+
assert res.status_code == 200
211+
212+
## login with email
213+
client.get(url_for_security("logout"))
214+
res = login_user_via_view(
215+
client=client,
216+
email=user_with_profile.email,
217+
password=user_with_profile.password_plaintext,
218+
)
219+
assert res.status_code == 302
220+
221+
## login with username
222+
client.get(url_for_security("logout"))
223+
res = login_user_via_view(
224+
client=client,
225+
email=user_with_profile.username,
226+
password=user_with_profile.password_plaintext,
227+
)
228+
assert res.status_code == 302

0 commit comments

Comments
 (0)