Skip to content

Commit a114ce5

Browse files
committed
Store the organization id in credentials
1 parent 922f483 commit a114ce5

File tree

6 files changed

+118
-11
lines changed

6 files changed

+118
-11
lines changed

packages/cloud/src/AuthService.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export interface AuthServiceEvents {
2121
const authCredentialsSchema = z.object({
2222
clientToken: z.string().min(1, "Client token cannot be empty"),
2323
sessionId: z.string().min(1, "Session ID cannot be empty"),
24+
organizationId: z.string().nullable().optional(),
2425
})
2526

2627
type AuthCredentials = z.infer<typeof authCredentialsSchema>
@@ -220,7 +221,16 @@ export class AuthService extends EventEmitter<AuthServiceEvents> {
220221

221222
try {
222223
const parsedJson = JSON.parse(credentialsJson)
223-
return authCredentialsSchema.parse(parsedJson)
224+
const credentials = authCredentialsSchema.parse(parsedJson)
225+
226+
// Migration: If no organizationId but we have userInfo, add it
227+
if (credentials.organizationId === undefined && this.userInfo?.organizationId) {
228+
credentials.organizationId = this.userInfo.organizationId
229+
await this.storeCredentials(credentials)
230+
this.log("[auth] Migrated credentials with organizationId")
231+
}
232+
233+
return credentials
224234
} catch (error) {
225235
if (error instanceof z.ZodError) {
226236
this.log("[auth] Invalid credentials format:", error.errors)
@@ -269,8 +279,13 @@ export class AuthService extends EventEmitter<AuthServiceEvents> {
269279
*
270280
* @param code The authorization code from the callback
271281
* @param state The state parameter from the callback
282+
* @param organizationId The organization ID from the callback (null for personal accounts)
272283
*/
273-
public async handleCallback(code: string | null, state: string | null): Promise<void> {
284+
public async handleCallback(
285+
code: string | null,
286+
state: string | null,
287+
organizationId?: string | null,
288+
): Promise<void> {
274289
if (!code || !state) {
275290
vscode.window.showInformationMessage("Invalid Roo Code Cloud sign in url")
276291
return
@@ -287,6 +302,9 @@ export class AuthService extends EventEmitter<AuthServiceEvents> {
287302

288303
const credentials = await this.clerkSignIn(code)
289304

305+
// Set organizationId (null for personal accounts)
306+
credentials.organizationId = organizationId || null
307+
290308
await this.storeCredentials(credentials)
291309

292310
vscode.window.showInformationMessage("Successfully authenticated with Roo Code Cloud")
@@ -417,6 +435,15 @@ export class AuthService extends EventEmitter<AuthServiceEvents> {
417435
return this.userInfo
418436
}
419437

438+
/**
439+
* Get the stored organization ID from credentials
440+
*
441+
* @returns The stored organization ID, null for personal accounts or if no credentials exist
442+
*/
443+
public getStoredOrganizationId(): string | null {
444+
return this.credentials?.organizationId || null
445+
}
446+
420447
private async clerkSignIn(ticket: string): Promise<AuthCredentials> {
421448
const formData = new URLSearchParams()
422449
formData.append("strategy", "ticket")
@@ -454,6 +481,12 @@ export class AuthService extends EventEmitter<AuthServiceEvents> {
454481
const formData = new URLSearchParams()
455482
formData.append("_is_native", "1")
456483

484+
// Only add organization_id if not null (personal accounts)
485+
const organizationId = this.getStoredOrganizationId()
486+
if (organizationId !== null) {
487+
formData.append("organization_id", organizationId)
488+
}
489+
457490
const response = await fetch(`${getClerkBaseUrl()}/v1/client/sessions/${this.credentials!.sessionId}/tokens`, {
458491
method: "POST",
459492
headers: {

packages/cloud/src/CloudService.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,14 +118,28 @@ export class CloudService {
118118
return userInfo?.organizationRole || null
119119
}
120120

121+
public hasStoredOrganizationId(): boolean {
122+
this.ensureInitialized()
123+
return this.authService!.getStoredOrganizationId() !== null
124+
}
125+
126+
public getStoredOrganizationId(): string | null {
127+
this.ensureInitialized()
128+
return this.authService!.getStoredOrganizationId()
129+
}
130+
121131
public getAuthState(): string {
122132
this.ensureInitialized()
123133
return this.authService!.getState()
124134
}
125135

126-
public async handleAuthCallback(code: string | null, state: string | null): Promise<void> {
136+
public async handleAuthCallback(
137+
code: string | null,
138+
state: string | null,
139+
organizationId?: string | null,
140+
): Promise<void> {
127141
this.ensureInitialized()
128-
return this.authService!.handleCallback(code, state)
142+
return this.authService!.handleCallback(code, state, organizationId)
129143
}
130144

131145
// SettingsService

packages/cloud/src/__tests__/AuthService.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ describe("AuthService", () => {
328328

329329
expect(mockContext.secrets.store).toHaveBeenCalledWith(
330330
"clerk-auth-credentials",
331-
JSON.stringify({ clientToken: "Bearer token-123", sessionId: "session-123" }),
331+
JSON.stringify({ clientToken: "Bearer token-123", sessionId: "session-123", organizationId: null }),
332332
)
333333
expect(mockShowInfo).toHaveBeenCalledWith("Successfully authenticated with Roo Code Cloud")
334334
})

packages/cloud/src/__tests__/CloudService.test.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ describe("CloudService", () => {
4040
getState: ReturnType<typeof vi.fn>
4141
getSessionToken: ReturnType<typeof vi.fn>
4242
handleCallback: ReturnType<typeof vi.fn>
43+
getStoredOrganizationId: ReturnType<typeof vi.fn>
4344
on: ReturnType<typeof vi.fn>
4445
off: ReturnType<typeof vi.fn>
4546
once: ReturnType<typeof vi.fn>
@@ -88,6 +89,7 @@ describe("CloudService", () => {
8889
getState: vi.fn().mockReturnValue("logged-out"),
8990
getSessionToken: vi.fn(),
9091
handleCallback: vi.fn(),
92+
getStoredOrganizationId: vi.fn().mockReturnValue(null),
9193
on: vi.fn(),
9294
off: vi.fn(),
9395
once: vi.fn(),
@@ -255,7 +257,41 @@ describe("CloudService", () => {
255257

256258
it("should delegate handleAuthCallback to AuthService", async () => {
257259
await cloudService.handleAuthCallback("code", "state")
258-
expect(mockAuthService.handleCallback).toHaveBeenCalledWith("code", "state")
260+
expect(mockAuthService.handleCallback).toHaveBeenCalledWith("code", "state", undefined)
261+
})
262+
263+
it("should delegate handleAuthCallback with organizationId to AuthService", async () => {
264+
await cloudService.handleAuthCallback("code", "state", "org_123")
265+
expect(mockAuthService.handleCallback).toHaveBeenCalledWith("code", "state", "org_123")
266+
})
267+
268+
it("should return stored organization ID from AuthService", () => {
269+
mockAuthService.getStoredOrganizationId.mockReturnValue("org_456")
270+
271+
const result = cloudService.getStoredOrganizationId()
272+
expect(mockAuthService.getStoredOrganizationId).toHaveBeenCalled()
273+
expect(result).toBe("org_456")
274+
})
275+
276+
it("should return null when no stored organization ID available", () => {
277+
mockAuthService.getStoredOrganizationId.mockReturnValue(null)
278+
279+
const result = cloudService.getStoredOrganizationId()
280+
expect(result).toBe(null)
281+
})
282+
283+
it("should return true when stored organization ID exists", () => {
284+
mockAuthService.getStoredOrganizationId.mockReturnValue("org_789")
285+
286+
const result = cloudService.hasStoredOrganizationId()
287+
expect(result).toBe(true)
288+
})
289+
290+
it("should return false when no stored organization ID exists", () => {
291+
mockAuthService.getStoredOrganizationId.mockReturnValue(null)
292+
293+
const result = cloudService.hasStoredOrganizationId()
294+
expect(result).toBe(false)
259295
})
260296
})
261297

src/activate/handleUri.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,13 @@ export const handleUri = async (uri: vscode.Uri) => {
3838
case "/auth/clerk/callback": {
3939
const code = query.get("code")
4040
const state = query.get("state")
41-
await CloudService.instance.handleAuthCallback(code, state)
41+
const organizationId = query.get("organizationId")
42+
43+
await CloudService.instance.handleAuthCallback(
44+
code,
45+
state,
46+
organizationId === "null" ? null : organizationId,
47+
)
4248
break
4349
}
4450
default:

src/services/mdm/MdmService.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { z } from "zod"
66

77
import { CloudService, getClerkBaseUrl, PRODUCTION_CLERK_BASE_URL } from "@roo-code/cloud"
88
import { Package } from "../../shared/package"
9+
import { t } from "../../i18n"
910

1011
// MDM Configuration Schema
1112
const mdmConfigSchema = z.object({
@@ -89,26 +90,43 @@ export class MdmService {
8990
if (!CloudService.hasInstance() || !CloudService.instance.hasOrIsAcquiringActiveSession()) {
9091
return {
9192
compliant: false,
92-
reason: "Your organization requires Roo Code Cloud authentication. Please sign in to continue.",
93+
reason: t("mdm.errors.cloud_auth_required"),
9394
}
9495
}
9596

9697
// Check organization match if specified
9798
const requiredOrgId = this.getRequiredOrganizationId()
9899
if (requiredOrgId) {
99100
try {
100-
const currentOrgId = CloudService.instance.getOrganizationId()
101+
// First try to get from active session
102+
let currentOrgId = CloudService.instance.getOrganizationId()
103+
104+
// If no active session, check stored credentials
105+
if (!currentOrgId) {
106+
const storedOrgId = CloudService.instance.getStoredOrganizationId()
107+
108+
// null means personal account, which is not compliant for org requirements
109+
if (storedOrgId === null || storedOrgId !== requiredOrgId) {
110+
return {
111+
compliant: false,
112+
reason: t("mdm.errors.organization_mismatch"),
113+
}
114+
}
115+
116+
currentOrgId = storedOrgId
117+
}
118+
101119
if (currentOrgId !== requiredOrgId) {
102120
return {
103121
compliant: false,
104-
reason: "You must be authenticated with your organization's Roo Code Cloud account.",
122+
reason: t("mdm.errors.organization_mismatch"),
105123
}
106124
}
107125
} catch (error) {
108126
this.log("[MDM] Error checking organization ID:", error)
109127
return {
110128
compliant: false,
111-
reason: "Unable to verify organization authentication.",
129+
reason: t("mdm.errors.verification_failed"),
112130
}
113131
}
114132
}

0 commit comments

Comments
 (0)