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

@@ -140,6 +140,8 @@ class AuthManager:
vendor_id: int | None = None,
vendor_code: str | None = None,
vendor_role: str | None = None,
platform_id: int | None = None,
platform_code: str | None = None,
) -> dict[str, Any]:
"""Create a JWT access token for an authenticated user.
@@ -151,6 +153,8 @@ class AuthManager:
vendor_id (int, optional): Vendor ID if logging into vendor context
vendor_code (str, optional): Vendor code if logging into vendor context
vendor_role (str, optional): User's role in this vendor (owner, manager, etc.)
platform_id (int, optional): Platform ID for platform admin context
platform_code (str, optional): Platform code for platform admin context
Returns:
Dict[str, Any]: Dictionary containing:
@@ -172,6 +176,21 @@ class AuthManager:
"iat": datetime.now(UTC), # Issued at time (JWT standard claim)
}
# Include admin-specific information for admin users
if user.is_admin:
payload["is_super_admin"] = user.is_super_admin
# For platform admins, include their accessible platform IDs
if not user.is_super_admin:
accessible = user.get_accessible_platform_ids()
if accessible is not None:
payload["accessible_platforms"] = accessible
# Include platform context for platform admins
if platform_id is not None:
payload["platform_id"] = platform_id
if platform_code is not None:
payload["platform_code"] = platform_code
# Include vendor information in token if provided (vendor-specific login)
if vendor_id is not None:
payload["vendor_id"] = vendor_id
@@ -242,6 +261,18 @@ class AuthManager:
), # Default to "user" role if not specified
}
# Include admin-specific information if present
if "is_super_admin" in payload:
user_data["is_super_admin"] = payload["is_super_admin"]
if "accessible_platforms" in payload:
user_data["accessible_platforms"] = payload["accessible_platforms"]
# Include platform context for platform admins
if "platform_id" in payload:
user_data["platform_id"] = payload["platform_id"]
if "platform_code" in payload:
user_data["platform_code"] = payload["platform_code"]
# Include vendor information if present in token
if "vendor_id" in payload:
user_data["vendor_id"] = payload["vendor_id"]
@@ -302,8 +333,20 @@ class AuthManager:
if not user.is_active:
raise UserNotActiveException()
# Attach vendor information to user object if present in token
# Attach admin-specific information to user object if present in token
# These become dynamic attributes on the user object for this request
if "is_super_admin" in user_data:
user.token_is_super_admin = user_data["is_super_admin"]
if "accessible_platforms" in user_data:
user.token_accessible_platforms = user_data["accessible_platforms"]
# Attach platform context to user object if present in token
if "platform_id" in user_data:
user.token_platform_id = user_data["platform_id"]
if "platform_code" in user_data:
user.token_platform_code = user_data["platform_code"]
# Attach vendor information to user object if present in token
if "vendor_id" in user_data:
user.token_vendor_id = user_data["vendor_id"]
if "vendor_code" in user_data:
@@ -443,12 +486,14 @@ class AuthManager:
hashed_password = self.hash_password("admin123")
# Create new admin user with default credentials
# Default admin is a super admin with access to all platforms
admin_user = User(
email="admin@example.com",
username="admin",
hashed_password=hashed_password,
role="admin",
is_active=True,
is_super_admin=True,
)
# Save to database