fix resetting of invalid logins: (#2002)
* Fixes issue in FailedLogin model: - fix data-model to remove nested 'attempted.attempted' - migrate existing data to remove nested field * Also, avoid setting dt_now() in model as that results in fixed date for all objects: - update FailedLogin to update 'attempted' date on every attempt - also update PageNote object to set date in constructor * Update text for too many logins to make it clear it is set only if its a valid email * fixes #2001
This commit is contained in:
parent
41d43ae249
commit
5f53db75ee
@ -17,7 +17,7 @@ from pymongo.errors import InvalidName
|
||||
from .migrations import BaseMigration
|
||||
|
||||
|
||||
CURR_DB_VERSION = "0034"
|
||||
CURR_DB_VERSION = "0035"
|
||||
|
||||
|
||||
# ============================================================================
|
||||
|
@ -0,0 +1,37 @@
|
||||
"""
|
||||
Migration 0035 -- fix model for failed logins
|
||||
"""
|
||||
|
||||
from btrixcloud.migrations import BaseMigration
|
||||
|
||||
|
||||
MIGRATION_VERSION = "0035"
|
||||
|
||||
|
||||
class Migration(BaseMigration):
|
||||
"""Migration class."""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def __init__(self, mdb, **kwargs):
|
||||
super().__init__(mdb, migration_version=MIGRATION_VERSION)
|
||||
|
||||
async def migrate_up(self):
|
||||
"""Perform migration up.
|
||||
|
||||
Set created from attempted.attempted
|
||||
"""
|
||||
failed_logins = self.mdb["logins"]
|
||||
|
||||
try:
|
||||
res = await failed_logins.update_many(
|
||||
{"attempted.attempted": {"$exists": 1}},
|
||||
[{"$set": {"attempted": "$attempted.attempted"}}],
|
||||
)
|
||||
updated = res.modified_count
|
||||
print(f"{updated} failed logins fixed", flush=True)
|
||||
# pylint: disable=broad-exception-caught
|
||||
except Exception as err:
|
||||
print(
|
||||
f"Error fixing failed logins: {err}",
|
||||
flush=True,
|
||||
)
|
@ -24,7 +24,6 @@ from pydantic import (
|
||||
# from fastapi_users import models as fastapi_users_models
|
||||
|
||||
from .db import BaseMongoModel
|
||||
from .utils import dt_now
|
||||
|
||||
# crawl scale for constraint
|
||||
MAX_CRAWL_SCALE = int(os.environ.get("MAX_CRAWL_SCALE", 3))
|
||||
@ -162,7 +161,7 @@ class FailedLogin(BaseMongoModel):
|
||||
Failed login model
|
||||
"""
|
||||
|
||||
attempted: datetime = dt_now()
|
||||
attempted: datetime
|
||||
email: str
|
||||
|
||||
# Consecutive failed logins, reset to 0 on successful login or after
|
||||
@ -1996,7 +1995,7 @@ class PageNote(BaseModel):
|
||||
|
||||
id: UUID
|
||||
text: str
|
||||
created: datetime = dt_now()
|
||||
created: datetime
|
||||
userid: UUID
|
||||
userName: str
|
||||
|
||||
|
@ -329,7 +329,9 @@ class PageOps:
|
||||
crawl_id: str,
|
||||
) -> Dict[str, Union[bool, PageNote]]:
|
||||
"""Add note to page"""
|
||||
note = PageNote(id=uuid4(), text=text, userid=user.id, userName=user.name)
|
||||
note = PageNote(
|
||||
id=uuid4(), text=text, userid=user.id, userName=user.name, created=dt_now()
|
||||
)
|
||||
|
||||
modified = dt_now()
|
||||
|
||||
@ -371,7 +373,11 @@ class PageOps:
|
||||
raise HTTPException(status_code=404, detail="page_note_not_found")
|
||||
|
||||
new_note = PageNote(
|
||||
id=note_in.id, text=note_in.text, userid=user.id, userName=user.name
|
||||
id=note_in.id,
|
||||
text=note_in.text,
|
||||
userid=user.id,
|
||||
userName=user.name,
|
||||
created=dt_now(),
|
||||
)
|
||||
page_notes[matching_index] = new_note.dict()
|
||||
|
||||
|
@ -37,7 +37,7 @@ from .models import (
|
||||
SuccessResponse,
|
||||
)
|
||||
from .pagination import DEFAULT_PAGE_SIZE, paginated_format
|
||||
from .utils import is_bool
|
||||
from .utils import is_bool, dt_now
|
||||
|
||||
from .auth import (
|
||||
init_jwt_auth,
|
||||
@ -525,13 +525,13 @@ class UserManager:
|
||||
|
||||
If a FailedLogin object doesn't already exist, create it
|
||||
"""
|
||||
failed_login = FailedLogin(id=uuid4(), email=email)
|
||||
failed_login = FailedLogin(id=uuid4(), email=email, attempted=dt_now())
|
||||
|
||||
await self.failed_logins.find_one_and_update(
|
||||
{"email": email},
|
||||
{
|
||||
"$setOnInsert": failed_login.to_dict(exclude={"count", "attempted"}),
|
||||
"$set": {"attempted": failed_login.dict(include={"attempted"})},
|
||||
"$set": {"attempted": failed_login.attempted},
|
||||
"$inc": {"count": 1},
|
||||
},
|
||||
upsert=True,
|
||||
|
@ -424,16 +424,14 @@ def test_user_change_role(admin_auth_headers, default_org_id):
|
||||
|
||||
def test_forgot_password():
|
||||
r = requests.post(
|
||||
f"{API_PREFIX}/auth/forgot-password",
|
||||
json={"email": "no-such-user@example.com"}
|
||||
f"{API_PREFIX}/auth/forgot-password", json={"email": "no-such-user@example.com"}
|
||||
)
|
||||
# always return success for security reasons even if user doesn't exist
|
||||
assert r.status_code == 202
|
||||
detail = r.json()["success"] == True
|
||||
|
||||
r = requests.post(
|
||||
f"{API_PREFIX}/auth/forgot-password",
|
||||
json={"email": VALID_USER_EMAIL}
|
||||
f"{API_PREFIX}/auth/forgot-password", json={"email": VALID_USER_EMAIL}
|
||||
)
|
||||
assert r.status_code == 202
|
||||
detail = r.json()["success"] == True
|
||||
|
@ -375,7 +375,7 @@ export class LogInPage extends LiteElement {
|
||||
let message = msg("Sorry, invalid username or password");
|
||||
if (e.statusCode === 429) {
|
||||
message = msg(
|
||||
"Sorry, too many failed login attempts. A reset password link has been sent to your email.",
|
||||
"Sorry, too many failed login attempts. If this is a valid email, a reset password link has been sent to your email.",
|
||||
);
|
||||
}
|
||||
this.formStateService.send({
|
||||
|
Loading…
Reference in New Issue
Block a user