@@ -21,6 +21,7 @@ export interface AuthServiceEvents {
2121const 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
2627type 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,17 @@ export class AuthService extends EventEmitter<AuthServiceEvents> {
454481 const formData = new URLSearchParams ( )
455482 formData . append ( "_is_native" , "1" )
456483
484+ // Handle 3 cases for organization_id:
485+ // 1. Have an org id: organization_id=THE_ORG_ID
486+ // 2. Have a personal account: organization_id= (empty string)
487+ // 3. Don't know if you have an org id (old style credentials): don't send organization_id param at all
488+ const organizationId = this . getStoredOrganizationId ( )
489+ if ( this . credentials ?. organizationId !== undefined ) {
490+ // We have organization context info (either org id or personal account)
491+ formData . append ( "organization_id" , organizationId || "" )
492+ }
493+ // If organizationId is undefined, don't send the param at all (old credentials)
494+
457495 const response = await fetch ( `${ getClerkBaseUrl ( ) } /v1/client/sessions/${ this . credentials ! . sessionId } /tokens` , {
458496 method : "POST" ,
459497 headers : {
@@ -505,29 +543,74 @@ export class AuthService extends EventEmitter<AuthServiceEvents> {
505543
506544 userInfo . picture = userData . image_url
507545
508- // Fetch organization memberships separately
546+ // Fetch organization info if user is in organization context
509547 try {
510- const orgMemberships = await this . clerkGetOrganizationMemberships ( )
511- if ( orgMemberships && orgMemberships . length > 0 ) {
512- // Get the first (or active) organization membership
513- const primaryOrgMembership = orgMemberships [ 0 ]
514- const organization = primaryOrgMembership ?. organization
515-
516- if ( organization ) {
517- userInfo . organizationId = organization . id
518- userInfo . organizationName = organization . name
519- userInfo . organizationRole = primaryOrgMembership . role
520- userInfo . organizationImageUrl = organization . image_url
548+ const storedOrgId = this . getStoredOrganizationId ( )
549+
550+ if ( this . credentials ?. organizationId !== undefined ) {
551+ // We have organization context info
552+ if ( storedOrgId !== null ) {
553+ // User is in organization context - fetch user's memberships and filter
554+ const orgMemberships = await this . clerkGetOrganizationMemberships ( )
555+ const userMembership = this . findOrganizationMembership ( orgMemberships , storedOrgId )
556+
557+ if ( userMembership ) {
558+ this . setUserOrganizationInfo ( userInfo , userMembership )
559+ this . log ( "[auth] User in organization context:" , {
560+ id : userMembership . organization . id ,
561+ name : userMembership . organization . name ,
562+ role : userMembership . role ,
563+ } )
564+ } else {
565+ this . log ( "[auth] Warning: User not found in stored organization:" , storedOrgId )
566+ }
567+ } else {
568+ this . log ( "[auth] User in personal account context - not setting organization info" )
569+ }
570+ } else {
571+ // Old credentials without organization context - fetch organization info to determine context
572+ const orgMemberships = await this . clerkGetOrganizationMemberships ( )
573+ const primaryOrgMembership = this . findPrimaryOrganizationMembership ( orgMemberships )
574+
575+ if ( primaryOrgMembership ) {
576+ this . setUserOrganizationInfo ( userInfo , primaryOrgMembership )
577+ this . log ( "[auth] Legacy credentials: Found organization membership:" , {
578+ id : primaryOrgMembership . organization . id ,
579+ name : primaryOrgMembership . organization . name ,
580+ role : primaryOrgMembership . role ,
581+ } )
582+ } else {
583+ this . log ( "[auth] Legacy credentials: No organization memberships found" )
521584 }
522585 }
523586 } catch ( error ) {
524- this . log ( "[auth] Failed to fetch organization memberships :" , error )
587+ this . log ( "[auth] Failed to fetch organization info :" , error )
525588 // Don't throw - organization info is optional
526589 }
527590
528591 return userInfo
529592 }
530593
594+ private findOrganizationMembership (
595+ memberships : CloudOrganizationMembership [ ] ,
596+ organizationId : string ,
597+ ) : CloudOrganizationMembership | undefined {
598+ return memberships ?. find ( ( membership ) => membership . organization . id === organizationId )
599+ }
600+
601+ private findPrimaryOrganizationMembership (
602+ memberships : CloudOrganizationMembership [ ] ,
603+ ) : CloudOrganizationMembership | undefined {
604+ return memberships && memberships . length > 0 ? memberships [ 0 ] : undefined
605+ }
606+
607+ private setUserOrganizationInfo ( userInfo : CloudUserInfo , membership : CloudOrganizationMembership ) : void {
608+ userInfo . organizationId = membership . organization . id
609+ userInfo . organizationName = membership . organization . name
610+ userInfo . organizationRole = membership . role
611+ userInfo . organizationImageUrl = membership . organization . image_url
612+ }
613+
531614 private async clerkGetOrganizationMemberships ( ) : Promise < CloudOrganizationMembership [ ] > {
532615 const response = await fetch ( `${ getClerkBaseUrl ( ) } /v1/me/organization_memberships` , {
533616 headers : {
0 commit comments