mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-09-05 09:07:13 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2dcf373801 |
@@ -144,13 +144,6 @@ class UserRouterGroup(group.RouterGroup):
|
|||||||
try:
|
try:
|
||||||
redirect_uri = self._validate_space_redirect_uri(redirect_uri, bind=False)
|
redirect_uri = self._validate_space_redirect_uri(redirect_uri, bind=False)
|
||||||
launch_workspace_uuid = quart.request.args.get('launch_workspace_uuid')
|
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 launch_workspace_uuid:
|
||||||
if not getattr(getattr(self.ap, 'deployment', None), 'multi_workspace_enabled', False):
|
if not getattr(getattr(self.ap, 'deployment', None), 'multi_workspace_enabled', False):
|
||||||
return self.fail(1, 'Space launch requires Cloud mode')
|
return self.fail(1, 'Space launch requires Cloud mode')
|
||||||
|
|||||||
@@ -124,11 +124,6 @@ class SpaceService:
|
|||||||
params['state'] = state
|
params['state'] = state
|
||||||
return f'{authorize_url}?{urlencode(params)}'
|
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(
|
async def exchange_oauth_code(
|
||||||
self,
|
self,
|
||||||
code: str,
|
code: str,
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ async def space_oauth_api():
|
|||||||
application.space_service.get_oauth_authorize_url = Mock(
|
application.space_service.get_oauth_authorize_url = Mock(
|
||||||
side_effect=lambda redirect_uri, state: f'https://space.example/authorize?state={state}'
|
side_effect=lambda redirect_uri, state: f'https://space.example/authorize?state={state}'
|
||||||
)
|
)
|
||||||
application.space_service.get_cloud_entry_url = Mock(return_value='https://space.example/cloud?environment=beta')
|
|
||||||
application.space_service.exchange_oauth_code = AsyncMock(
|
application.space_service.exchange_oauth_code = AsyncMock(
|
||||||
return_value={
|
return_value={
|
||||||
'access_token': 'space-access-token',
|
'access_token': 'space-access-token',
|
||||||
@@ -129,7 +129,7 @@ async def test_cloud_launch_state_is_server_issued_and_workspace_bound(space_oau
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_cloud_login_entry_redirects_to_space_workspace_launcher(space_oauth_api):
|
async def test_cloud_login_entry_starts_stateful_space_oauth(space_oauth_api):
|
||||||
application, client = space_oauth_api
|
application, client = space_oauth_api
|
||||||
application.deployment.mode = 'cloud'
|
application.deployment.mode = 'cloud'
|
||||||
|
|
||||||
@@ -143,9 +143,9 @@ async def test_cloud_login_entry_redirects_to_space_workspace_launcher(space_oau
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert (await response.get_json())['data']['authorize_url'] == ('https://space.example/cloud?environment=beta')
|
authorize_url = (await response.get_json())['data']['authorize_url']
|
||||||
application.space_service.get_cloud_entry_url.assert_called_once_with()
|
assert authorize_url.startswith('https://space.example/authorize?state=')
|
||||||
application.user_service.issue_space_oauth_state.assert_not_awaited()
|
application.user_service.issue_space_oauth_state.assert_awaited_once_with('login')
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|||||||
@@ -1385,17 +1385,13 @@ export class BackendClient extends BaseHttpClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============ Space OAuth API (Redirect Flow) ============
|
// ============ Space OAuth API (Redirect Flow) ============
|
||||||
public getSpaceAuthorizeUrl(
|
public getSpaceAuthorizeUrl(redirectUri: string): Promise<{
|
||||||
redirectUri: string,
|
|
||||||
options?: { cloudEntry?: boolean },
|
|
||||||
): Promise<{
|
|
||||||
authorize_url: string;
|
authorize_url: string;
|
||||||
}> {
|
}> {
|
||||||
return this.get(
|
return this.get(
|
||||||
'/api/v1/user/space/authorize-url',
|
'/api/v1/user/space/authorize-url',
|
||||||
{
|
{
|
||||||
redirect_uri: redirectUri,
|
redirect_uri: redirectUri,
|
||||||
...(options?.cloudEntry ? { cloud_entry: '1' } : {}),
|
|
||||||
},
|
},
|
||||||
{ skipWorkspace: true },
|
{ skipWorkspace: true },
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -202,13 +202,7 @@ export default function Login() {
|
|||||||
try {
|
try {
|
||||||
const currentOrigin = window.location.origin;
|
const currentOrigin = window.location.origin;
|
||||||
const redirectUri = `${currentOrigin}/auth/space/callback`;
|
const redirectUri = `${currentOrigin}/auth/space/callback`;
|
||||||
const response = await httpClient.getSpaceAuthorizeUrl(redirectUri, {
|
const response = await httpClient.getSpaceAuthorizeUrl(redirectUri);
|
||||||
// Cloud Accounts must be launched from Space so a first visit can
|
|
||||||
// lazily create and project the personal Workspace. Invitation login
|
|
||||||
// remains on the OAuth callback path because it targets the invited
|
|
||||||
// Workspace instead.
|
|
||||||
cloudEntry: !getPendingInvitationToken(),
|
|
||||||
});
|
|
||||||
window.location.href = response.authorize_url;
|
window.location.href = response.authorize_url;
|
||||||
} catch {
|
} catch {
|
||||||
toast.error(t('common.spaceLoginFailed'));
|
toast.error(t('common.spaceLoginFailed'));
|
||||||
|
|||||||
@@ -7,11 +7,12 @@ const source = fs.readFileSync(
|
|||||||
'utf8',
|
'utf8',
|
||||||
);
|
);
|
||||||
|
|
||||||
test('normal Cloud login enters through the Space Workspace launcher', () => {
|
test('normal Cloud login starts stateful Space OAuth', () => {
|
||||||
assert.match(source, /cloudEntry:\s*!getPendingInvitationToken\(\)/);
|
assert.match(source, /getSpaceAuthorizeUrl\(redirectUri\)/);
|
||||||
assert.match(source, /getSpaceAuthorizeUrl\(redirectUri,\s*\{/);
|
assert.doesNotMatch(source, /cloudEntry/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('invitation login remains on the OAuth callback path', () => {
|
test('invitation login remains on the OAuth callback path', () => {
|
||||||
assert.match(source, /cloudEntry:\s*!getPendingInvitationToken\(\)/);
|
assert.match(source, /getPendingInvitationToken\(\)/);
|
||||||
|
assert.match(source, /getSpaceAuthorizeUrl\(redirectUri\)/);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user