Block login access for relieved employees
This commit is contained in:
@@ -6,6 +6,7 @@ from sqlalchemy import select
|
||||
|
||||
from app.core.db.deps import get_common_db
|
||||
from app.modules.core.iam.models import User
|
||||
from app.modules.employees.models import Employee
|
||||
from app.modules.core.tenancy.models import Branch
|
||||
from app.modules.core.tenancy.settings_models import BranchSettings
|
||||
|
||||
@@ -22,6 +23,20 @@ def get_current_user(request: Request, db: Session = Depends(get_common_db)) ->
|
||||
|
||||
user = db.execute(select(User).where(User.id == int(user_id))).scalar_one_or_none()
|
||||
if not user or not user.is_active or not getattr(user, "allow_login", True) or getattr(user, "is_locked", False) or getattr(user, "deleted_at", None) is not None:
|
||||
request.session.pop(SESSION_USER_ID_KEY, None)
|
||||
request.session.pop(SESSION_LOGIN_AT_KEY, None)
|
||||
return None
|
||||
|
||||
relieved_employee = db.execute(
|
||||
select(Employee.id).where(
|
||||
Employee.user_id == int(user.id),
|
||||
Employee.tenant_id == int(user.tenant_id),
|
||||
Employee.status == "relieved",
|
||||
).limit(1)
|
||||
).scalar_one_or_none()
|
||||
if relieved_employee is not None:
|
||||
request.session.pop(SESSION_USER_ID_KEY, None)
|
||||
request.session.pop(SESSION_LOGIN_AT_KEY, None)
|
||||
return None
|
||||
|
||||
# Enforce session duration from BranchSettings
|
||||
|
||||
@@ -298,6 +298,24 @@ def create_login_user_for_employee(
|
||||
return user
|
||||
|
||||
|
||||
def _disable_relieved_employee_login(db: Session, emp: Employee) -> None:
|
||||
"""Revoke login for a relieved employee without deleting the user identity.
|
||||
|
||||
Historical engagements, attendance, payroll, documents and audit references
|
||||
continue to point to the same User row. Authentication is disabled through
|
||||
allow_login, while the session/auth layer independently blocks any employee
|
||||
whose HR status is relieved.
|
||||
"""
|
||||
if (getattr(emp, "status", "") or "").strip().lower() != "relieved":
|
||||
return
|
||||
user_id = getattr(emp, "user_id", None)
|
||||
if not user_id:
|
||||
return
|
||||
linked_user = db.get(User, int(user_id))
|
||||
if linked_user is not None:
|
||||
linked_user.allow_login = False
|
||||
|
||||
|
||||
def create_employee(db: Session, actor: User, scope: EmployeeScope, data: dict[str, Any]) -> Employee:
|
||||
cleaned = _clean_payload(data)
|
||||
partner_staff_mode = bool(scope.is_partner and not scope.is_system_admin and not scope.is_firm_admin)
|
||||
@@ -388,6 +406,8 @@ def create_employee(db: Session, actor: User, scope: EmployeeScope, data: dict[s
|
||||
updated_by_user_id=actor.id,
|
||||
)
|
||||
db.add(emp)
|
||||
db.flush()
|
||||
_disable_relieved_employee_login(db, emp)
|
||||
db.commit()
|
||||
db.refresh(emp)
|
||||
return emp
|
||||
@@ -426,6 +446,7 @@ def update_employee(db: Session, actor: User, emp: Employee, data: dict[str, Any
|
||||
setattr(emp, field, cleaned.get(field))
|
||||
emp.updated_by_user_id = actor.id
|
||||
emp.updated_at_utc = datetime.now(timezone.utc)
|
||||
_disable_relieved_employee_login(db, emp)
|
||||
db.commit()
|
||||
db.refresh(emp)
|
||||
return emp
|
||||
@@ -441,6 +462,7 @@ def change_employee_status(db: Session, actor: User, emp: Employee, status: str,
|
||||
emp.date_of_leaving = date_of_leaving
|
||||
emp.updated_by_user_id = actor.id
|
||||
emp.updated_at_utc = datetime.now(timezone.utc)
|
||||
_disable_relieved_employee_login(db, emp)
|
||||
db.commit()
|
||||
db.refresh(emp)
|
||||
return emp
|
||||
@@ -2269,6 +2291,7 @@ def complete_offboarding_request(db: Session, user: User, req: EmployeeOffboardi
|
||||
employee.is_active = False
|
||||
employee.date_of_leaving = req.approved_relieving_date or req.requested_relieving_date or date.today()
|
||||
employee.updated_by_user_id = user.id
|
||||
_disable_relieved_employee_login(db, employee)
|
||||
req.status = "completed"
|
||||
req.completed_by_user_id = user.id
|
||||
req.completed_at_utc = datetime.now(timezone.utc)
|
||||
|
||||
+15
-3
@@ -25,6 +25,7 @@ from app.modules.core.rbac.models import Permission, Role, RolePermission, UserR
|
||||
from app.modules.core.tenancy.models import Branch, FinancialYear, Tenant
|
||||
from app.modules.core.tenancy.settings_models import BranchSettings
|
||||
from app.modules.email_integration.services import send_auth_otp_email, send_password_changed_email
|
||||
from app.modules.employees.models import Employee
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -237,10 +238,21 @@ def _post_login_redirect(must_change_password: bool, permissions: set[str], role
|
||||
|
||||
return "/employee/dashboard"
|
||||
|
||||
def _is_user_login_allowed(user: User) -> tuple[bool, str | None]:
|
||||
def _is_user_login_allowed(user: User, db=None) -> tuple[bool, str | None]:
|
||||
if not user:
|
||||
return False, "Invalid credentials"
|
||||
|
||||
if db is not None:
|
||||
relieved_employee = db.execute(
|
||||
select(Employee.id).where(
|
||||
Employee.user_id == int(user.id),
|
||||
Employee.tenant_id == int(user.tenant_id),
|
||||
Employee.status == "relieved",
|
||||
).limit(1)
|
||||
).scalar_one_or_none()
|
||||
if relieved_employee is not None:
|
||||
return False, "Login is disabled because this employee has been relieved."
|
||||
|
||||
if not getattr(user, "is_active", True):
|
||||
return False, "User account is inactive."
|
||||
|
||||
@@ -517,7 +529,7 @@ def _submit_login(
|
||||
else bool(user and not is_client_only)
|
||||
)
|
||||
|
||||
can_login, blocked_reason = _is_user_login_allowed(user)
|
||||
can_login, blocked_reason = _is_user_login_allowed(user, db)
|
||||
password_ok = bool(user and verify_password(password, user.password_hash))
|
||||
|
||||
if not user or not portal_allowed or not can_login or not password_ok:
|
||||
@@ -861,7 +873,7 @@ def forgot_password_submit(
|
||||
status_code=200,
|
||||
)
|
||||
|
||||
can_login, _ = _is_user_login_allowed(user)
|
||||
can_login, _ = _is_user_login_allowed(user, db)
|
||||
if not can_login:
|
||||
return _render_forgot_password(
|
||||
request,
|
||||
|
||||
Reference in New Issue
Block a user