@@ -109,44 +109,43 @@ export class OAuthService {
109109 } ;
110110
111111 const codeVerifier = this . generateCodeVerifier ( ) ;
112- const codeChallenge = this . generateCodeChallenge ( codeVerifier ) ;
113- const redirectUri = this . getRedirectUri ( ) ;
112+ const authUrl = this . buildAuthorizeUrl ( region , codeVerifier ) ;
114113
115- // Build the authorization URL
116- const cloudUrl = getCloudUrlFromRegion ( region ) ;
117- const authUrl = new URL ( `${ cloudUrl } /oauth/authorize` ) ;
118- authUrl . searchParams . set ( "client_id" , getOauthClientIdFromRegion ( region ) ) ;
119- authUrl . searchParams . set ( "redirect_uri" , redirectUri ) ;
120- authUrl . searchParams . set ( "response_type" , "code" ) ;
121- authUrl . searchParams . set ( "code_challenge" , codeChallenge ) ;
122- authUrl . searchParams . set ( "code_challenge_method" , "S256" ) ;
123- authUrl . searchParams . set ( "scope" , config . scopes . join ( " " ) ) ;
124- authUrl . searchParams . set ( "required_access_level" , "project" ) ;
125-
126- // Create a promise that will be resolved when the callback arrives
127- const code = IS_DEV
128- ? await this . waitForHttpCallback (
129- codeVerifier ,
130- config ,
131- authUrl . toString ( ) ,
132- )
133- : await this . waitForDeepLinkCallback (
134- codeVerifier ,
135- config ,
136- authUrl . toString ( ) ,
137- ) ;
138-
139- // Exchange the code for tokens
140- const tokenResponse = await this . exchangeCodeForToken (
141- code ,
142- codeVerifier ,
114+ return await this . startFlowWithUrl (
143115 config ,
116+ codeVerifier ,
117+ authUrl . toString ( ) ,
144118 ) ;
145-
119+ } catch ( error ) {
146120 return {
147- success : true ,
148- data : tokenResponse ,
121+ success : false ,
122+ error : error instanceof Error ? error . message : "Unknown error" ,
123+ } ;
124+ }
125+ }
126+
127+ /**
128+ * Start the OAuth flow from the signup page.
129+ */
130+ public async startSignupFlow ( region : CloudRegion ) : Promise < StartFlowOutput > {
131+ try {
132+ // Cancel any existing flow
133+ this . cancelFlow ( ) ;
134+
135+ const config : OAuthConfig = {
136+ scopes : OAUTH_SCOPES ,
137+ cloudRegion : region ,
149138 } ;
139+
140+ const codeVerifier = this . generateCodeVerifier ( ) ;
141+ const authUrl = this . buildAuthorizeUrl ( region , codeVerifier ) ;
142+ const signupUrl = this . buildSignupUrl ( region , authUrl ) ;
143+
144+ return await this . startFlowWithUrl (
145+ config ,
146+ codeVerifier ,
147+ signupUrl . toString ( ) ,
148+ ) ;
150149 } catch ( error ) {
151150 return {
152151 success : false ,
@@ -443,11 +442,62 @@ export class OAuthService {
443442 return response . json ( ) ;
444443 }
445444
445+ private buildAuthorizeUrl ( region : CloudRegion , codeVerifier : string ) : URL {
446+ const codeChallenge = this . generateCodeChallenge ( codeVerifier ) ;
447+ const redirectUri = this . getRedirectUri ( ) ;
448+ const cloudUrl = getCloudUrlFromRegion ( region ) ;
449+ const authUrl = new URL ( `${ cloudUrl } /oauth/authorize` ) ;
450+ authUrl . searchParams . set ( "client_id" , getOauthClientIdFromRegion ( region ) ) ;
451+ authUrl . searchParams . set ( "redirect_uri" , redirectUri ) ;
452+ authUrl . searchParams . set ( "response_type" , "code" ) ;
453+ authUrl . searchParams . set ( "code_challenge" , codeChallenge ) ;
454+ authUrl . searchParams . set ( "code_challenge_method" , "S256" ) ;
455+ authUrl . searchParams . set ( "scope" , OAUTH_SCOPES . join ( " " ) ) ;
456+ authUrl . searchParams . set ( "required_access_level" , "project" ) ;
457+ return authUrl ;
458+ }
459+
460+ private buildSignupUrl ( region : CloudRegion , authUrl : URL ) : URL {
461+ const cloudUrl = getCloudUrlFromRegion ( region ) ;
462+ const signupUrl = new URL ( `${ cloudUrl } /signup` ) ;
463+ const nextPath = `${ authUrl . pathname } ${ authUrl . search } ` ;
464+ signupUrl . searchParams . set ( "next" , nextPath ) ;
465+ return signupUrl ;
466+ }
467+
468+ private async startFlowWithUrl (
469+ config : OAuthConfig ,
470+ codeVerifier : string ,
471+ authUrl : string ,
472+ ) : Promise < StartFlowOutput > {
473+ const code = IS_DEV
474+ ? await this . waitForHttpCallback ( codeVerifier , config , authUrl )
475+ : await this . waitForDeepLinkCallback ( codeVerifier , config , authUrl ) ;
476+
477+ const tokenResponse = await this . exchangeCodeForToken (
478+ code ,
479+ codeVerifier ,
480+ config ,
481+ ) ;
482+
483+ return {
484+ success : true ,
485+ data : tokenResponse ,
486+ } ;
487+ }
488+
446489 private generateCodeVerifier ( ) : string {
447490 return crypto . randomBytes ( 32 ) . toString ( "base64url" ) ;
448491 }
449492
450493 private generateCodeChallenge ( verifier : string ) : string {
451494 return crypto . createHash ( "sha256" ) . update ( verifier ) . digest ( "base64url" ) ;
452495 }
496+
497+ /**
498+ * Open an external URL in the default browser.
499+ */
500+ public async openExternalUrl ( url : string ) : Promise < void > {
501+ await shell . openExternal ( url ) ;
502+ }
453503}
0 commit comments