@@ -11,7 +11,7 @@ import * as nls from 'vscode-nls';
11
11
import { v4 as uuid } from 'uuid' ;
12
12
import fetch , { Response } from 'node-fetch' ;
13
13
import Logger from './logger' ;
14
- import { toBase64UrlEncoding } from './utils' ;
14
+ import { isSupportedEnvironment , toBase64UrlEncoding } from './utils' ;
15
15
import { sha256 } from './env/node/sha256' ;
16
16
import { BetterTokenStorage , IDidChangeInOtherWindowEvent } from './betterSecretStorage' ;
17
17
import { LoopbackAuthServer } from './authServer' ;
@@ -319,13 +319,7 @@ export class AzureActiveDirectoryService {
319
319
} , 5000 ) ;
320
320
}
321
321
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 ) ;
329
323
return session ;
330
324
}
331
325
@@ -355,9 +349,11 @@ export class AzureActiveDirectoryService {
355
349
const uri = vscode . Uri . parse ( `${ signInUrl } ?${ oauthStartQuery . toString ( ) } ` ) ;
356
350
vscode . env . openExternal ( uri ) ;
357
351
352
+ let inputBox : vscode . InputBox | undefined ;
358
353
const timeoutPromise = new Promise ( ( _ : ( value : vscode . AuthenticationSession ) => void , reject ) => {
359
354
const wait = setTimeout ( ( ) => {
360
355
clearTimeout ( wait ) ;
356
+ inputBox ?. dispose ( ) ;
361
357
reject ( 'Login timed out.' ) ;
362
358
} , 1000 * 60 * 5 ) ;
363
359
} ) ;
@@ -369,7 +365,12 @@ export class AzureActiveDirectoryService {
369
365
// before completing it.
370
366
let existingPromise = this . _codeExchangePromises . get ( scopeData . scopeStr ) ;
371
367
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
+ }
373
374
this . _codeExchangePromises . set ( scopeData . scopeStr , existingPromise ) ;
374
375
}
375
376
@@ -659,13 +660,7 @@ export class AzureActiveDirectoryService {
659
660
throw new Error ( 'No available code verifier' ) ;
660
661
}
661
662
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 ) ;
669
664
resolve ( session ) ;
670
665
} catch ( err ) {
671
666
reject ( err ) ;
@@ -680,8 +675,33 @@ export class AzureActiveDirectoryService {
680
675
} ) ;
681
676
}
682
677
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 > {
684
703
Logger . info ( `Exchanging login code for token for scopes: ${ scopeData . scopeStr } ` ) ;
704
+ let token : IToken | undefined ;
685
705
try {
686
706
const postData = querystring . stringify ( {
687
707
grant_type : 'authorization_code' ,
@@ -698,11 +718,18 @@ export class AzureActiveDirectoryService {
698
718
699
719
const json = await this . fetchTokenResponse ( endpoint , postData , scopeData ) ;
700
720
Logger . info ( `Exchanging login code for token (for scopes: ${ scopeData . scopeStr } ) succeeded!` ) ;
701
- return this . convertToTokenSync ( json , scopeData ) ;
721
+ token = this . convertToTokenSync ( json , scopeData ) ;
702
722
} catch ( e ) {
703
723
Logger . error ( `Error exchanging code for token (for scopes ${ scopeData . scopeStr } ): ${ e } ` ) ;
704
724
throw e ;
705
725
}
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 ) ;
706
733
}
707
734
708
735
private async fetchTokenResponse ( endpoint : string , postData : string , scopeData : IScopeData ) : Promise < ITokenResponse > {
0 commit comments