Block login access for relieved employees

This commit is contained in:
A R R R Associates
2026-09-03 11:49:30 +05:30
parent 0c5f555eb4
commit 10e66f76c9
3 changed files with 53 additions and 3 deletions
+15
View File
@@ -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
+23
View File
@@ -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
View File
@@ -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,