mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-24 18:52:29 -07:00
feat(workspaces): implement organization-based multi-tenancy and shared workspaces
This commit is contained in:
@@ -10,6 +10,7 @@ export interface AuthenticatedRequest extends Request {
|
||||
user: {
|
||||
id: string;
|
||||
email: string;
|
||||
org_id?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -20,6 +21,7 @@ export interface AuthenticatedRequest extends Request {
|
||||
export const iapMiddleware = (req: Request, res: Response, next: NextFunction) => {
|
||||
const userEmail = req.header('x-goog-authenticated-user-email');
|
||||
const userId = req.header('x-goog-authenticated-user-id');
|
||||
const orgId = req.header('x-goog-authenticated-user-org');
|
||||
|
||||
// If running locally or without IAP, use a dev user
|
||||
if (!userEmail || !userId) {
|
||||
@@ -28,9 +30,10 @@ export const iapMiddleware = (req: Request, res: Response, next: NextFunction) =
|
||||
return;
|
||||
}
|
||||
|
||||
(req as AuthenticatedRequest).user = {
|
||||
(req as unknown as AuthenticatedRequest).user = {
|
||||
id: 'dev-user-id',
|
||||
email: 'dev-user@google.com',
|
||||
org_id: 'dev-org-id',
|
||||
};
|
||||
next();
|
||||
return;
|
||||
@@ -40,9 +43,10 @@ export const iapMiddleware = (req: Request, res: Response, next: NextFunction) =
|
||||
const cleanId = userId.replace('accounts.google.com:', '');
|
||||
const cleanEmail = userEmail.replace('accounts.google.com:', '');
|
||||
|
||||
(req as AuthenticatedRequest).user = {
|
||||
(req as unknown as AuthenticatedRequest).user = {
|
||||
id: cleanId,
|
||||
email: cleanEmail,
|
||||
org_id: orgId,
|
||||
};
|
||||
|
||||
next();
|
||||
|
||||
@@ -20,12 +20,17 @@ const CreateWorkspaceSchema = z.object({
|
||||
machineType: z.string().optional().default('e2-standard-4'),
|
||||
imageTag: z.string().optional().default('latest'),
|
||||
zone: z.string().optional().default('us-west1-a'),
|
||||
orgId: z.string().optional(),
|
||||
repoId: z.string().optional(),
|
||||
});
|
||||
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const authReq = req as unknown as AuthenticatedRequest;
|
||||
const workspaces = await workspaceService.listWorkspaces(authReq.user.id);
|
||||
const workspaces = await workspaceService.listWorkspacesForUser(
|
||||
authReq.user.id,
|
||||
authReq.user.org_id
|
||||
);
|
||||
res.json(workspaces);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
@@ -42,7 +47,7 @@ router.post('/', async (req, res) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const { name, machineType, imageTag, zone } = validation.data;
|
||||
const { name, machineType, imageTag, zone, orgId, repoId } = validation.data;
|
||||
const workspaceId = uuidv4();
|
||||
const instanceName = `workspace-${workspaceId.slice(0, 8)}`;
|
||||
|
||||
@@ -57,6 +62,8 @@ router.post('/', async (req, res) => {
|
||||
project_id: computeService.getProjectId(),
|
||||
created_at: now,
|
||||
last_connected_at: now,
|
||||
org_id: orgId || authReq.user.org_id,
|
||||
repo_id: repoId,
|
||||
};
|
||||
|
||||
// 1. Save to state store
|
||||
|
||||
@@ -16,6 +16,9 @@ export interface WorkspaceData {
|
||||
project_id: string;
|
||||
created_at: string;
|
||||
last_connected_at: string;
|
||||
team_id?: string;
|
||||
org_id?: string;
|
||||
repo_id?: string;
|
||||
}
|
||||
|
||||
export interface WorkspaceRecord extends WorkspaceData {
|
||||
@@ -46,6 +49,37 @@ export class WorkspaceService {
|
||||
});
|
||||
}
|
||||
|
||||
async listWorkspacesForUser(ownerId: string, orgId?: string): Promise<WorkspaceRecord[]> {
|
||||
// 1. Get workspaces owned by user
|
||||
const userSnapshot = await this.getCollection()
|
||||
.where('owner_id', '==', ownerId)
|
||||
.get();
|
||||
|
||||
const userWorkspaces = userSnapshot.docs.map(doc => ({
|
||||
id: doc.id,
|
||||
...(doc.data() as WorkspaceData),
|
||||
}));
|
||||
|
||||
// 2. Get workspaces shared with user's org (if orgId provided)
|
||||
if (orgId) {
|
||||
const orgSnapshot = await this.getCollection()
|
||||
.where('org_id', '==', orgId)
|
||||
.get();
|
||||
|
||||
const orgWorkspaces = orgSnapshot.docs.map(doc => ({
|
||||
id: doc.id,
|
||||
...(doc.data() as WorkspaceData),
|
||||
}));
|
||||
|
||||
// Combine and deduplicate by ID
|
||||
const combined = [...userWorkspaces, ...orgWorkspaces];
|
||||
const unique = Array.from(new Map(combined.map(ws => [ws.id, ws])).values());
|
||||
return unique;
|
||||
}
|
||||
|
||||
return userWorkspaces;
|
||||
}
|
||||
|
||||
async listWorkspaces(ownerId: string): Promise<WorkspaceRecord[]> {
|
||||
const snapshot = await this.getCollection()
|
||||
.where('owner_id', '==', ownerId)
|
||||
|
||||
Reference in New Issue
Block a user