mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-26 06:16:09 +00:00
fix(cloud): preserve authenticated account context
This commit is contained in:
@@ -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]})
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user