fix: platform admin authentication and UserContext completeness
Issues fixed: - Platform selection returned LoginResponse requiring user timestamps, but UserContext doesn't have created_at/updated_at. Created dedicated PlatformSelectResponse that returns only token and platform info. - UserContext was missing platform context fields (token_platform_id, token_platform_code). JWT token included them but they weren't extracted into UserContext, causing fallback warnings. - admin_menu_config.py accessed admin_platforms (SQLAlchemy relationship) on UserContext (Pydantic schema). Changed to use accessible_platform_ids. - Static file mount order in main.py caused 404 for locale files. More specific paths (/static/modules/X/locales) must be mounted before less specific paths (/static/modules/X). Changes: - models/schema/auth.py: Add PlatformSelectResponse, token_platform_id, token_platform_code, can_access_platform(), get_accessible_platform_ids() - admin_auth.py: Use PlatformSelectResponse for select-platform endpoint - admin_platform_service.py: Accept User | UserContext in validation - admin_menu_config.py: Use accessible_platform_ids instead of admin_platforms - main.py: Mount locales before static for correct path priority Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -431,14 +431,14 @@ async def get_rendered_admin_menu(
|
||||
else:
|
||||
# Platform admin: use platform-level config
|
||||
# Get the selected platform from the JWT token
|
||||
platform_id = getattr(current_user, "token_platform_id", None)
|
||||
platform_id = current_user.token_platform_id
|
||||
|
||||
# Fallback to first platform if no platform in token (shouldn't happen)
|
||||
if platform_id is None and current_user.admin_platforms:
|
||||
platform_id = current_user.admin_platforms[0].id
|
||||
# Fallback to first accessible platform if no platform in token (shouldn't happen)
|
||||
if platform_id is None and current_user.accessible_platform_ids:
|
||||
platform_id = current_user.accessible_platform_ids[0]
|
||||
logger.warning(
|
||||
f"[MENU_CONFIG] No platform_id in token for {current_user.email}, "
|
||||
f"falling back to first platform: {platform_id}"
|
||||
f"falling back to first accessible platform: {platform_id}"
|
||||
)
|
||||
|
||||
menu = menu_service.get_menu_for_rendering(
|
||||
|
||||
@@ -23,7 +23,7 @@ from app.modules.core.services.auth_service import auth_service
|
||||
from middleware.auth import AuthManager
|
||||
from app.modules.tenancy.models import Platform # noqa: API-007 - Admin needs to query platforms
|
||||
from models.schema.auth import UserContext
|
||||
from models.schema.auth import LoginResponse, LogoutResponse, UserLogin, UserResponse
|
||||
from models.schema.auth import LoginResponse, LogoutResponse, PlatformSelectResponse, UserLogin, UserResponse
|
||||
|
||||
admin_auth_router = APIRouter(prefix="/auth")
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -160,7 +160,7 @@ def get_accessible_platforms(
|
||||
}
|
||||
|
||||
|
||||
@admin_auth_router.post("/select-platform")
|
||||
@admin_auth_router.post("/select-platform", response_model=PlatformSelectResponse)
|
||||
def select_platform(
|
||||
platform_id: int,
|
||||
response: Response,
|
||||
@@ -177,7 +177,7 @@ def select_platform(
|
||||
platform_id: Platform ID to select
|
||||
|
||||
Returns:
|
||||
LoginResponse with new token containing platform context
|
||||
PlatformSelectResponse with new token and platform info
|
||||
"""
|
||||
if current_user.is_super_admin:
|
||||
raise InvalidCredentialsException(
|
||||
@@ -213,9 +213,10 @@ def select_platform(
|
||||
|
||||
logger.info(f"Admin {current_user.username} selected platform {platform.code}")
|
||||
|
||||
return LoginResponse(
|
||||
return PlatformSelectResponse(
|
||||
access_token=token_data["access_token"],
|
||||
token_type=token_data["token_type"],
|
||||
expires_in=token_data["expires_in"],
|
||||
user=current_user,
|
||||
platform_id=platform.id,
|
||||
platform_code=platform.code,
|
||||
)
|
||||
|
||||
@@ -23,6 +23,7 @@ from app.modules.tenancy.exceptions import (
|
||||
from app.modules.tenancy.models import AdminPlatform
|
||||
from app.modules.tenancy.models import Platform
|
||||
from app.modules.tenancy.models import User
|
||||
from models.schema.auth import UserContext
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -223,14 +224,14 @@ class AdminPlatformService:
|
||||
|
||||
def validate_admin_platform_access(
|
||||
self,
|
||||
user: User,
|
||||
user: User | UserContext,
|
||||
platform_id: int,
|
||||
) -> None:
|
||||
"""
|
||||
Validate that an admin has access to a platform.
|
||||
|
||||
Args:
|
||||
user: User object
|
||||
user: User model or UserContext schema (both have can_access_platform)
|
||||
platform_id: Platform ID to check
|
||||
|
||||
Raises:
|
||||
|
||||
Reference in New Issue
Block a user