updates
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -12,7 +12,10 @@ from ...bookings.models.booking import Booking, BookingStatus
|
||||
from ...shared.utils.role_helpers import can_manage_users
|
||||
from ...shared.utils.response_helpers import success_response
|
||||
from ...analytics.services.audit_service import audit_service
|
||||
from ...shared.config.logging_config import get_logger
|
||||
from ..schemas.user import CreateUserRequest, UpdateUserRequest
|
||||
|
||||
logger = get_logger(__name__)
|
||||
router = APIRouter(prefix='/users', tags=['users'])
|
||||
|
||||
@router.get('/')
|
||||
@@ -71,7 +74,31 @@ async def create_user(
|
||||
password = user_data.password
|
||||
full_name = user_data.full_name
|
||||
phone_number = user_data.phone_number
|
||||
role_id = user_data.role_id or 3 # Default to customer role
|
||||
|
||||
# Get customer role for default
|
||||
customer_role = db.query(Role).filter(Role.name == 'customer').first()
|
||||
if not customer_role:
|
||||
raise HTTPException(status_code=500, detail='Customer role not found')
|
||||
|
||||
# Handle role - accept either role_id or role name
|
||||
role_id = None
|
||||
if user_data.role_id is not None:
|
||||
role_id = user_data.role_id
|
||||
elif user_data.role is not None:
|
||||
# Convert role name to role_id
|
||||
role_by_name = db.query(Role).filter(Role.name == user_data.role).first()
|
||||
if not role_by_name:
|
||||
raise HTTPException(status_code=400, detail=f'Invalid role name: {user_data.role}')
|
||||
role_id = role_by_name.id
|
||||
else:
|
||||
# Default to customer role
|
||||
role_id = customer_role.id
|
||||
|
||||
# Validate that the role exists
|
||||
role = db.query(Role).filter(Role.id == role_id).first()
|
||||
if not role:
|
||||
raise HTTPException(status_code=400, detail='Invalid role specified')
|
||||
|
||||
existing = db.query(User).filter(User.email == email).first()
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail='Email already exists')
|
||||
@@ -186,18 +213,32 @@ async def update_user(
|
||||
user.email = user_data.email
|
||||
if user_data.phone_number is not None:
|
||||
user.phone = user_data.phone_number
|
||||
if user_data.role_id is not None and can_manage_users(current_user, db):
|
||||
|
||||
# Handle role update - accept either role_id or role name
|
||||
role_id_to_set = None
|
||||
if user_data.role_id is not None:
|
||||
role_id_to_set = user_data.role_id
|
||||
elif user_data.role is not None:
|
||||
# Convert role name to role_id
|
||||
role_by_name = db.query(Role).filter(Role.name == user_data.role).first()
|
||||
if not role_by_name:
|
||||
raise HTTPException(status_code=400, detail=f'Invalid role name: {user_data.role}')
|
||||
role_id_to_set = role_by_name.id
|
||||
|
||||
if role_id_to_set is not None and can_manage_users(current_user, db):
|
||||
# SECURITY: Prevent admin from changing their own role
|
||||
if current_user.id == id:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail='You cannot change your own role. Please ask another admin to do it.'
|
||||
)
|
||||
new_role = db.query(Role).filter(Role.id == user_data.role_id).first()
|
||||
new_role_name = new_role.name if new_role else None
|
||||
if user_data.role_id != old_role_id:
|
||||
changes['role'] = {'old': old_role_name, 'new': new_role_name, 'old_id': old_role_id, 'new_id': user_data.role_id}
|
||||
user.role_id = user_data.role_id
|
||||
new_role = db.query(Role).filter(Role.id == role_id_to_set).first()
|
||||
if not new_role:
|
||||
raise HTTPException(status_code=400, detail='Invalid role ID specified')
|
||||
new_role_name = new_role.name
|
||||
if role_id_to_set != old_role_id:
|
||||
changes['role'] = {'old': old_role_name, 'new': new_role_name, 'old_id': old_role_id, 'new_id': role_id_to_set}
|
||||
user.role_id = role_id_to_set
|
||||
if user_data.is_active is not None and can_manage_users(current_user, db):
|
||||
if user_data.is_active != old_is_active:
|
||||
changes['is_active'] = {'old': old_is_active, 'new': user_data.is_active}
|
||||
@@ -260,8 +301,6 @@ async def update_user(
|
||||
status='success'
|
||||
)
|
||||
except Exception as e:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.warning(f'Failed to log user update audit: {e}')
|
||||
|
||||
user_dict = {'id': user.id, 'email': user.email, 'full_name': user.full_name, 'phone': user.phone, 'phone_number': user.phone, 'currency': getattr(user, 'currency', 'VND'), 'role_id': user.role_id, 'is_active': user.is_active}
|
||||
@@ -304,10 +343,7 @@ async def delete_user(id: int, request: Request, current_user: User=Depends(auth
|
||||
if active_bookings > 0:
|
||||
raise HTTPException(status_code=400, detail='Cannot delete user with active bookings')
|
||||
|
||||
db.delete(user)
|
||||
db.commit()
|
||||
|
||||
# SECURITY: Log user deletion for audit trail
|
||||
# Log user deletion BEFORE deletion (so we can reference the user)
|
||||
try:
|
||||
await audit_service.log_action(
|
||||
db=db,
|
||||
@@ -322,13 +358,49 @@ async def delete_user(id: int, request: Request, current_user: User=Depends(auth
|
||||
status='success'
|
||||
)
|
||||
except Exception as e:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.warning(f'Failed to log user deletion audit: {e}')
|
||||
|
||||
# Handle foreign key constraints: Anonymize audit logs before deletion
|
||||
# This prevents foreign key constraint errors while preserving audit trail
|
||||
try:
|
||||
from ...analytics.models.audit_log import AuditLog
|
||||
audit_logs = db.query(AuditLog).filter(AuditLog.user_id == id).all()
|
||||
for log in audit_logs:
|
||||
# Set user_id to None to break foreign key constraint
|
||||
# This anonymizes the logs while keeping them for security monitoring
|
||||
log.user_id = None
|
||||
if audit_logs:
|
||||
db.flush() # Flush changes before deleting user
|
||||
logger.info(f'Anonymized {len(audit_logs)} audit logs for user {id} before deletion')
|
||||
except Exception as e:
|
||||
logger.warning(f'Could not anonymize audit logs for user {id}: {str(e)}')
|
||||
# Continue with deletion attempt - if it fails, we'll catch the constraint error
|
||||
|
||||
# Delete the user
|
||||
try:
|
||||
db.delete(user)
|
||||
db.commit()
|
||||
except Exception as delete_error:
|
||||
db.rollback()
|
||||
error_msg = str(delete_error)
|
||||
# Check for foreign key constraint errors
|
||||
if 'foreign key' in error_msg.lower() or 'constraint' in error_msg.lower() or '1451' in error_msg:
|
||||
logger.error(f'Foreign key constraint error when deleting user {id}: {error_msg}')
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail='Cannot delete user: User has associated records (bookings, payments, audit logs, etc.) that prevent deletion. Please deactivate the user instead.'
|
||||
)
|
||||
else:
|
||||
logger.error(f'Error deleting user {id}: {error_msg}', exc_info=True)
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f'Error deleting user: {error_msg}'
|
||||
)
|
||||
|
||||
return success_response(message='User deleted successfully')
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
logger.error(f'Unexpected error deleting user {id}: {str(e)}', exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=f'Error deleting user: {str(e)}')
|
||||
Binary file not shown.
@@ -12,6 +12,7 @@ class CreateUserRequest(BaseModel):
|
||||
password: str = Field(..., min_length=8, description="Password")
|
||||
phone_number: Optional[str] = Field(None, max_length=20, description="Phone number")
|
||||
role_id: Optional[int] = Field(None, gt=0, description="Role ID")
|
||||
role: Optional[str] = Field(None, description="Role name (alternative to role_id)")
|
||||
|
||||
@field_validator('password')
|
||||
@classmethod
|
||||
@@ -34,5 +35,6 @@ class UpdateUserRequest(BaseModel):
|
||||
email: Optional[EmailStr] = None
|
||||
phone_number: Optional[str] = Field(None, max_length=20)
|
||||
role_id: Optional[int] = Field(None, gt=0)
|
||||
role: Optional[str] = Field(None, description="Role name (alternative to role_id)")
|
||||
is_active: Optional[bool] = None
|
||||
|
||||
|
||||
Binary file not shown.
@@ -46,7 +46,7 @@ async def get_guest_requests(
|
||||
priority: Optional[str] = Query(None),
|
||||
page: int = Query(1, ge=1),
|
||||
limit: int = Query(20, ge=1, le=100),
|
||||
current_user: User = Depends(authorize_roles('admin', 'staff', 'housekeeping')),
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get guest requests with filtering"""
|
||||
@@ -57,17 +57,22 @@ async def get_guest_requests(
|
||||
joinedload(GuestRequest.guest)
|
||||
)
|
||||
|
||||
# Check if user is housekeeping - they can only see requests assigned to them or unassigned
|
||||
# Check user role to determine access level
|
||||
role = db.query(Role).filter(Role.id == current_user.role_id).first()
|
||||
is_housekeeping = role and role.name == 'housekeeping'
|
||||
role_name = role.name if role else 'customer'
|
||||
|
||||
if is_housekeeping:
|
||||
# Customers can only see their own requests
|
||||
if role_name == 'customer':
|
||||
query = query.filter(GuestRequest.user_id == current_user.id)
|
||||
# Housekeeping can only see requests assigned to them or unassigned
|
||||
elif role_name == 'housekeeping':
|
||||
query = query.filter(
|
||||
or_(
|
||||
GuestRequest.assigned_to == current_user.id,
|
||||
GuestRequest.assigned_to.is_(None)
|
||||
)
|
||||
)
|
||||
# Admin and staff can see all requests (no additional filter needed)
|
||||
|
||||
if status:
|
||||
query = query.filter(GuestRequest.status == status)
|
||||
@@ -379,7 +384,7 @@ async def update_guest_request(
|
||||
@router.get('/{request_id}')
|
||||
async def get_guest_request(
|
||||
request_id: int,
|
||||
current_user: User = Depends(authorize_roles('admin', 'staff', 'housekeeping')),
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get a single guest request"""
|
||||
@@ -397,10 +402,17 @@ async def get_guest_request(
|
||||
|
||||
# Check permissions
|
||||
role = db.query(Role).filter(Role.id == current_user.role_id).first()
|
||||
is_housekeeping = role and role.name == 'housekeeping'
|
||||
role_name = role.name if role else 'customer'
|
||||
|
||||
if is_housekeeping and request.assigned_to and request.assigned_to != current_user.id:
|
||||
raise HTTPException(status_code=403, detail='You can only view requests assigned to you')
|
||||
# Customers can only view their own requests
|
||||
if role_name == 'customer':
|
||||
if request.user_id != current_user.id:
|
||||
raise HTTPException(status_code=403, detail='You can only view your own requests')
|
||||
# Housekeeping can only view requests assigned to them or unassigned
|
||||
elif role_name == 'housekeeping':
|
||||
if request.assigned_to and request.assigned_to != current_user.id:
|
||||
raise HTTPException(status_code=403, detail='You can only view requests assigned to you')
|
||||
# Admin and staff can view all requests (no additional check needed)
|
||||
|
||||
return {
|
||||
'status': 'success',
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -39,6 +39,9 @@ async def create_api_key(
|
||||
):
|
||||
"""Create a new API key."""
|
||||
try:
|
||||
from sqlalchemy.exc import ProgrammingError
|
||||
import pymysql
|
||||
|
||||
expires_at = None
|
||||
if key_data.expires_at:
|
||||
expires_at = datetime.fromisoformat(key_data.expires_at.replace('Z', '+00:00'))
|
||||
@@ -67,6 +70,17 @@ async def create_api_key(
|
||||
},
|
||||
message='API key created successfully. Save this key securely - it will not be shown again.'
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except (ProgrammingError, pymysql.err.ProgrammingError) as e:
|
||||
error_str = str(e).lower()
|
||||
if "doesn't exist" in error_str or "does not exist" in error_str or "table" in error_str and "not found" in error_str:
|
||||
logger.warning(f'API keys table does not exist: {str(e)}')
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail='API keys table not found. Please run database migrations to create the table.'
|
||||
)
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f'Error creating API key: {str(e)}', exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
@@ -78,6 +92,9 @@ async def get_api_keys(
|
||||
):
|
||||
"""Get all API keys."""
|
||||
try:
|
||||
from sqlalchemy.exc import ProgrammingError
|
||||
import pymysql
|
||||
|
||||
api_keys = db.query(APIKey).order_by(APIKey.created_at.desc()).all()
|
||||
|
||||
return success_response(data={
|
||||
@@ -93,6 +110,12 @@ async def get_api_keys(
|
||||
'created_at': k.created_at.isoformat() if k.created_at else None
|
||||
} for k in api_keys]
|
||||
})
|
||||
except (ProgrammingError, pymysql.err.ProgrammingError) as e:
|
||||
error_str = str(e).lower()
|
||||
if "doesn't exist" in error_str or "does not exist" in error_str or "table" in error_str and "not found" in error_str:
|
||||
logger.warning(f'API keys table does not exist: {str(e)}')
|
||||
return success_response(data={'api_keys': []}, message='API keys table not found. Please run database migrations.')
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f'Error getting API keys: {str(e)}', exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@@ -65,18 +65,30 @@ async def get_webhooks(
|
||||
):
|
||||
"""Get all webhooks."""
|
||||
try:
|
||||
webhooks = db.query(Webhook).order_by(Webhook.created_at.desc()).all()
|
||||
from sqlalchemy.orm import noload
|
||||
# Explicitly prevent relationship loading which might be causing the SQL error
|
||||
webhooks = db.query(Webhook).options(noload(Webhook.creator)).order_by(Webhook.created_at.desc()).all()
|
||||
|
||||
return success_response(data={
|
||||
'webhooks': [{
|
||||
'id': w.id,
|
||||
'name': w.name,
|
||||
'url': w.url,
|
||||
'events': w.events,
|
||||
'status': w.status.value,
|
||||
'created_at': w.created_at.isoformat() if w.created_at else None
|
||||
} for w in webhooks]
|
||||
})
|
||||
result = []
|
||||
for w in webhooks:
|
||||
try:
|
||||
result.append({
|
||||
'id': w.id,
|
||||
'name': w.name,
|
||||
'url': w.url,
|
||||
'events': w.events if w.events else [],
|
||||
'status': w.status.value if w.status else 'inactive',
|
||||
'created_at': w.created_at.isoformat() if w.created_at else None,
|
||||
'updated_at': w.updated_at.isoformat() if w.updated_at else None,
|
||||
'description': w.description,
|
||||
'retry_count': w.retry_count,
|
||||
'timeout_seconds': w.timeout_seconds
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f'Error serializing webhook {w.id}: {str(e)}', exc_info=True)
|
||||
continue
|
||||
|
||||
return success_response(data={'webhooks': result})
|
||||
except Exception as e:
|
||||
logger.error(f'Error getting webhooks: {str(e)}', exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@@ -105,6 +105,10 @@ from ..notifications.models.email_campaign import Campaign, CampaignStatus, Camp
|
||||
from ..security.models.security_event import SecurityEvent, SecurityEventType, SecurityEventSeverity, IPWhitelist, IPBlacklist, OAuthProvider, OAuthToken
|
||||
from ..security.models.gdpr_compliance import DataSubjectRequest, DataSubjectRequestType, DataSubjectRequestStatus, DataRetentionPolicy, ConsentRecord
|
||||
|
||||
# Integration models
|
||||
from ..integrations.models.api_key import APIKey
|
||||
from ..integrations.models.webhook import Webhook, WebhookDelivery, WebhookEventType, WebhookStatus, WebhookDeliveryStatus
|
||||
|
||||
__all__ = [
|
||||
# Auth
|
||||
'Role', 'User', 'RefreshToken', 'PasswordResetToken',
|
||||
@@ -155,4 +159,6 @@ __all__ = [
|
||||
# Security
|
||||
'SecurityEvent', 'SecurityEventType', 'SecurityEventSeverity', 'IPWhitelist', 'IPBlacklist', 'OAuthProvider', 'OAuthToken',
|
||||
'DataSubjectRequest', 'DataSubjectRequestType', 'DataSubjectRequestStatus', 'DataRetentionPolicy', 'ConsentRecord',
|
||||
# Integrations
|
||||
'APIKey', 'Webhook', 'WebhookDelivery', 'WebhookEventType', 'WebhookStatus', 'WebhookDeliveryStatus',
|
||||
]
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -184,7 +184,7 @@ def serialize_channel(channel: TeamChannel, current_user_id: int, unread_count:
|
||||
"id": m.id,
|
||||
"full_name": m.full_name,
|
||||
"email": m.email,
|
||||
"avatar_url": m.avatar_url,
|
||||
"avatar_url": m.avatar, # User model uses 'avatar' field
|
||||
"role": m.role.name if m.role else None
|
||||
}
|
||||
for m in channel.members
|
||||
@@ -202,7 +202,7 @@ def serialize_message(message: TeamMessage) -> dict:
|
||||
"sender": {
|
||||
"id": message.sender.id,
|
||||
"full_name": message.sender.full_name,
|
||||
"avatar_url": message.sender.avatar_url,
|
||||
"avatar_url": message.sender.avatar, # User model uses 'avatar' field
|
||||
"role": message.sender.role.name if message.sender.role else None
|
||||
} if message.sender else None,
|
||||
"content": message.content if not message.is_deleted else "[Message deleted]",
|
||||
@@ -629,7 +629,7 @@ async def send_direct_message(
|
||||
"sender": {
|
||||
"id": current_user.id,
|
||||
"full_name": current_user.full_name,
|
||||
"avatar_url": current_user.avatar_url
|
||||
"avatar_url": current_user.avatar # User model uses 'avatar' field
|
||||
},
|
||||
"message": serialized
|
||||
}
|
||||
@@ -647,20 +647,55 @@ async def get_team_users(
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get all team users (admin, staff, housekeeping) for messaging."""
|
||||
# Get role IDs for team roles to ensure proper filtering
|
||||
team_roles = db.query(Role).filter(
|
||||
Role.name.in_(['admin', 'staff', 'housekeeping'])
|
||||
).all()
|
||||
team_role_ids = [r.id for r in team_roles]
|
||||
|
||||
if not team_role_ids:
|
||||
logger.error('No team roles found in database')
|
||||
return {"success": True, "data": []}
|
||||
|
||||
# Build query with proper role filtering
|
||||
query = db.query(User).options(
|
||||
joinedload(User.role),
|
||||
joinedload(User.presence)
|
||||
).join(Role).filter(
|
||||
Role.name.in_(['admin', 'staff', 'housekeeping']),
|
||||
).filter(
|
||||
User.role_id.in_(team_role_ids),
|
||||
User.is_active == True,
|
||||
User.id != current_user.id
|
||||
)
|
||||
|
||||
if role:
|
||||
query = query.filter(Role.name == role)
|
||||
role_obj = db.query(Role).filter(Role.name == role).first()
|
||||
if role_obj:
|
||||
query = query.filter(User.role_id == role_obj.id)
|
||||
|
||||
users = query.order_by(User.full_name).all()
|
||||
|
||||
# Debug: Check total count of team users (including inactive) for troubleshooting
|
||||
if not users:
|
||||
# Check if there are any team users at all (including inactive)
|
||||
all_team_users = db.query(User).filter(
|
||||
User.role_id.in_(team_role_ids),
|
||||
User.id != current_user.id
|
||||
).count()
|
||||
|
||||
active_team_users = db.query(User).filter(
|
||||
User.role_id.in_(team_role_ids),
|
||||
User.is_active == True,
|
||||
User.id != current_user.id
|
||||
).count()
|
||||
|
||||
logger.warning(
|
||||
f'No active team users found for user {current_user.id} ({current_user.email}). '
|
||||
f'Total team users (including inactive): {all_team_users}, '
|
||||
f'Active team users: {active_team_users}. '
|
||||
f'Query filters: role={role}, is_active=True, excluded_user_id={current_user.id}, '
|
||||
f'team_role_ids={team_role_ids}'
|
||||
)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"data": [
|
||||
@@ -668,10 +703,10 @@ async def get_team_users(
|
||||
"id": u.id,
|
||||
"full_name": u.full_name,
|
||||
"email": u.email,
|
||||
"avatar_url": u.avatar_url,
|
||||
"avatar_url": u.avatar, # User model uses 'avatar' field
|
||||
"role": u.role.name if u.role else None,
|
||||
"status": u.presence.status if u.presence else 'offline',
|
||||
"last_seen": u.presence.last_seen_at.isoformat() if u.presence else None
|
||||
"last_seen": u.presence.last_seen_at.isoformat() if u.presence and u.presence.last_seen_at else None
|
||||
}
|
||||
for u in users
|
||||
]
|
||||
|
||||
Binary file not shown.
@@ -26,6 +26,8 @@ async def verify_step_up(
|
||||
):
|
||||
"""Verify step-up authentication (MFA token or password re-entry)."""
|
||||
try:
|
||||
from ..models.accountant_session import AccountantSession
|
||||
|
||||
mfa_token = step_up_data.get('mfa_token')
|
||||
password = step_up_data.get('password')
|
||||
session_token = step_up_data.get('session_token')
|
||||
@@ -34,8 +36,18 @@ async def verify_step_up(
|
||||
# Try to get from header or cookie
|
||||
session_token = request.headers.get('X-Session-Token') or request.cookies.get('session_token')
|
||||
|
||||
# If still no session token, try to find the most recent active session for this user
|
||||
if not session_token:
|
||||
raise HTTPException(status_code=400, detail='Session token is required')
|
||||
active_session = db.query(AccountantSession).filter(
|
||||
AccountantSession.user_id == current_user.id,
|
||||
AccountantSession.is_active == True,
|
||||
AccountantSession.expires_at > datetime.utcnow()
|
||||
).order_by(AccountantSession.last_activity.desc()).first()
|
||||
|
||||
if active_session:
|
||||
session_token = active_session.session_token
|
||||
else:
|
||||
raise HTTPException(status_code=400, detail='No active session found. Please log in again.')
|
||||
|
||||
# Verify MFA if token provided
|
||||
if mfa_token:
|
||||
|
||||
Binary file not shown.
@@ -126,8 +126,18 @@ class AccountantSecurityService:
|
||||
Check if step-up authentication is required.
|
||||
Returns (requires_step_up: bool, reason: str | None)
|
||||
"""
|
||||
# If no session token provided, try to find the most recent active session for this user
|
||||
if not session_token:
|
||||
return True, "Step-up authentication required for this action"
|
||||
active_session = db.query(AccountantSession).filter(
|
||||
AccountantSession.user_id == user_id,
|
||||
AccountantSession.is_active == True,
|
||||
AccountantSession.expires_at > datetime.utcnow()
|
||||
).order_by(AccountantSession.last_activity.desc()).first()
|
||||
|
||||
if active_session:
|
||||
session_token = active_session.session_token
|
||||
else:
|
||||
return True, "Step-up authentication required for this action"
|
||||
|
||||
session = AccountantSecurityService.validate_session(db, session_token, update_activity=False)
|
||||
if not session:
|
||||
@@ -167,6 +177,8 @@ class AccountantSecurityService:
|
||||
minutes=AccountantSecurityService.STEP_UP_VALIDITY_MINUTES
|
||||
)
|
||||
|
||||
# Use flush to ensure changes are visible in the same transaction
|
||||
# The route handler will commit
|
||||
db.flush()
|
||||
return True
|
||||
|
||||
|
||||
Binary file not shown.
@@ -21,8 +21,9 @@ async def get_favorites(current_user: User=Depends(get_current_user), db: Sessio
|
||||
role = db.query(Role).filter(Role.id == current_user.role_id).first()
|
||||
role_name = role.name if role else 'customer'
|
||||
|
||||
if role_name in ['admin', 'staff', 'accountant']:
|
||||
raise HTTPException(status_code=403, detail='Admin, staff, and accountant users cannot have favorites')
|
||||
# Only customers can have favorites
|
||||
if role_name != 'customer':
|
||||
raise HTTPException(status_code=403, detail='Only customers can have favorites')
|
||||
try:
|
||||
favorites = db.query(Favorite).filter(Favorite.user_id == current_user.id).order_by(Favorite.created_at.desc()).all()
|
||||
result = []
|
||||
@@ -50,8 +51,9 @@ async def add_favorite(room_id: int, current_user: User=Depends(get_current_user
|
||||
role = db.query(Role).filter(Role.id == current_user.role_id).first()
|
||||
role_name = role.name if role else 'customer'
|
||||
|
||||
if role_name in ['admin', 'staff', 'accountant']:
|
||||
raise HTTPException(status_code=403, detail='Admin, staff, and accountant users cannot add favorites')
|
||||
# Only customers can add favorites
|
||||
if role_name != 'customer':
|
||||
raise HTTPException(status_code=403, detail='Only customers can add favorites')
|
||||
try:
|
||||
room = db.query(Room).filter(Room.id == room_id).first()
|
||||
if not room:
|
||||
@@ -80,8 +82,9 @@ async def remove_favorite(room_id: int, current_user: User=Depends(get_current_u
|
||||
role = db.query(Role).filter(Role.id == current_user.role_id).first()
|
||||
role_name = role.name if role else 'customer'
|
||||
|
||||
if role_name in ['admin', 'staff', 'accountant']:
|
||||
raise HTTPException(status_code=403, detail='Admin, staff, and accountant users cannot remove favorites')
|
||||
# Only customers can remove favorites
|
||||
if role_name != 'customer':
|
||||
raise HTTPException(status_code=403, detail='Only customers can remove favorites')
|
||||
try:
|
||||
favorite = db.query(Favorite).filter(Favorite.user_id == current_user.id, Favorite.room_id == room_id).first()
|
||||
if not favorite:
|
||||
@@ -105,7 +108,8 @@ async def check_favorite(room_id: int, current_user: User=Depends(get_current_us
|
||||
role = db.query(Role).filter(Role.id == current_user.role_id).first()
|
||||
role_name = role.name if role else 'customer'
|
||||
|
||||
if role_name in ['admin', 'staff', 'accountant']:
|
||||
# Only customers can have favorites
|
||||
if role_name != 'customer':
|
||||
return {'status': 'success', 'data': {'isFavorited': False}}
|
||||
try:
|
||||
favorite = db.query(Favorite).filter(Favorite.user_id == current_user.id, Favorite.room_id == room_id).first()
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user