users: add case-insensitive index to maintain backwards compatibility with fastapi-users (#1319)
follow up to #1290 Based on implementation in: https://github.com/fastapi-users/fastapi-users-db-mongodb/blob/main/fastapi_users_db_mongodb/__init__.py
This commit is contained in:
parent
3c884f94c9
commit
c1d3beda9c
@ -19,6 +19,7 @@ from fastapi import (
|
||||
)
|
||||
|
||||
from pymongo.errors import DuplicateKeyError
|
||||
from pymongo.collation import Collation
|
||||
|
||||
from .models import (
|
||||
UserCreate,
|
||||
@ -65,6 +66,8 @@ class UserManager:
|
||||
self.invites = invites
|
||||
self.org_ops = None
|
||||
|
||||
self.email_collation = Collation("en", strength=2)
|
||||
|
||||
self.registration_enabled = is_bool(os.environ.get("REGISTRATION_ENABLED"))
|
||||
|
||||
# pylint: disable=attribute-defined-outside-init
|
||||
@ -78,6 +81,13 @@ class UserManager:
|
||||
"""init lookup index"""
|
||||
await self.users.create_index("id", unique=True)
|
||||
await self.users.create_index("email", unique=True)
|
||||
|
||||
await self.users.create_index(
|
||||
"email",
|
||||
name="case_insensitive_email_index",
|
||||
collation=self.email_collation,
|
||||
)
|
||||
|
||||
# Expire failed logins object after one hour
|
||||
await self.failed_logins.create_index("attempted", expireAfterSeconds=3600)
|
||||
|
||||
@ -379,7 +389,9 @@ class UserManager:
|
||||
|
||||
async def get_by_email(self, email: str) -> Optional[User]:
|
||||
"""get user by email"""
|
||||
user = await self.users.find_one({"email": email})
|
||||
user = await self.users.find_one(
|
||||
{"email": email}, collation=self.email_collation
|
||||
)
|
||||
if not user:
|
||||
return None
|
||||
|
||||
@ -535,7 +547,9 @@ class UserManager:
|
||||
|
||||
async def reset_failed_logins(self, email: str) -> None:
|
||||
"""Reset consecutive failed login attempts by deleting FailedLogin object"""
|
||||
await self.failed_logins.delete_one({"email": email})
|
||||
await self.failed_logins.delete_one(
|
||||
{"email": email}, collation=self.email_collation
|
||||
)
|
||||
|
||||
async def inc_failed_logins(self, email: str) -> None:
|
||||
"""Inc consecutive failed login attempts for user by 1
|
||||
@ -552,11 +566,14 @@ class UserManager:
|
||||
"$inc": {"count": 1},
|
||||
},
|
||||
upsert=True,
|
||||
collation=self.email_collation,
|
||||
)
|
||||
|
||||
async def get_failed_logins_count(self, email: str) -> int:
|
||||
"""Get failed login attempts for user, falling back to 0"""
|
||||
failed_login = await self.failed_logins.find_one({"email": email})
|
||||
failed_login = await self.failed_logins.find_one(
|
||||
{"email": email}, collation=self.email_collation
|
||||
)
|
||||
if not failed_login:
|
||||
return 0
|
||||
return failed_login.get("count", 0)
|
||||
|
@ -15,7 +15,8 @@ ADMIN_PW = "PASSW0RD!"
|
||||
VIEWER_USERNAME = "viewer@example.com"
|
||||
VIEWER_PW = "viewerPASSW0RD!"
|
||||
|
||||
CRAWLER_USERNAME = "crawler@example.com"
|
||||
CRAWLER_USERNAME = "CraWleR@example.com"
|
||||
CRAWLER_USERNAME_LOWERCASE = "crawler@example.com"
|
||||
CRAWLER_PW = "crawlerPASSWORD!"
|
||||
|
||||
_admin_config_id = None
|
||||
|
@ -4,6 +4,8 @@ import time
|
||||
from .conftest import (
|
||||
API_PREFIX,
|
||||
CRAWLER_USERNAME,
|
||||
CRAWLER_USERNAME_LOWERCASE,
|
||||
CRAWLER_PW,
|
||||
ADMIN_PW,
|
||||
ADMIN_USERNAME,
|
||||
FINISHED_STATES,
|
||||
@ -14,7 +16,6 @@ VALID_USER_PW = "validpassw0rd!"
|
||||
VALID_USER_PW_RESET = "new!password"
|
||||
VALID_USER_PW_RESET_AGAIN = "new!password1"
|
||||
|
||||
|
||||
my_id = None
|
||||
valid_user_headers = None
|
||||
|
||||
@ -71,6 +72,20 @@ def test_me_id(admin_auth_headers, default_org_id):
|
||||
assert r.status_code == 404
|
||||
|
||||
|
||||
def test_login_case_insensitive_email():
|
||||
r = requests.post(
|
||||
f"{API_PREFIX}/auth/jwt/login",
|
||||
data={
|
||||
"username": CRAWLER_USERNAME_LOWERCASE,
|
||||
"password": CRAWLER_PW,
|
||||
"grant_type": "password",
|
||||
},
|
||||
)
|
||||
data = r.json()
|
||||
assert r.status_code == 200
|
||||
assert data["access_token"]
|
||||
|
||||
|
||||
def test_add_user_to_org_invalid_password(admin_auth_headers, default_org_id):
|
||||
r = requests.post(
|
||||
f"{API_PREFIX}/orgs/{default_org_id}/add-user",
|
||||
|
Loading…
Reference in New Issue
Block a user