Skip to content

Commit ad14e3c

Browse files
authored
Improve login form handling errors (#93)
* Pass username/identifier to InvalidCredentials * Allow hiding NoSuchUser error
1 parent 02328d0 commit ad14e3c

File tree

7 files changed

+20
-5
lines changed

7 files changed

+20
-5
lines changed

CHANGES.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ Version 0.7
77
- Support multiple id fields in SAML identity provider
88
- Include ``client_id`` in authlib logout URL since some OIDC providers mayrequire this
99
- Allow setting timeout for authlib token requests (default: 10 seconds)
10+
- Add new ``MULTIPASS_HIDE_NO_SUCH_USER`` config setting to convert ``NoSuchUser``
11+
exceptions to ``InvalidCredentials`` to avoid disclosing whether a username is valid
12+
- Include the username in the ``identifier`` attribute of the ``InvalidCredentials``
13+
exception so applications can apply e.g. per-username rate limiting
1014

1115
Version 0.6
1216
-----------

docs/quickstart.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ The following configuration values exist for Flask-Multipass:
5454
``MULTIPASS_FAILURE_CATEGORY`` Category of message when flashing after unsuccessful login
5555
``MULTIPASS_ALL_MATCHING_IDENTITIES`` If true, all matching identities are passed after successful authentication
5656
``MULTIPASS_REQUIRE_IDENTITY`` If true, ``IdentityRetrievalFailed`` is raised when no matching identities are found, otherwise empty list is passed
57+
``MULTIPASS_HIDE_NO_SUCH_USER`` If true, ``InvalidCredentials`` instead of ``NoSuchUser`` is raised when no user is found in the system
5758
====================================== =========================================
5859

5960
A configuration example can be found here: :ref:`config_example`

flask_multipass/core.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@
1212
from werkzeug.exceptions import NotFound
1313

1414
from flask_multipass.auth import AuthProvider
15-
from flask_multipass.exceptions import GroupRetrievalFailed, IdentityRetrievalFailed, MultipassException
15+
from flask_multipass.exceptions import (
16+
GroupRetrievalFailed,
17+
IdentityRetrievalFailed,
18+
InvalidCredentials,
19+
MultipassException,
20+
NoSuchUser,
21+
)
1622
from flask_multipass.identity import IdentityProvider
1723
from flask_multipass.util import (
1824
get_canonical_provider_map,
@@ -72,6 +78,7 @@ def init_app(self, app):
7278
app.config.setdefault('MULTIPASS_FAILURE_CATEGORY', 'error')
7379
app.config.setdefault('MULTIPASS_ALL_MATCHING_IDENTITIES', False)
7480
app.config.setdefault('MULTIPASS_REQUIRE_IDENTITY', True)
81+
app.config.setdefault('MULTIPASS_HIDE_NO_SUCH_USER', False)
7582
with app.app_context():
7683
self._create_login_rule()
7784
state.auth_providers = ImmutableDict(self._create_providers('AUTH', AuthProvider))
@@ -528,6 +535,8 @@ def handle_login_form(self, provider, data):
528535
try:
529536
response = provider.process_local_login(data)
530537
except MultipassException as e:
538+
if isinstance(e, NoSuchUser) and current_app.config['MULTIPASS_HIDE_NO_SUCH_USER']:
539+
e = InvalidCredentials(e.provider)
531540
self.handle_auth_error(e)
532541
else:
533542
return response

flask_multipass/exceptions.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ def __init__(self, details=None, provider=None):
3232
class InvalidCredentials(AuthenticationFailed):
3333
"""Indicates a failure to authenticate using the given credentials."""
3434

35-
def __init__(self, details=None, provider=None):
35+
def __init__(self, details=None, provider=None, identifier=None):
3636
AuthenticationFailed.__init__(self, 'Invalid credentials', details=details, provider=provider)
37+
self.identifier = identifier
3738

3839

3940
class IdentityRetrievalFailed(MultipassException):

flask_multipass/providers/ldap/providers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def process_local_login(self, data):
8181
raise NoSuchUser(provider=self)
8282
current_ldap.connection.simple_bind_s(user_dn, password)
8383
except INVALID_CREDENTIALS:
84-
raise InvalidCredentials(provider=self)
84+
raise InvalidCredentials(provider=self, identifier=data['username'])
8585
auth_info = AuthInfo(self, identifier=user_data[self.ldap_settings['uid']][0])
8686
return self.multipass.handle_auth_success(auth_info)
8787

flask_multipass/providers/sqlalchemy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def process_local_login(self, data):
5555
if not identity:
5656
raise NoSuchUser(provider=self)
5757
if not self.check_password(identity, data['password']):
58-
raise InvalidCredentials(provider=self)
58+
raise InvalidCredentials(provider=self, identifier=data['identifier'])
5959
auth_info = AuthInfo(self, identity=identity)
6060
return self.multipass.handle_auth_success(auth_info)
6161

flask_multipass/providers/static.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def process_local_login(self, data):
4444
if password is None:
4545
raise NoSuchUser(provider=self)
4646
if password != data['password']:
47-
raise InvalidCredentials(provider=self)
47+
raise InvalidCredentials(provider=self, identifier=data['username'])
4848
auth_info = AuthInfo(self, username=data['username'])
4949
return self.multipass.handle_auth_success(auth_info)
5050

0 commit comments

Comments
 (0)