fix(cloud): preserve authenticated account context

This commit is contained in:
dadachann
2026-07-25 02:01:30 +08:00
parent c860159446
commit 84440df47f
4 changed files with 24 additions and 13 deletions
+6 -3
View File
@@ -93,11 +93,11 @@ class RouterGroup(abc.ABC):
return self.http_status(401, -1, 'No valid user token provided') return self.http_status(401, -1, 'No valid user token provided')
try: try:
_, user_email = await self._authenticate_account(token) account, user_email = await self._authenticate_account(token)
# Account-token routes deliberately stop before Workspace # Account-token routes deliberately stop before Workspace
# selection. They may bootstrap a selector, but cannot # selection. They may bootstrap a selector, but cannot
# receive RequestContext or enforce Workspace permissions. # receive RequestContext or enforce Workspace permissions.
self._inject_handler_context(f, kwargs, user_email, None) self._inject_handler_context(f, kwargs, user_email, None, account)
except Exception as e: except Exception as e:
return self._auth_error_response(e) return self._auth_error_response(e)
@@ -357,10 +357,13 @@ class RouterGroup(abc.ABC):
kwargs: dict[str, typing.Any], kwargs: dict[str, typing.Any],
user_email: str | None, user_email: str | None,
request_context: RequestContext | None, request_context: RequestContext | None,
account: typing.Any = None,
) -> None: ) -> None:
parameters = handler.__code__.co_varnames parameters = inspect.signature(handler).parameters
if user_email is not None and 'user_email' in parameters: if user_email is not None and 'user_email' in parameters:
kwargs['user_email'] = user_email kwargs['user_email'] = user_email
if account is not None and 'account' in parameters:
kwargs['account'] = account
if request_context is not None: if request_context is not None:
if 'request_context' in parameters: if 'request_context' in parameters:
kwargs['request_context'] = request_context kwargs['request_context'] = request_context
@@ -85,8 +85,8 @@ class UserRouterGroup(group.RouterGroup):
return self.success(data={'token': token}) return self.success(data={'token': token})
@self.route('/check-token', methods=['GET'], auth_type=group.AuthType.ACCOUNT_TOKEN) @self.route('/check-token', methods=['GET'], auth_type=group.AuthType.ACCOUNT_TOKEN)
async def _(user_email: str) -> str: async def _(account) -> str:
token = await self.ap.user_service.generate_jwt_token(user_email) token = await self.ap.user_service.generate_jwt_token(account)
return self.success(data={'token': token}) return self.success(data={'token': token})
@@ -61,7 +61,7 @@ def _invitation_payload(invitation: WorkspaceInvitation) -> dict[str, typing.Any
class WorkspacesRouterGroup(group.RouterGroup): class WorkspacesRouterGroup(group.RouterGroup):
async def initialize(self) -> None: async def initialize(self) -> None:
@self.route('/bootstrap', methods=['GET'], auth_type=group.AuthType.ACCOUNT_TOKEN) @self.route('/bootstrap', methods=['GET'], auth_type=group.AuthType.ACCOUNT_TOKEN)
async def _(user_email: str) -> typing.Any: async def _(account) -> typing.Any:
"""List the active Workspaces available to an authenticated Account. """List the active Workspaces available to an authenticated Account.
This account-only endpoint intentionally runs before Workspace This account-only endpoint intentionally runs before Workspace
@@ -69,9 +69,6 @@ class WorkspacesRouterGroup(group.RouterGroup):
choose a default Workspace for a multi-membership Account. choose a default Workspace for a multi-membership Account.
""" """
account = await self.ap.user_service.get_user_by_email(user_email)
if account is None:
return self.http_status(401, 'invalid_authentication', 'Account not found')
accesses = await self.ap.workspace_collaboration_service.list_account_workspaces(account.uuid) accesses = await self.ap.workspace_collaboration_service.list_account_workspaces(account.uuid)
return self.success( return self.success(
data={ data={
@@ -88,10 +85,7 @@ class WorkspacesRouterGroup(group.RouterGroup):
) )
@self.route('', methods=['GET'], auth_type=group.AuthType.ACCOUNT_TOKEN) @self.route('', methods=['GET'], auth_type=group.AuthType.ACCOUNT_TOKEN)
async def _(user_email: str) -> typing.Any: async def _(account) -> typing.Any:
account = await self.ap.user_service.get_user_by_email(user_email)
if account is None:
return self.http_status(401, 'invalid_authentication', 'Account not found')
accesses = await self.ap.workspace_collaboration_service.list_account_workspaces(account.uuid) accesses = await self.ap.workspace_collaboration_service.list_account_workspaces(account.uuid)
return self.success(data={'workspaces': [_workspace_payload(access.workspace) for access in accesses]}) return self.success(data={'workspaces': [_workspace_payload(access.workspace) for access in accesses]})
+14
View File
@@ -3,6 +3,7 @@ from __future__ import annotations
import json import json
import logging import logging
from types import SimpleNamespace from types import SimpleNamespace
from unittest.mock import AsyncMock
import pytest import pytest
import sqlalchemy import sqlalchemy
@@ -99,6 +100,19 @@ def _auth(token: str, workspace_uuid: str | None = None) -> dict[str, str]:
return headers return headers
async def test_account_bootstrap_uses_the_account_resolved_from_the_token(workspace_api):
application, client, _, owner_token = workspace_api
account = await application.user_service.get_authenticated_account(owner_token)
application.user_service.get_authenticated_account = AsyncMock(return_value=account)
application.user_service.get_user_by_email = AsyncMock(return_value=None)
response = await client.get('/api/v1/workspaces/bootstrap', headers=_auth(owner_token))
assert response.status_code == 200
application.user_service.get_user_by_email.assert_not_awaited()
async def test_fresh_sqlite_login_returns_current_workspace_and_user_info(workspace_api): async def test_fresh_sqlite_login_returns_current_workspace_and_user_info(workspace_api):
_, client, _, owner_token = workspace_api _, client, _, owner_token = workspace_api