mirror of
https://github.com/aljazceru/enclava.git
synced 2025-12-17 23:44:24 +01:00
mega changes
This commit is contained in:
@@ -4,75 +4,119 @@ User model
|
||||
from datetime import datetime
|
||||
from typing import Optional, List
|
||||
from enum import Enum
|
||||
from sqlalchemy import Column, Integer, String, DateTime, Boolean, Text, JSON
|
||||
from sqlalchemy import (
|
||||
Column,
|
||||
Integer,
|
||||
String,
|
||||
DateTime,
|
||||
Boolean,
|
||||
Text,
|
||||
JSON,
|
||||
ForeignKey,
|
||||
Numeric,
|
||||
)
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy import inspect as sa_inspect
|
||||
from app.db.database import Base
|
||||
|
||||
|
||||
class UserRole(str, Enum):
|
||||
"""User role enumeration"""
|
||||
USER = "user"
|
||||
ADMIN = "admin"
|
||||
SUPER_ADMIN = "super_admin"
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
class User(Base):
|
||||
"""User model for authentication and user management"""
|
||||
|
||||
|
||||
__tablename__ = "users"
|
||||
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
email = Column(String, unique=True, index=True, nullable=False)
|
||||
username = Column(String, unique=True, index=True, nullable=False)
|
||||
hashed_password = Column(String, nullable=False)
|
||||
full_name = Column(String, nullable=True)
|
||||
|
||||
# User status and permissions
|
||||
|
||||
# Account status
|
||||
is_active = Column(Boolean, default=True)
|
||||
is_superuser = Column(Boolean, default=False)
|
||||
is_verified = Column(Boolean, default=False)
|
||||
|
||||
# Role-based access control
|
||||
role = Column(String, default=UserRole.USER.value) # user, admin, super_admin
|
||||
permissions = Column(JSON, default=dict) # Custom permissions
|
||||
|
||||
is_superuser = Column(Boolean, default=False) # Legacy field for compatibility
|
||||
|
||||
# Role-based access control (using new Role model)
|
||||
role_id = Column(Integer, ForeignKey("roles.id"), nullable=True)
|
||||
custom_permissions = Column(JSON, default=dict) # Custom permissions override
|
||||
|
||||
# Account management
|
||||
account_locked = Column(Boolean, default=False)
|
||||
account_locked_until = Column(DateTime, nullable=True)
|
||||
failed_login_attempts = Column(Integer, default=0)
|
||||
last_failed_login = Column(DateTime, nullable=True)
|
||||
force_password_change = Column(Boolean, default=False)
|
||||
|
||||
# Profile information
|
||||
avatar_url = Column(String, nullable=True)
|
||||
bio = Column(Text, nullable=True)
|
||||
company = Column(String, nullable=True)
|
||||
website = Column(String, nullable=True)
|
||||
|
||||
|
||||
# Timestamps
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
last_login = Column(DateTime, nullable=True)
|
||||
|
||||
|
||||
# Settings
|
||||
preferences = Column(JSON, default=dict)
|
||||
notification_settings = Column(JSON, default=dict)
|
||||
|
||||
|
||||
# Relationships
|
||||
api_keys = relationship("APIKey", back_populates="user", cascade="all, delete-orphan")
|
||||
usage_tracking = relationship("UsageTracking", back_populates="user", cascade="all, delete-orphan")
|
||||
budgets = relationship("Budget", back_populates="user", cascade="all, delete-orphan")
|
||||
audit_logs = relationship("AuditLog", back_populates="user", cascade="all, delete-orphan")
|
||||
role = relationship("Role", back_populates="users")
|
||||
api_keys = relationship(
|
||||
"APIKey", back_populates="user", cascade="all, delete-orphan"
|
||||
)
|
||||
usage_tracking = relationship(
|
||||
"UsageTracking", back_populates="user", cascade="all, delete-orphan"
|
||||
)
|
||||
budgets = relationship(
|
||||
"Budget", back_populates="user", cascade="all, delete-orphan"
|
||||
)
|
||||
audit_logs = relationship(
|
||||
"AuditLog", back_populates="user", cascade="all, delete-orphan"
|
||||
)
|
||||
installed_plugins = relationship("Plugin", back_populates="installed_by_user")
|
||||
|
||||
created_tools = relationship(
|
||||
"Tool", back_populates="created_by", cascade="all, delete-orphan"
|
||||
)
|
||||
tool_executions = relationship(
|
||||
"ToolExecution", back_populates="executed_by", cascade="all, delete-orphan"
|
||||
)
|
||||
notifications = relationship(
|
||||
"Notification", back_populates="user", cascade="all, delete-orphan"
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<User(id={self.id}, email='{self.email}', username='{self.username}')>"
|
||||
|
||||
|
||||
def to_dict(self):
|
||||
"""Convert user to dictionary for API responses"""
|
||||
# Check if role relationship is loaded to avoid lazy loading in async context
|
||||
inspector = sa_inspect(self)
|
||||
role_loaded = "role" not in inspector.unloaded
|
||||
|
||||
return {
|
||||
"id": self.id,
|
||||
"email": self.email,
|
||||
"username": self.username,
|
||||
"full_name": self.full_name,
|
||||
"is_active": self.is_active,
|
||||
"is_superuser": self.is_superuser,
|
||||
"is_verified": self.is_verified,
|
||||
"role": self.role,
|
||||
"permissions": self.permissions,
|
||||
"is_superuser": self.is_superuser,
|
||||
"role_id": self.role_id,
|
||||
"role": self.role.to_dict() if role_loaded and self.role else None,
|
||||
"custom_permissions": self.custom_permissions,
|
||||
"account_locked": self.account_locked,
|
||||
"account_locked_until": self.account_locked_until.isoformat()
|
||||
if self.account_locked_until
|
||||
else None,
|
||||
"failed_login_attempts": self.failed_login_attempts,
|
||||
"last_failed_login": self.last_failed_login.isoformat()
|
||||
if self.last_failed_login
|
||||
else None,
|
||||
"force_password_change": self.force_password_change,
|
||||
"avatar_url": self.avatar_url,
|
||||
"bio": self.bio,
|
||||
"company": self.company,
|
||||
@@ -81,54 +125,157 @@ class User(Base):
|
||||
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
||||
"last_login": self.last_login.isoformat() if self.last_login else None,
|
||||
"preferences": self.preferences,
|
||||
"notification_settings": self.notification_settings
|
||||
"notification_settings": self.notification_settings,
|
||||
}
|
||||
|
||||
|
||||
def has_permission(self, permission: str) -> bool:
|
||||
"""Check if user has a specific permission"""
|
||||
"""Check if user has a specific permission using role hierarchy"""
|
||||
if self.is_superuser:
|
||||
return True
|
||||
|
||||
# Check role-based permissions
|
||||
role_permissions = {
|
||||
"user": ["read_own", "create_own", "update_own"],
|
||||
"admin": ["read_all", "create_all", "update_all", "delete_own"],
|
||||
"super_admin": ["read_all", "create_all", "update_all", "delete_all", "manage_users", "manage_modules"]
|
||||
}
|
||||
|
||||
if self.role in role_permissions and permission in role_permissions[self.role]:
|
||||
|
||||
# Check custom permissions first (override)
|
||||
if permission in self.custom_permissions.get("denied", []):
|
||||
return False
|
||||
if permission in self.custom_permissions.get("granted", []):
|
||||
return True
|
||||
|
||||
# Check custom permissions
|
||||
return permission in self.permissions
|
||||
|
||||
|
||||
# Check role permissions if user has a role assigned
|
||||
if self.role:
|
||||
return self.role.has_permission(permission)
|
||||
|
||||
return False
|
||||
|
||||
def can_access_module(self, module_name: str) -> bool:
|
||||
"""Check if user can access a specific module"""
|
||||
if self.is_superuser:
|
||||
return True
|
||||
|
||||
# Check module-specific permissions
|
||||
module_permissions = self.permissions.get("modules", {})
|
||||
return module_permissions.get(module_name, False)
|
||||
|
||||
|
||||
# Check custom permissions first
|
||||
module_permissions = self.custom_permissions.get("modules", {})
|
||||
if module_name in module_permissions:
|
||||
return module_permissions[module_name]
|
||||
|
||||
# Check role permissions
|
||||
if self.role:
|
||||
# For admin roles, allow all modules
|
||||
if self.role.level in ["admin", "super_admin"]:
|
||||
return True
|
||||
# For regular users, check module access
|
||||
elif self.role.level == "user":
|
||||
return True # Basic users can access all modules
|
||||
# For read-only users, limit access
|
||||
elif self.role.level == "read_only":
|
||||
return module_name in ["chatbot", "analytics"] # Only certain modules
|
||||
|
||||
return False
|
||||
|
||||
def update_last_login(self):
|
||||
"""Update the last login timestamp"""
|
||||
self.last_login = datetime.utcnow()
|
||||
|
||||
|
||||
def update_preferences(self, preferences: dict):
|
||||
"""Update user preferences"""
|
||||
if self.preferences is None:
|
||||
self.preferences = {}
|
||||
self.preferences.update(preferences)
|
||||
|
||||
|
||||
def update_notification_settings(self, settings: dict):
|
||||
"""Update notification settings"""
|
||||
if self.notification_settings is None:
|
||||
self.notification_settings = {}
|
||||
self.notification_settings.update(settings)
|
||||
|
||||
|
||||
def get_effective_permissions(self) -> dict:
|
||||
"""Get all effective permissions combining role and custom permissions"""
|
||||
permissions = {"granted": set(), "denied": set()}
|
||||
|
||||
# Start with role permissions
|
||||
if self.role:
|
||||
role_perms = self.role.permissions
|
||||
permissions["granted"].update(role_perms.get("granted", []))
|
||||
permissions["denied"].update(role_perms.get("denied", []))
|
||||
|
||||
# Apply custom permissions (override role permissions)
|
||||
permissions["granted"].update(self.custom_permissions.get("granted", []))
|
||||
permissions["denied"].update(self.custom_permissions.get("denied", []))
|
||||
|
||||
# Remove any denied permissions from granted
|
||||
permissions["granted"] -= permissions["denied"]
|
||||
|
||||
return {
|
||||
"granted": list(permissions["granted"]),
|
||||
"denied": list(permissions["denied"]),
|
||||
}
|
||||
|
||||
def can_create_api_key(self) -> bool:
|
||||
"""Check if user can create API keys based on role and limits"""
|
||||
if not self.is_active or self.account_locked:
|
||||
return False
|
||||
|
||||
# Check permission
|
||||
if not self.has_permission("create_api_key"):
|
||||
return False
|
||||
|
||||
# Check if user has reached their API key limit
|
||||
current_keys = [key for key in self.api_keys if key.is_active]
|
||||
max_keys = (
|
||||
self.role.permissions.get("limits", {}).get("max_api_keys", 5)
|
||||
if self.role
|
||||
else 5
|
||||
)
|
||||
|
||||
return len(current_keys) < max_keys
|
||||
|
||||
def can_create_tool(self) -> bool:
|
||||
"""Check if user can create custom tools"""
|
||||
return (
|
||||
self.is_active
|
||||
and not self.account_locked
|
||||
and self.has_permission("create_tool")
|
||||
)
|
||||
|
||||
def is_budget_exceeded(self) -> bool:
|
||||
"""Check if user has exceeded their budget limits"""
|
||||
if not self.budgets:
|
||||
return False
|
||||
|
||||
active_budget = next((b for b in self.budgets if b.is_active), None)
|
||||
if not active_budget:
|
||||
return False
|
||||
|
||||
return active_budget.current_usage > active_budget.limit
|
||||
|
||||
def lock_account(self, duration_hours: int = 24):
|
||||
"""Lock user account for specified duration"""
|
||||
from datetime import timedelta
|
||||
|
||||
self.account_locked = True
|
||||
self.account_locked_until = datetime.utcnow() + timedelta(hours=duration_hours)
|
||||
|
||||
def unlock_account(self):
|
||||
"""Unlock user account"""
|
||||
self.account_locked = False
|
||||
self.account_locked_until = None
|
||||
self.failed_login_attempts = 0
|
||||
|
||||
def record_failed_login(self):
|
||||
"""Record a failed login attempt"""
|
||||
self.failed_login_attempts += 1
|
||||
self.last_failed_login = datetime.utcnow()
|
||||
|
||||
# Lock account after 5 failed attempts
|
||||
if self.failed_login_attempts >= 5:
|
||||
self.lock_account(24) # Lock for 24 hours
|
||||
|
||||
def reset_failed_logins(self):
|
||||
"""Reset failed login counter"""
|
||||
self.failed_login_attempts = 0
|
||||
self.last_failed_login = None
|
||||
|
||||
@classmethod
|
||||
def create_default_admin(cls, email: str, username: str, password_hash: str) -> "User":
|
||||
def create_default_admin(
|
||||
cls, email: str, username: str, password_hash: str
|
||||
) -> "User":
|
||||
"""Create a default admin user"""
|
||||
return cls(
|
||||
email=email,
|
||||
@@ -136,24 +283,16 @@ class User(Base):
|
||||
hashed_password=password_hash,
|
||||
full_name="System Administrator",
|
||||
is_active=True,
|
||||
is_superuser=True,
|
||||
is_superuser=True, # Legacy compatibility
|
||||
is_verified=True,
|
||||
role="super_admin",
|
||||
permissions={
|
||||
"modules": {
|
||||
"cache": True,
|
||||
"analytics": True,
|
||||
"rag": True
|
||||
}
|
||||
},
|
||||
preferences={
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"timezone": "UTC"
|
||||
# Note: role_id will be set after role is created in init_db
|
||||
custom_permissions={
|
||||
"modules": {"cache": True, "analytics": True, "rag": True}
|
||||
},
|
||||
preferences={"theme": "dark", "language": "en", "timezone": "UTC"},
|
||||
notification_settings={
|
||||
"email_notifications": True,
|
||||
"security_alerts": True,
|
||||
"system_updates": True
|
||||
}
|
||||
)
|
||||
"system_updates": True,
|
||||
},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user