diff --git a/src/langbot/pkg/api/http/controller/group.py b/src/langbot/pkg/api/http/controller/group.py index 9933855db..33c7d0d06 100644 --- a/src/langbot/pkg/api/http/controller/group.py +++ b/src/langbot/pkg/api/http/controller/group.py @@ -93,11 +93,11 @@ class RouterGroup(abc.ABC): return self.http_status(401, -1, 'No valid user token provided') try: - _, user_email = await self._authenticate_account(token) + account, user_email = await self._authenticate_account(token) # Account-token routes deliberately stop before Workspace # selection. They may bootstrap a selector, but cannot # 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: return self._auth_error_response(e) @@ -357,10 +357,13 @@ class RouterGroup(abc.ABC): kwargs: dict[str, typing.Any], user_email: str | None, request_context: RequestContext | None, + account: typing.Any = None, ) -> None: - parameters = handler.__code__.co_varnames + parameters = inspect.signature(handler).parameters if user_email is not None and 'user_email' in parameters: 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' in parameters: kwargs['request_context'] = request_context diff --git a/src/langbot/pkg/api/http/controller/groups/user.py b/src/langbot/pkg/api/http/controller/groups/user.py index ce5c8c591..596aaf817 100644 --- a/src/langbot/pkg/api/http/controller/groups/user.py +++ b/src/langbot/pkg/api/http/controller/groups/user.py @@ -85,8 +85,8 @@ class UserRouterGroup(group.RouterGroup): return self.success(data={'token': token}) @self.route('/check-token', methods=['GET'], auth_type=group.AuthType.ACCOUNT_TOKEN) - async def _(user_email: str) -> str: - token = await self.ap.user_service.generate_jwt_token(user_email) + async def _(account) -> str: + token = await self.ap.user_service.generate_jwt_token(account) return self.success(data={'token': token}) diff --git a/src/langbot/pkg/api/http/controller/groups/workspaces.py b/src/langbot/pkg/api/http/controller/groups/workspaces.py index fa19b099d..290eb5aef 100644 --- a/src/langbot/pkg/api/http/controller/groups/workspaces.py +++ b/src/langbot/pkg/api/http/controller/groups/workspaces.py @@ -61,7 +61,7 @@ def _invitation_payload(invitation: WorkspaceInvitation) -> dict[str, typing.Any class WorkspacesRouterGroup(group.RouterGroup): async def initialize(self) -> None: @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. 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. """ - 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) return self.success( data={ @@ -88,10 +85,7 @@ class WorkspacesRouterGroup(group.RouterGroup): ) @self.route('', methods=['GET'], auth_type=group.AuthType.ACCOUNT_TOKEN) - async def _(user_email: str) -> 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') + async def _(account) -> typing.Any: 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]}) diff --git a/tests/integration/api/test_workspaces.py b/tests/integration/api/test_workspaces.py index 56127b0f7..404d283c4 100644 --- a/tests/integration/api/test_workspaces.py +++ b/tests/integration/api/test_workspaces.py @@ -3,6 +3,7 @@ from __future__ import annotations import json import logging from types import SimpleNamespace +from unittest.mock import AsyncMock import pytest import sqlalchemy @@ -99,6 +100,19 @@ def _auth(token: str, workspace_uuid: str | None = None) -> dict[str, str]: 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): _, client, _, owner_token = workspace_api