feat(cloud): add scoped support admin sessions

This commit is contained in:
dadachann
2026-07-31 07:25:33 +00:00
parent e1ac5e0fc8
commit b0ece9c455
25 changed files with 1285 additions and 25 deletions
+14 -1
View File
@@ -3,6 +3,7 @@ import { useNavigate, useSearchParams } from 'react-router-dom';
import { httpClient } from '@/app/infra/http/HttpClient';
import {
beginAuthenticatedSession,
beginSupportAdminSession,
bootstrapWorkspaceSession,
getPendingInvitationToken,
} from '@/app/infra/http';
@@ -27,8 +28,10 @@ import langbotIcon from '@/app/assets/langbot-logo.webp';
type SpaceOAuthLoginResult = {
token: string;
user: string;
user?: string;
workspace_uuid?: string;
principal_type?: 'account' | 'support_admin';
actor_account_uuid?: string;
};
const pendingSpaceOAuthLogins = new Map<
@@ -94,6 +97,16 @@ function SpaceOAuthCallbackContent() {
return;
}
if (response.principal_type === 'support_admin') {
if (!response.workspace_uuid) {
throw new Error('Support admin launch did not include a Workspace');
}
beginSupportAdminSession(response.token, response.workspace_uuid);
await bootstrapWorkspaceSession();
navigate('/home', { replace: true });
return;
}
beginAuthenticatedSession(response.token, response.user);
if (getPendingInvitationToken()) {
navigate('/invitations/accept', { replace: true });
+5 -1
View File
@@ -17,6 +17,7 @@ import {
bootstrapWorkspaceSession,
systemInfo,
initializeSystemInfo,
isSupportAdminSession,
useCurrentWorkspace,
} from '@/app/infra/http';
import { useNavigate, useLocation } from 'react-router-dom';
@@ -156,7 +157,10 @@ export default function HomeLayout({
// selected Workspace's wizard state.
useEffect(() => {
if (!identityReady) return;
if (systemInfo.wizard_status === 'none') {
if (
systemInfo?.wizard_status === 'none' &&
!isSupportAdminSession()
) {
navigate('/wizard', { replace: true });
}
}, [identityReady, navigate]);
+3 -1
View File
@@ -1327,8 +1327,10 @@ export class BackendClient extends BaseHttpClient {
launchAssertion?: string,
): Promise<{
token: string;
user: string;
user?: string;
workspace_uuid?: string;
principal_type?: 'account' | 'support_admin';
actor_account_uuid?: string;
}> {
const response = await this.instance.post(
'/api/v1/user/space/callback',
+48
View File
@@ -217,10 +217,34 @@ export function beginAuthenticatedSession(
if (typeof window === 'undefined') return;
localStorage.removeItem('token');
localStorage.removeItem('userEmail');
localStorage.removeItem('authPrincipalType');
localStorage.setItem('token', token);
if (userEmail) localStorage.setItem('userEmail', userEmail);
}
export function beginSupportAdminSession(
token: string,
workspaceUuid: string,
): void {
userInfo = null;
clearWorkspaceSelection();
clearWorkspaceBootstrapSnapshot();
if (typeof window === 'undefined') return;
localStorage.removeItem('token');
localStorage.removeItem('userEmail');
localStorage.setItem('token', token);
localStorage.setItem('authPrincipalType', 'support_admin');
setActiveWorkspaceUuid(workspaceUuid);
}
export function isSupportAdminSession(): boolean {
return (
typeof window !== 'undefined' &&
localStorage.getItem('authPrincipalType') === 'support_admin'
);
}
async function initializeSelectedWorkspace(
workspaceUuid: string,
workspaces: WorkspaceBootstrapEntry[],
@@ -252,6 +276,27 @@ async function initializeSelectedWorkspace(
export async function bootstrapWorkspaceSession(
options: WorkspaceBootstrapOptions = {},
): Promise<WorkspaceBootstrapResult> {
if (isSupportAdminSession()) {
const selectedWorkspaceUuid = getActiveWorkspaceUuid();
if (!selectedWorkspaceUuid) {
throw new Error('Support admin session is missing its Workspace scope');
}
if (
options.preferredWorkspaceUuid &&
options.preferredWorkspaceUuid !== selectedWorkspaceUuid
) {
throw new Error('Support admin session cannot change Workspace scope');
}
await initializeWorkspaceInfo();
const workspace = getCurrentWorkspaceSnapshot();
if (!workspace || workspace.workspace.uuid !== selectedWorkspaceUuid) {
clearWorkspaceSelection();
throw new Error('Support admin Workspace scope could not be initialized');
}
clearWorkspaceBootstrapSnapshot();
return { status: 'ready', workspace, workspaces: [] };
}
if (options.resetSelection) {
clearWorkspaceSelection();
clearWorkspaceBootstrapSnapshot();
@@ -339,6 +384,9 @@ export const clearUserInfo = (): void => {
userInfo = null;
clearWorkspaceSelection();
clearWorkspaceBootstrapSnapshot();
if (typeof window !== 'undefined') {
localStorage.removeItem('authPrincipalType');
}
};
export {
@@ -0,0 +1,39 @@
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import test from 'node:test';
import { fileURLToPath } from 'node:url';
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
test('support-admin launch stores a scoped principal instead of starting an Account session', () => {
const callback = read('src/app/auth/space/callback/page.tsx');
assert.match(callback, /response\.principal_type === 'support_admin'/);
assert.match(callback, /beginSupportAdminSession\(response\.token, response\.workspace_uuid\)/);
});
test('support-admin workspace bootstrap never calls Account bootstrap', () => {
const source = read('src/app/infra/http/index.ts');
assert.match(source, /export function beginSupportAdminSession/);
assert.match(
source,
/export function isSupportAdminSession\(\): boolean/,
);
const supportBranch = source.indexOf('if (isSupportAdminSession())');
const accountBootstrap = source.indexOf(
'backendClient.getWorkspaceBootstrap()',
);
assert.ok(supportBranch >= 0);
assert.ok(accountBootstrap > supportBranch);
assert.match(
source.slice(supportBranch, accountBootstrap),
/initializeWorkspaceInfo\([\s\S]*status: 'ready'/,
);
assert.match(
source,
/localStorage\.setItem\('authPrincipalType', 'support_admin'\)/,
);
const homeLayout = read('src/app/home/layout.tsx');
assert.match(homeLayout, /!isSupportAdminSession\(\)/);
});