mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-12-28 21:15:26 -05:00
security: implement user lockout (#1552)
* add data-types required for login security * implement user lockout checking at login * cleanup legacy patterns * expose passwords in test_user * test user lockout after bad attempts * test user service * bump alembic version * save increment to database * add locked_at to datetime transformer on import * do proper test cleanup * implement scheduled task * spelling * document env variables * implement context manager for session * use context manager * implement reset script * cleanup generator * run generator * implement API endpoint for resetting locked users * add button to reset all locked users * add info when account is locked * use ignore instead of expect-error
This commit is contained in:
@@ -8,7 +8,9 @@ from mealie.routes._base import BaseAdminController, controller
|
||||
from mealie.routes._base.mixins import HttpRepo
|
||||
from mealie.schema.response.pagination import PaginationQuery
|
||||
from mealie.schema.response.responses import ErrorResponse
|
||||
from mealie.schema.user.auth import UnlockResults
|
||||
from mealie.schema.user.user import UserIn, UserOut, UserPagination
|
||||
from mealie.services.user_services.user_service import UserService
|
||||
|
||||
router = APIRouter(prefix="/users", tags=["Admin: Users"])
|
||||
|
||||
@@ -17,9 +19,6 @@ router = APIRouter(prefix="/users", tags=["Admin: Users"])
|
||||
class AdminUserManagementRoutes(BaseAdminController):
|
||||
@cached_property
|
||||
def repo(self):
|
||||
if not self.user:
|
||||
raise Exception("No user is logged in.")
|
||||
|
||||
return self.repos.users
|
||||
|
||||
# =======================================================================
|
||||
@@ -44,6 +43,13 @@ class AdminUserManagementRoutes(BaseAdminController):
|
||||
data.password = security.hash_password(data.password)
|
||||
return self.mixins.create_one(data)
|
||||
|
||||
@router.post("/unlock", response_model=UnlockResults)
|
||||
def unlock_users(self, force: bool = False) -> UnlockResults:
|
||||
user_service = UserService(self.repos)
|
||||
unlocked = user_service.reset_locked_users(force=force)
|
||||
|
||||
return UnlockResults(unlocked=unlocked)
|
||||
|
||||
@router.get("/{item_id}", response_model=UserOut)
|
||||
def get_one(self, item_id: UUID4):
|
||||
return self.mixins.get_one(item_id)
|
||||
|
||||
@@ -10,6 +10,7 @@ from sqlalchemy.orm.session import Session
|
||||
from mealie.core import security
|
||||
from mealie.core.dependencies import get_current_user
|
||||
from mealie.core.security import authenticate_user
|
||||
from mealie.core.security.security import UserLockedOut
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.routes._base.routers import UserAPIRouter
|
||||
from mealie.schema.user import PrivateUser
|
||||
@@ -53,7 +54,10 @@ def get_token(data: CustomOAuth2Form = Depends(), session: Session = Depends(gen
|
||||
email = data.username
|
||||
password = data.password
|
||||
|
||||
user = authenticate_user(session, email, password) # type: ignore
|
||||
try:
|
||||
user = authenticate_user(session, email, password) # type: ignore
|
||||
except UserLockedOut as e:
|
||||
raise HTTPException(status_code=status.HTTP_423_LOCKED, detail="User is locked out") from e
|
||||
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
|
||||
Reference in New Issue
Block a user