diff --git a/packages/cloud/src/AuthService.ts b/packages/cloud/src/AuthService.ts index fda7df7945..68036ce3c9 100644 --- a/packages/cloud/src/AuthService.ts +++ b/packages/cloud/src/AuthService.ts @@ -5,7 +5,7 @@ import axios from "axios" import * as vscode from "vscode" import { z } from "zod" -import type { CloudUserInfo } from "@roo-code/types" +import type { CloudUserInfo, CloudOrganizationMembership } from "@roo-code/types" import { getClerkBaseUrl, getRooCodeApiUrl } from "./Config" import { RefreshTimer } from "./RefreshTimer" @@ -420,9 +420,42 @@ export class AuthService extends EventEmitter { } userInfo.picture = userData?.image_url + + // Fetch organization memberships separately + try { + const orgMemberships = await this.clerkGetOrganizationMemberships() + if (orgMemberships && orgMemberships.length > 0) { + // Get the first (or active) organization membership + const primaryOrgMembership = orgMemberships[0] + const organization = primaryOrgMembership?.organization + + if (organization) { + userInfo.organizationId = organization.id + userInfo.organizationName = organization.name + userInfo.organizationRole = primaryOrgMembership.role + } + } + } catch (error) { + this.log("[auth] Failed to fetch organization memberships:", error) + // Don't throw - organization info is optional + } + return userInfo } + private async clerkGetOrganizationMemberships(): Promise { + const response = await axios.get(`${getClerkBaseUrl()}/v1/me/organization_memberships`, { + headers: { + Authorization: `Bearer ${this.credentials!.clientToken}`, + "User-Agent": this.userAgent(), + }, + }) + + // The response structure is: { response: [...] } + // Extract the organization memberships from the response.response array + return response.data?.response || [] + } + private async clerkLogout(credentials: AuthCredentials): Promise { const formData = new URLSearchParams() formData.append("_is_native", "1") diff --git a/packages/cloud/src/CloudService.ts b/packages/cloud/src/CloudService.ts index 08a270bfc3..fe3bad970c 100644 --- a/packages/cloud/src/CloudService.ts +++ b/packages/cloud/src/CloudService.ts @@ -92,6 +92,24 @@ export class CloudService { return this.authService!.getUserInfo() } + public getOrganizationId(): string | null { + this.ensureInitialized() + const userInfo = this.authService!.getUserInfo() + return userInfo?.organizationId || null + } + + public getOrganizationName(): string | null { + this.ensureInitialized() + const userInfo = this.authService!.getUserInfo() + return userInfo?.organizationName || null + } + + public getOrganizationRole(): string | null { + this.ensureInitialized() + const userInfo = this.authService!.getUserInfo() + return userInfo?.organizationRole || null + } + public getAuthState(): string { this.ensureInitialized() return this.authService!.getState() diff --git a/packages/cloud/src/__tests__/CloudService.test.ts b/packages/cloud/src/__tests__/CloudService.test.ts index 8e6ca98313..03b28568d5 100644 --- a/packages/cloud/src/__tests__/CloudService.test.ts +++ b/packages/cloud/src/__tests__/CloudService.test.ts @@ -184,6 +184,72 @@ describe("CloudService", () => { expect(mockAuthService.getUserInfo).toHaveBeenCalled() }) + it("should return organization ID from user info", () => { + const mockUserInfo = { + name: "Test User", + email: "test@example.com", + organizationId: "org_123", + organizationName: "Test Org", + organizationRole: "admin", + } + mockAuthService.getUserInfo.mockReturnValue(mockUserInfo) + + const result = cloudService.getOrganizationId() + expect(mockAuthService.getUserInfo).toHaveBeenCalled() + expect(result).toBe("org_123") + }) + + it("should return null when no organization ID available", () => { + mockAuthService.getUserInfo.mockReturnValue(null) + + const result = cloudService.getOrganizationId() + expect(result).toBe(null) + }) + + it("should return organization name from user info", () => { + const mockUserInfo = { + name: "Test User", + email: "test@example.com", + organizationId: "org_123", + organizationName: "Test Org", + organizationRole: "admin", + } + mockAuthService.getUserInfo.mockReturnValue(mockUserInfo) + + const result = cloudService.getOrganizationName() + expect(mockAuthService.getUserInfo).toHaveBeenCalled() + expect(result).toBe("Test Org") + }) + + it("should return null when no organization name available", () => { + mockAuthService.getUserInfo.mockReturnValue(null) + + const result = cloudService.getOrganizationName() + expect(result).toBe(null) + }) + + it("should return organization role from user info", () => { + const mockUserInfo = { + name: "Test User", + email: "test@example.com", + organizationId: "org_123", + organizationName: "Test Org", + organizationRole: "admin", + } + mockAuthService.getUserInfo.mockReturnValue(mockUserInfo) + + const result = cloudService.getOrganizationRole() + expect(mockAuthService.getUserInfo).toHaveBeenCalled() + expect(result).toBe("admin") + }) + + it("should return null when no organization role available", () => { + mockAuthService.getUserInfo.mockReturnValue(null) + + const result = cloudService.getOrganizationRole() + expect(result).toBe(null) + }) + it("should delegate getAuthState to AuthService", () => { const result = cloudService.getAuthState() expect(mockAuthService.getState).toHaveBeenCalled() diff --git a/packages/types/src/cloud.ts b/packages/types/src/cloud.ts index 6347d596fe..6f5547b3d5 100644 --- a/packages/types/src/cloud.ts +++ b/packages/types/src/cloud.ts @@ -10,6 +10,32 @@ export interface CloudUserInfo { name?: string email?: string picture?: string + organizationId?: string + organizationName?: string + organizationRole?: string +} + +/** + * CloudOrganization Types + */ + +export interface CloudOrganization { + id: string + name: string + slug?: string + image_url?: string + has_image?: boolean + created_at?: number + updated_at?: number +} + +export interface CloudOrganizationMembership { + id: string + organization: CloudOrganization + role: string + permissions?: string[] + created_at?: number + updated_at?: number } /** diff --git a/webview-ui/src/components/account/AccountView.tsx b/webview-ui/src/components/account/AccountView.tsx index 1468caa58e..04d6cb7d2b 100644 --- a/webview-ui/src/components/account/AccountView.tsx +++ b/webview-ui/src/components/account/AccountView.tsx @@ -44,6 +44,11 @@ export const AccountView = ({ userInfo, isAuthenticated, onDone }: AccountViewPr

{userInfo?.name || t("account:unknownUser")}

+ {userInfo?.organizationName && ( +

+ {userInfo.organizationName} +

+ )}

{userInfo?.email || ""}

)}