fix: prevent ghost memberships from soft-deleted users

This commit is contained in:
2026-06-10 05:30:24 +00:00
parent 05589ce442
commit a6d74d9316
4 changed files with 61 additions and 15 deletions
+2 -12
View File
@@ -306,10 +306,10 @@ def admin_delete_user(user_id):
from gatehouse_app.models.user.user import User as _User
from gatehouse_app.models.ssh_ca.ssh_key import SSHKey
from gatehouse_app.models.ssh_ca.ssh_certificate import SSHCertificate
from gatehouse_app.models.auth.authentication_method import OAuthState
from gatehouse_app.extensions import db as _db
from gatehouse_app.utils.constants import AuditAction, OrganizationRole
from gatehouse_app.services.audit_service import AuditService
from gatehouse_app.services.user_service import UserService
caller = g.current_user
data = request.get_json() or {}
@@ -372,20 +372,10 @@ def admin_delete_user(user_id):
target_email = target.email
target_id_str = str(target.id)
now = datetime.now(timezone.utc)
try:
# Soft delete the user — set deleted_at timestamp.
target.deleted_at = now
# Soft delete associated OAuthState records.
OAuthState.query.filter_by(user_id=target_id_str).filter(OAuthState.deleted_at == None).update(
{"deleted_at": now}, synchronize_session=False
)
_db.session.flush()
UserService.delete_user(target, soft=True)
except Exception as exc:
_db.session.rollback()
_logger.error(f"Soft delete failed for {target_id_str}: {exc}")
return api_response(success=False, message="Failed to delete user account. Please try again.", status=500, error_type="SERVER_ERROR")
@@ -68,11 +68,18 @@ class Organization(BaseModel):
def is_member(self, user_id: str) -> bool:
"""Check if a user is a member of the organization."""
from gatehouse_app.models.organization.organization_member import OrganizationMember
from gatehouse_app.models.user.user import User
return (
OrganizationMember.query.filter_by(
user_id=user_id, organization_id=self.id, deleted_at=None
).first()
db.session.query(OrganizationMember)
.join(User, OrganizationMember.user_id == User.id)
.filter(
OrganizationMember.user_id == user_id,
OrganizationMember.organization_id == self.id,
OrganizationMember.deleted_at.is_(None),
User.deleted_at.is_(None),
)
.first()
is not None
)
def get_active_members(self):
+7
View File
@@ -105,6 +105,7 @@ class UserService:
- Session (all active sessions killed)
- OIDCAuthCode (pending auth codes invalidated)
- OIDCRefreshToken (refresh tokens invalidated)
- OAuthState (OAuth flow states invalidated)
- OIDCSession (OIDC sessions killed)
- OIDCTokenMetadata (token metadata hidden)
@@ -120,6 +121,7 @@ class UserService:
"""
from datetime import datetime, timezone
from gatehouse_app.extensions import db as _db
from gatehouse_app.models.auth.authentication_method import OAuthState
if soft:
now = datetime.now(timezone.utc)
@@ -169,6 +171,11 @@ class UserService:
pass
cert.deleted_at = now
# --- OAuth states -----------------------------------------------
OAuthState.query.filter_by(user_id=user.id).filter(
OAuthState.deleted_at == None
).update({"deleted_at": now}, synchronize_session=False)
# --- Sessions ---------------------------------------------------
for session in user.sessions:
if session.deleted_at is None: