feat: implement super admin and platform admin roles
Add multi-platform admin authorization system with: - AdminPlatform junction table for admin-platform assignments - is_super_admin flag on User model for global admin access - Platform selection flow for platform admins after login - JWT token updates to include platform context - New API endpoints for admin user management (super admin only) - Auth dependencies for super admin and platform access checks Includes comprehensive test coverage: - Unit tests for AdminPlatform model and User admin methods - Unit tests for AdminPlatformService operations - Integration tests for admin users API endpoints Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
168
app/api/deps.py
168
app/api/deps.py
@@ -203,6 +203,174 @@ def get_current_admin_api(
|
||||
return user
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# SUPER ADMIN AUTHENTICATION
|
||||
# ============================================================================
|
||||
|
||||
|
||||
def get_current_super_admin(
|
||||
request: Request,
|
||||
credentials: HTTPAuthorizationCredentials | None = Depends(security),
|
||||
admin_token: str | None = Cookie(None),
|
||||
db: Session = Depends(get_db),
|
||||
) -> User:
|
||||
"""
|
||||
Require super admin role.
|
||||
|
||||
Used for: Global settings, user management, platform creation/deletion,
|
||||
admin-platform assignment management.
|
||||
|
||||
Args:
|
||||
request: FastAPI request
|
||||
credentials: Optional Bearer token from header
|
||||
admin_token: Optional token from admin_token cookie
|
||||
db: Database session
|
||||
|
||||
Returns:
|
||||
User: Authenticated super admin user
|
||||
|
||||
Raises:
|
||||
InvalidTokenException: If no token or invalid token
|
||||
AdminRequiredException: If user is not admin or not super admin
|
||||
"""
|
||||
user = get_current_admin_from_cookie_or_header(request, credentials, admin_token, db)
|
||||
|
||||
if not user.is_super_admin:
|
||||
logger.warning(
|
||||
f"Platform admin {user.username} attempted super admin route: {request.url.path}"
|
||||
)
|
||||
raise AdminRequiredException("Super admin privileges required")
|
||||
|
||||
return user
|
||||
|
||||
|
||||
def get_current_super_admin_api(
|
||||
credentials: HTTPAuthorizationCredentials = Depends(security),
|
||||
db: Session = Depends(get_db),
|
||||
) -> User:
|
||||
"""
|
||||
Require super admin role (API header only).
|
||||
|
||||
Used for super admin API endpoints that should not accept cookies.
|
||||
|
||||
Args:
|
||||
credentials: Bearer token from Authorization header
|
||||
db: Database session
|
||||
|
||||
Returns:
|
||||
User: Authenticated super admin user
|
||||
|
||||
Raises:
|
||||
InvalidTokenException: If no token or invalid token
|
||||
AdminRequiredException: If user is not admin or not super admin
|
||||
"""
|
||||
user = get_current_admin_api(credentials, db)
|
||||
|
||||
if not user.is_super_admin:
|
||||
logger.warning(f"Platform admin {user.username} attempted super admin API")
|
||||
raise AdminRequiredException("Super admin privileges required")
|
||||
|
||||
return user
|
||||
|
||||
|
||||
def require_platform_access(platform_id: int):
|
||||
"""
|
||||
Dependency factory to require admin access to a specific platform.
|
||||
|
||||
Super admins can access all platforms.
|
||||
Platform admins can only access their assigned platforms.
|
||||
|
||||
Usage:
|
||||
@router.get("/platforms/{platform_id}/vendors")
|
||||
def list_vendors(
|
||||
platform_id: int,
|
||||
admin: User = Depends(require_platform_access(platform_id))
|
||||
):
|
||||
...
|
||||
"""
|
||||
|
||||
def _check_platform_access(
|
||||
request: Request,
|
||||
credentials: HTTPAuthorizationCredentials | None = Depends(security),
|
||||
admin_token: str | None = Cookie(None),
|
||||
db: Session = Depends(get_db),
|
||||
) -> User:
|
||||
user = get_current_admin_from_cookie_or_header(
|
||||
request, credentials, admin_token, db
|
||||
)
|
||||
|
||||
if not user.can_access_platform(platform_id):
|
||||
logger.warning(
|
||||
f"Admin {user.username} denied access to platform_id={platform_id}"
|
||||
)
|
||||
raise InsufficientPermissionsException(
|
||||
f"Access denied to platform {platform_id}"
|
||||
)
|
||||
|
||||
return user
|
||||
|
||||
return _check_platform_access
|
||||
|
||||
|
||||
def get_admin_with_platform_context(
|
||||
request: Request,
|
||||
credentials: HTTPAuthorizationCredentials | None = Depends(security),
|
||||
admin_token: str | None = Cookie(None),
|
||||
db: Session = Depends(get_db),
|
||||
) -> User:
|
||||
"""
|
||||
Get admin user and verify platform context from token.
|
||||
|
||||
For platform admins, extracts platform_id from JWT token and verifies access.
|
||||
Stores platform in request.state.admin_platform for endpoint use.
|
||||
|
||||
Super admins bypass platform context check (they can access all platforms).
|
||||
|
||||
Args:
|
||||
request: FastAPI request
|
||||
credentials: Optional Bearer token from header
|
||||
admin_token: Optional token from admin_token cookie
|
||||
db: Database session
|
||||
|
||||
Returns:
|
||||
User: Authenticated admin with platform context
|
||||
|
||||
Raises:
|
||||
InvalidTokenException: If platform admin token missing platform info
|
||||
InsufficientPermissionsException: If platform access revoked
|
||||
"""
|
||||
from models.database.platform import Platform
|
||||
|
||||
user = get_current_admin_from_cookie_or_header(request, credentials, admin_token, db)
|
||||
|
||||
# Super admins bypass platform context
|
||||
if user.is_super_admin:
|
||||
return user
|
||||
|
||||
# Platform admins need platform_id in token
|
||||
if not hasattr(user, "token_platform_id"):
|
||||
raise InvalidTokenException(
|
||||
"Token missing platform information. Please select a platform."
|
||||
)
|
||||
|
||||
platform_id = user.token_platform_id
|
||||
|
||||
# Verify admin still has access to this platform
|
||||
if not user.can_access_platform(platform_id):
|
||||
logger.warning(
|
||||
f"Admin {user.username} lost access to platform_id={platform_id}"
|
||||
)
|
||||
raise InsufficientPermissionsException(
|
||||
"Access to this platform has been revoked. Please login again."
|
||||
)
|
||||
|
||||
# Load platform and store in request state
|
||||
platform = db.query(Platform).filter(Platform.id == platform_id).first()
|
||||
request.state.admin_platform = platform
|
||||
|
||||
return user
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# VENDOR AUTHENTICATION
|
||||
# ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user