fix(cloud): launch newly registered accounts through Space (#2499)

* fix(cloud): launch new accounts through Space

* style: format Cloud entry URL

* fix(cloud): wait for launch workspace projection

---------

Co-authored-by: dadachann <185672915+dadachann@users.noreply.github.com>
This commit is contained in:
Hyu
2026-09-02 21:41:35 +08:00
committed by GitHub
parent 7b7d3f04e8
commit 018dd7a363
6 changed files with 143 additions and 9 deletions
@@ -9,6 +9,7 @@ from .. import group
from .....entity.errors import account as account_errors
from ...context import RequestContext
from .....cloud.launch import SpaceLaunchError
from .....workspace.errors import WorkspaceNotFoundError
from ...service.user import ControlPlaneDirectoryRequiredError, PublicRegistrationClosedError
@@ -143,6 +144,13 @@ class UserRouterGroup(group.RouterGroup):
try:
redirect_uri = self._validate_space_redirect_uri(redirect_uri, bind=False)
launch_workspace_uuid = quart.request.args.get('launch_workspace_uuid')
cloud_entry = quart.request.args.get('cloud_entry') == '1'
if (
cloud_entry
and not launch_workspace_uuid
and getattr(getattr(self.ap, 'deployment', None), 'mode', 'oss') == 'cloud'
):
return self.success(data={'authorize_url': self.ap.space_service.get_cloud_entry_url()})
if launch_workspace_uuid:
if not getattr(getattr(self.ap, 'deployment', None), 'multi_workspace_enabled', False):
return self.fail(1, 'Space launch requires Cloud mode')
@@ -429,13 +437,33 @@ class UserRouterGroup(group.RouterGroup):
)
account = await self.ap.user_service.get_user_by_uuid(launch['account_uuid'])
projection_service = self.ap.directory_projection_service
access = None
# A first Cloud launch creates the personal Workspace immediately
# before redirecting here. Pull a bounded number of signed event
# pages until both the Account and its target Workspace membership
# are visible instead of rejecting during the background-sync window.
for attempt in range(4):
if account is not None:
self.ap.user_service._require_active_account(account)
try:
access = await self.ap.workspace_collaboration_service.resolve_account_workspace(
account.uuid,
launch['workspace_uuid'],
)
break
except WorkspaceNotFoundError:
if projection_service is None or attempt == 3:
raise
elif projection_service is None or attempt == 3:
break
await projection_service.sync_once()
account = await self.ap.user_service.get_user_by_uuid(launch['account_uuid'])
if account is None:
raise SpaceLaunchError('Launch Account is not projected into Core')
self.ap.user_service._require_active_account(account)
access = await self.ap.workspace_collaboration_service.resolve_account_workspace(
account.uuid,
launch['workspace_uuid'],
)
if access is None: # pragma: no cover - bounded loop resolves or raises.
raise SpaceLaunchError('Launch Workspace is not projected into Core')
token = await self.ap.user_service.generate_jwt_token(account)
return self.success(
data={
@@ -124,6 +124,11 @@ class SpaceService:
params['state'] = state
return f'{authorize_url}?{urlencode(params)}'
def get_cloud_entry_url(self) -> str:
"""Return the Space-owned Cloud selector for a Cloud Account login."""
return f'{self._get_space_config()["url"].rstrip("/")}/cloud?environment=beta'
async def exchange_oauth_code(
self,
code: str,