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:
2026-01-24 18:44:49 +01:00
parent 7e39bb0564
commit 53e05dd497
18 changed files with 2792 additions and 6 deletions

View File

@@ -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
# ============================================================================