@@ -100,7 +100,7 @@ export class AzureActiveDirectoryService {
100
100
private _tokens : IToken [ ] = [ ] ;
101
101
private _refreshTimeouts : Map < string , NodeJS . Timeout > = new Map < string , NodeJS . Timeout > ( ) ;
102
102
private _uriHandler : UriEventHandler ;
103
- private _disposables : vscode . Disposable [ ] = [ ] ;
103
+ private _disposable : vscode . Disposable ;
104
104
105
105
// Used to keep track of current requests when not using the local server approach.
106
106
private _pendingStates = new Map < string , string [ ] > ( ) ;
@@ -112,51 +112,59 @@ export class AzureActiveDirectoryService {
112
112
constructor ( private _context : vscode . ExtensionContext ) {
113
113
this . _keychain = new Keychain ( _context ) ;
114
114
this . _uriHandler = new UriEventHandler ( ) ;
115
- this . _disposables . push ( vscode . window . registerUriHandler ( this . _uriHandler ) ) ;
115
+ this . _disposable = vscode . Disposable . from (
116
+ vscode . window . registerUriHandler ( this . _uriHandler ) ,
117
+ this . _context . secrets . onDidChange ( ( ) => this . checkForUpdates ( ) ) ) ;
116
118
}
117
119
118
120
public async initialize ( ) : Promise < void > {
119
- const storedData = await this . _keychain . getToken ( ) || await this . _keychain . tryMigrate ( ) ;
120
- if ( storedData ) {
121
- try {
122
- const sessions = this . parseStoredData ( storedData ) ;
123
- const refreshes = sessions . map ( async session => {
124
- if ( ! session . refreshToken ) {
125
- return Promise . resolve ( ) ;
126
- }
121
+ Logger . info ( 'Reading sessions from keychain...' ) ;
122
+ const storedData = await this . _keychain . getToken ( ) ;
123
+ if ( ! storedData ) {
124
+ Logger . info ( 'No stored sessions found.' ) ;
125
+ return ;
126
+ }
127
+ Logger . info ( 'Got stored sessions!' ) ;
127
128
128
- try {
129
- await this . refreshToken ( session . refreshToken , session . scope , session . id ) ;
130
- } catch ( e ) {
131
- if ( e . message === REFRESH_NETWORK_FAILURE ) {
132
- const didSucceedOnRetry = await this . handleRefreshNetworkError ( session . id , session . refreshToken , session . scope ) ;
133
- if ( ! didSucceedOnRetry ) {
134
- this . _tokens . push ( {
135
- accessToken : undefined ,
136
- refreshToken : session . refreshToken ,
137
- account : {
138
- label : session . account . label ?? session . account . displayName ! ,
139
- id : session . account . id
140
- } ,
141
- scope : session . scope ,
142
- sessionId : session . id
143
- } ) ;
144
- this . pollForReconnect ( session . id , session . refreshToken , session . scope ) ;
145
- }
146
- } else {
147
- await this . removeSession ( session . id ) ;
129
+ try {
130
+ const sessions = this . parseStoredData ( storedData ) ;
131
+ const refreshes = sessions . map ( async session => {
132
+ Logger . trace ( `Read the following session from the keychain with the following scopes: ${ session . scope } ` ) ;
133
+ if ( ! session . refreshToken ) {
134
+ Logger . trace ( `Session with the following scopes does not have a refresh token so we will not try to refresh it: ${ session . scope } ` ) ;
135
+ return Promise . resolve ( ) ;
136
+ }
137
+
138
+ try {
139
+ await this . refreshToken ( session . refreshToken , session . scope , session . id ) ;
140
+ } catch ( e ) {
141
+ // If we aren't connected to the internet, then wait and try to refresh again later.
142
+ if ( e . message === REFRESH_NETWORK_FAILURE ) {
143
+ const didSucceedOnRetry = await this . handleRefreshNetworkError ( session . id , session . refreshToken , session . scope ) ;
144
+ if ( ! didSucceedOnRetry ) {
145
+ this . _tokens . push ( {
146
+ accessToken : undefined ,
147
+ refreshToken : session . refreshToken ,
148
+ account : {
149
+ label : session . account . label ?? session . account . displayName ! ,
150
+ id : session . account . id
151
+ } ,
152
+ scope : session . scope ,
153
+ sessionId : session . id
154
+ } ) ;
155
+ this . pollForReconnect ( session . id , session . refreshToken , session . scope ) ;
148
156
}
157
+ } else {
158
+ await this . removeSession ( session . id ) ;
149
159
}
150
- } ) ;
160
+ }
161
+ } ) ;
151
162
152
- await Promise . all ( refreshes ) ;
153
- } catch ( e ) {
154
- Logger . info ( 'Failed to initialize stored data' ) ;
155
- await this . clearSessions ( ) ;
156
- }
163
+ await Promise . all ( refreshes ) ;
164
+ } catch ( e ) {
165
+ Logger . error ( `Failed to initialize stored data: ${ e } ` ) ;
166
+ await this . clearSessions ( ) ;
157
167
}
158
-
159
- this . _disposables . push ( this . _context . secrets . onDidChange ( ( ) => this . checkForUpdates ) ) ;
160
168
}
161
169
162
170
private parseStoredData ( data : string ) : IStoredSession [ ] {
@@ -263,16 +271,16 @@ export class AzureActiveDirectoryService {
263
271
private async resolveAccessAndIdTokens ( token : IToken ) : Promise < IMicrosoftTokens > {
264
272
if ( token . accessToken && ( ! token . expiresAt || token . expiresAt > Date . now ( ) ) ) {
265
273
token . expiresAt
266
- ? Logger . info ( `Token available from cache, expires in ${ token . expiresAt - Date . now ( ) } milliseconds` )
267
- : Logger . info ( 'Token available from cache' ) ;
274
+ ? Logger . info ( `Token available from cache (for scopes ${ token . scope } ) , expires in ${ token . expiresAt - Date . now ( ) } milliseconds` )
275
+ : Logger . info ( 'Token available from cache (for scopes ${token.scope}) ' ) ;
268
276
return Promise . resolve ( {
269
277
accessToken : token . accessToken ,
270
278
idToken : token . idToken
271
279
} ) ;
272
280
}
273
281
274
282
try {
275
- Logger . info ( ' Token expired or unavailable, trying refresh' ) ;
283
+ Logger . info ( ` Token expired or unavailable (for scopes ${ token . scope } ) , trying refresh` ) ;
276
284
const refreshedToken = await this . refreshToken ( token . refreshToken , token . scope , token . sessionId ) ;
277
285
if ( refreshedToken . accessToken ) {
278
286
return {
@@ -301,17 +309,21 @@ export class AzureActiveDirectoryService {
301
309
}
302
310
303
311
async getSessions ( scopes ?: string [ ] ) : Promise < vscode . AuthenticationSession [ ] > {
312
+ Logger . info ( `Getting sessions for ${ scopes ?. join ( ',' ) ?? 'all scopes' } ...` ) ;
304
313
if ( ! scopes ) {
305
- return this . sessions ;
314
+ const sessions = await this . sessions ;
315
+ Logger . info ( `Got ${ sessions . length } sessions for all scopes...` ) ;
316
+ return sessions ;
306
317
}
307
318
308
319
const orderedScopes = scopes . sort ( ) . join ( ' ' ) ;
309
320
const matchingTokens = this . _tokens . filter ( token => token . scope === orderedScopes ) ;
321
+ Logger . info ( `Got ${ matchingTokens . length } sessions for ${ scopes ?. join ( ',' ) } ...` ) ;
310
322
return Promise . all ( matchingTokens . map ( token => this . convertToSession ( token ) ) ) ;
311
323
}
312
324
313
325
public async createSession ( scope : string ) : Promise < vscode . AuthenticationSession > {
314
- Logger . info ( ' Logging in...' ) ;
326
+ Logger . info ( ` Logging in for the following scopes: ${ scope } ` ) ;
315
327
if ( ! scope . includes ( 'offline_access' ) ) {
316
328
Logger . info ( 'Warning: The \'offline_access\' scope was not included, so the generated token will not be able to be refreshed.' ) ;
317
329
}
@@ -360,7 +372,7 @@ export class AzureActiveDirectoryService {
360
372
}
361
373
token = await this . exchangeCodeForToken ( codeRes . code , codeVerifier , scope ) ;
362
374
this . setToken ( token , scope ) ;
363
- Logger . info ( ' Login successful' ) ;
375
+ Logger . info ( ` Login successful for scopes: ${ scope } ` ) ;
364
376
res . writeHead ( 302 , { Location : '/' } ) ;
365
377
const session = await this . convertToSession ( token ) ;
366
378
return session ;
@@ -371,7 +383,7 @@ export class AzureActiveDirectoryService {
371
383
res . end ( ) ;
372
384
}
373
385
} catch ( e ) {
374
- Logger . error ( e . message ) ;
386
+ Logger . error ( `Error creating session for scopes: ${ scope } Error: ${ e } ` ) ;
375
387
376
388
// If the error was about starting the server, try directly hitting the login endpoint instead
377
389
if ( e . message === 'Error listening to server' || e . message === 'Closed' || e . message === 'Timeout waiting for port' ) {
@@ -387,8 +399,7 @@ export class AzureActiveDirectoryService {
387
399
}
388
400
389
401
public dispose ( ) : void {
390
- this . _disposables . forEach ( disposable => disposable . dispose ( ) ) ;
391
- this . _disposables = [ ] ;
402
+ this . _disposable . dispose ( ) ;
392
403
}
393
404
394
405
private getCallbackEnvironment ( callbackUri : vscode . Uri ) : string {
@@ -550,7 +561,7 @@ export class AzureActiveDirectoryService {
550
561
}
551
562
552
563
private async exchangeCodeForToken ( code : string , codeVerifier : string , scope : string ) : Promise < IToken > {
553
- Logger . info ( ' Exchanging login code for token' ) ;
564
+ Logger . info ( ` Exchanging login code for token for scopes: ${ scope } ` ) ;
554
565
try {
555
566
const postData = querystring . stringify ( {
556
567
grant_type : 'authorization_code' ,
@@ -575,21 +586,21 @@ export class AzureActiveDirectoryService {
575
586
} ) ;
576
587
577
588
if ( result . ok ) {
578
- Logger . info ( 'Exchanging login code for token success' ) ;
579
589
const json = await result . json ( ) ;
590
+ Logger . info ( `Exchanging login code for token (for scopes: ${ scope } ) succeeded!` ) ;
580
591
return this . getTokenFromResponse ( json , scope ) ;
581
592
} else {
582
- Logger . error ( ' Exchanging login code for token failed' ) ;
593
+ Logger . error ( ` Exchanging login code for token (for scopes: ${ scope } ) failed: ${ await result . text ( ) } ` ) ;
583
594
throw new Error ( 'Unable to login.' ) ;
584
595
}
585
596
} catch ( e ) {
586
- Logger . error ( e . message ) ;
597
+ Logger . error ( `Error exchanging code for token (for scopes ${ scope } ): ${ e } ` ) ;
587
598
throw e ;
588
599
}
589
600
}
590
601
591
602
private async refreshToken ( refreshToken : string , scope : string , sessionId : string ) : Promise < IToken > {
592
- Logger . info ( ' Refreshing token...' ) ;
603
+ Logger . info ( ` Refreshing token for scopes: ${ scope } ` ) ;
593
604
const postData = querystring . stringify ( {
594
605
refresh_token : refreshToken ,
595
606
client_id : clientId ,
@@ -611,7 +622,7 @@ export class AzureActiveDirectoryService {
611
622
body : postData
612
623
} ) ;
613
624
} catch ( e ) {
614
- Logger . error ( ' Refreshing token failed' ) ;
625
+ Logger . error ( ` Refreshing token failed (for scopes: ${ scope } ) Error: ${ e } ` ) ;
615
626
throw new Error ( REFRESH_NETWORK_FAILURE ) ;
616
627
}
617
628
@@ -620,14 +631,14 @@ export class AzureActiveDirectoryService {
620
631
const json = await result . json ( ) ;
621
632
const token = this . getTokenFromResponse ( json , scope , sessionId ) ;
622
633
this . setToken ( token , scope ) ;
623
- Logger . info ( ' Token refresh success' ) ;
634
+ Logger . info ( ` Token refresh success for scopes: ${ token . scope } ` ) ;
624
635
return token ;
625
636
} else {
626
637
throw new Error ( 'Bad request.' ) ;
627
638
}
628
639
} catch ( e ) {
629
640
vscode . window . showErrorMessage ( localize ( 'signOut' , "You have been signed out because reading stored authentication information failed." ) ) ;
630
- Logger . error ( `Refreshing token failed: ${ result . statusText } ` ) ;
641
+ Logger . error ( `Refreshing token failed (for scopes: ${ scope } ) : ${ result . statusText } ` ) ;
631
642
throw new Error ( 'Refreshing token failed' ) ;
632
643
}
633
644
}
@@ -668,7 +679,7 @@ export class AzureActiveDirectoryService {
668
679
private handleRefreshNetworkError ( sessionId : string , refreshToken : string , scope : string , attempts : number = 1 ) : Promise < boolean > {
669
680
return new Promise ( ( resolve , _ ) => {
670
681
if ( attempts === 3 ) {
671
- Logger . error ( ' Token refresh failed after 3 attempts' ) ;
682
+ Logger . error ( ` Token refresh (for scopes: ${ scope } ) failed after 3 attempts` ) ;
672
683
return resolve ( false ) ;
673
684
}
674
685
0 commit comments