@@ -11,7 +11,7 @@ import * as nls from 'vscode-nls';
1111import { v4 as uuid } from 'uuid' ;
1212import fetch , { Response } from 'node-fetch' ;
1313import Logger from './logger' ;
14- import { toBase64UrlEncoding } from './utils' ;
14+ import { isSupportedEnvironment , toBase64UrlEncoding } from './utils' ;
1515import { sha256 } from './env/node/sha256' ;
1616import { BetterTokenStorage , IDidChangeInOtherWindowEvent } from './betterSecretStorage' ;
1717import { LoopbackAuthServer } from './authServer' ;
@@ -319,13 +319,7 @@ export class AzureActiveDirectoryService {
319319 } , 5000 ) ;
320320 }
321321
322- const token = await this . exchangeCodeForToken ( codeToExchange , codeVerifier , scopeData ) ;
323- if ( token . expiresIn ) {
324- this . setSessionTimeout ( token . sessionId , token . refreshToken , scopeData , token . expiresIn * AzureActiveDirectoryService . REFRESH_TIMEOUT_MODIFIER ) ;
325- }
326- await this . setToken ( token , scopeData ) ;
327- Logger . info ( `Login successful for scopes: ${ scopeData . scopeStr } ` ) ;
328- const session = await this . convertToSession ( token ) ;
322+ const session = await this . exchangeCodeForSession ( codeToExchange , codeVerifier , scopeData ) ;
329323 return session ;
330324 }
331325
@@ -355,9 +349,11 @@ export class AzureActiveDirectoryService {
355349 const uri = vscode . Uri . parse ( `${ signInUrl } ?${ oauthStartQuery . toString ( ) } ` ) ;
356350 vscode . env . openExternal ( uri ) ;
357351
352+ let inputBox : vscode . InputBox | undefined ;
358353 const timeoutPromise = new Promise ( ( _ : ( value : vscode . AuthenticationSession ) => void , reject ) => {
359354 const wait = setTimeout ( ( ) => {
360355 clearTimeout ( wait ) ;
356+ inputBox ?. dispose ( ) ;
361357 reject ( 'Login timed out.' ) ;
362358 } , 1000 * 60 * 5 ) ;
363359 } ) ;
@@ -369,7 +365,12 @@ export class AzureActiveDirectoryService {
369365 // before completing it.
370366 let existingPromise = this . _codeExchangePromises . get ( scopeData . scopeStr ) ;
371367 if ( ! existingPromise ) {
372- existingPromise = this . handleCodeResponse ( scopeData ) ;
368+ if ( isSupportedEnvironment ( callbackUri ) ) {
369+ existingPromise = this . handleCodeResponse ( scopeData ) ;
370+ } else {
371+ inputBox = vscode . window . createInputBox ( ) ;
372+ existingPromise = Promise . race ( [ this . handleCodeInputBox ( inputBox , codeVerifier , scopeData ) , this . handleCodeResponse ( scopeData ) ] ) ;
373+ }
373374 this . _codeExchangePromises . set ( scopeData . scopeStr , existingPromise ) ;
374375 }
375376
@@ -659,13 +660,7 @@ export class AzureActiveDirectoryService {
659660 throw new Error ( 'No available code verifier' ) ;
660661 }
661662
662- const token = await this . exchangeCodeForToken ( code , verifier , scopeData ) ;
663- if ( token . expiresIn ) {
664- this . setSessionTimeout ( token . sessionId , token . refreshToken , scopeData , token . expiresIn * AzureActiveDirectoryService . REFRESH_TIMEOUT_MODIFIER ) ;
665- }
666- await this . setToken ( token , scopeData ) ;
667-
668- const session = await this . convertToSession ( token ) ;
663+ const session = await this . exchangeCodeForSession ( code , verifier , scopeData ) ;
669664 resolve ( session ) ;
670665 } catch ( err ) {
671666 reject ( err ) ;
@@ -680,8 +675,33 @@ export class AzureActiveDirectoryService {
680675 } ) ;
681676 }
682677
683- private async exchangeCodeForToken ( code : string , codeVerifier : string , scopeData : IScopeData ) : Promise < IToken > {
678+ private async handleCodeInputBox ( inputBox : vscode . InputBox , verifier : string , scopeData : IScopeData ) : Promise < vscode . AuthenticationSession > {
679+ inputBox . ignoreFocusOut = true ;
680+ inputBox . title = localize ( 'pasteCodeTitle' , 'Microsoft Authentication' ) ;
681+ inputBox . prompt = localize ( 'pasteCodePrompt' , 'Provide the authorization code to complete the sign in flow.' ) ;
682+ inputBox . placeholder = localize ( 'pasteCodePlaceholder' , 'Paste authorization code here...' ) ;
683+ return new Promise ( ( resolve : ( value : vscode . AuthenticationSession ) => void , reject ) => {
684+ inputBox . show ( ) ;
685+ inputBox . onDidAccept ( async ( ) => {
686+ const code = inputBox . value ;
687+ if ( code ) {
688+ inputBox . dispose ( ) ;
689+ const session = await this . exchangeCodeForSession ( code , verifier , scopeData ) ;
690+ resolve ( session ) ;
691+ }
692+ } ) ;
693+ inputBox . onDidHide ( ( ) => {
694+ if ( ! inputBox . value ) {
695+ inputBox . dispose ( ) ;
696+ reject ( 'Cancelled' ) ;
697+ }
698+ } ) ;
699+ } ) ;
700+ }
701+
702+ private async exchangeCodeForSession ( code : string , codeVerifier : string , scopeData : IScopeData ) : Promise < vscode . AuthenticationSession > {
684703 Logger . info ( `Exchanging login code for token for scopes: ${ scopeData . scopeStr } ` ) ;
704+ let token : IToken | undefined ;
685705 try {
686706 const postData = querystring . stringify ( {
687707 grant_type : 'authorization_code' ,
@@ -698,11 +718,18 @@ export class AzureActiveDirectoryService {
698718
699719 const json = await this . fetchTokenResponse ( endpoint , postData , scopeData ) ;
700720 Logger . info ( `Exchanging login code for token (for scopes: ${ scopeData . scopeStr } ) succeeded!` ) ;
701- return this . convertToTokenSync ( json , scopeData ) ;
721+ token = this . convertToTokenSync ( json , scopeData ) ;
702722 } catch ( e ) {
703723 Logger . error ( `Error exchanging code for token (for scopes ${ scopeData . scopeStr } ): ${ e } ` ) ;
704724 throw e ;
705725 }
726+
727+ if ( token . expiresIn ) {
728+ this . setSessionTimeout ( token . sessionId , token . refreshToken , scopeData , token . expiresIn * AzureActiveDirectoryService . REFRESH_TIMEOUT_MODIFIER ) ;
729+ }
730+ await this . setToken ( token , scopeData ) ;
731+ Logger . info ( `Login successful for scopes: ${ scopeData . scopeStr } ` ) ;
732+ return await this . convertToSession ( token ) ;
706733 }
707734
708735 private async fetchTokenResponse ( endpoint : string , postData : string , scopeData : IScopeData ) : Promise < ITokenResponse > {
0 commit comments