@@ -62,6 +62,8 @@ import {
62
62
import { isSageMaker , isCloud9 , isAmazonQ } from '../shared/extensionUtilities'
63
63
import { telemetry } from '../shared/telemetry/telemetry'
64
64
import { randomUUID } from '../shared/crypto'
65
+ import { asStringifiedStack } from '../shared/telemetry/spans'
66
+ import { withTelemetryContext } from '../shared/telemetry/util'
65
67
66
68
interface AuthService {
67
69
/**
@@ -124,6 +126,9 @@ export type AuthType = Auth
124
126
export type DeletedConnection = { connId : Connection [ 'id' ] ; storedProfile ?: StoredProfile }
125
127
type DeclaredConnection = Pick < SsoProfile , 'ssoRegion' | 'startUrl' > & { source : string }
126
128
129
+ // Must be declared outside of class for use by decorator
130
+ const authClassName = 'Auth'
131
+
127
132
export class Auth implements AuthService , ConnectionManager {
128
133
readonly #ssoCache = getCache ( )
129
134
readonly #validationErrors = new Map < Connection [ 'id' ] , Error > ( )
@@ -162,6 +167,7 @@ export class Auth implements AuthService, ConnectionManager {
162
167
return Object . values ( this . _declaredConnections )
163
168
}
164
169
170
+ @withTelemetryContext ( { name : 'restorePreviousSession' , class : authClassName } )
165
171
public async restorePreviousSession ( ) : Promise < Connection | undefined > {
166
172
const id = this . store . getCurrentProfileId ( )
167
173
if ( id === undefined ) {
@@ -177,6 +183,7 @@ export class Auth implements AuthService, ConnectionManager {
177
183
178
184
public async reauthenticate ( { id } : Pick < SsoConnection , 'id' > , invalidate ?: boolean ) : Promise < SsoConnection >
179
185
public async reauthenticate ( { id } : Pick < IamConnection , 'id' > , invalidate ?: boolean ) : Promise < IamConnection >
186
+ @withTelemetryContext ( { name : 'reauthenticate' , class : authClassName } )
180
187
public async reauthenticate ( { id } : Pick < Connection , 'id' > , invalidate ?: boolean ) : Promise < Connection > {
181
188
const shouldInvalidate = invalidate ?? true
182
189
const profile = this . store . getProfileOrThrow ( id )
@@ -195,6 +202,7 @@ export class Auth implements AuthService, ConnectionManager {
195
202
196
203
public async useConnection ( { id } : Pick < SsoConnection , 'id' > ) : Promise < SsoConnection >
197
204
public async useConnection ( { id } : Pick < IamConnection , 'id' > ) : Promise < IamConnection >
205
+ @withTelemetryContext ( { name : 'useConnection' , class : authClassName } )
198
206
public async useConnection ( { id } : Pick < Connection , 'id' > ) : Promise < Connection > {
199
207
await this . refreshConnectionState ( { id } )
200
208
@@ -211,6 +219,7 @@ export class Auth implements AuthService, ConnectionManager {
211
219
return conn
212
220
}
213
221
222
+ @withTelemetryContext ( { name : 'logout' , class : authClassName } )
214
223
public async logout ( ) : Promise < void > {
215
224
if ( this . activeConnection === undefined ) {
216
225
return
@@ -222,6 +231,7 @@ export class Auth implements AuthService, ConnectionManager {
222
231
this . #onDidChangeActiveConnection. fire ( undefined )
223
232
}
224
233
234
+ @withTelemetryContext ( { name : 'listConnections' , class : authClassName } )
225
235
public async listConnections ( ) : Promise < Connection [ ] > {
226
236
await loadIamProfilesIntoStore ( this . store , this . iamProfileProvider )
227
237
@@ -281,6 +291,7 @@ export class Auth implements AuthService, ConnectionManager {
281
291
}
282
292
283
293
public async createConnection ( profile : SsoProfile ) : Promise < SsoConnection >
294
+ @withTelemetryContext ( { name : 'createConnection' , class : authClassName } )
284
295
public async createConnection ( profile : Profile ) : Promise < Connection > {
285
296
if ( profile . type === 'iam' ) {
286
297
throw new Error ( 'Creating IAM connections is not supported' )
@@ -311,6 +322,7 @@ export class Auth implements AuthService, ConnectionManager {
311
322
}
312
323
}
313
324
325
+ @withTelemetryContext ( { name : 'deleteConnection' , class : authClassName } )
314
326
public async deleteConnection ( connection : Pick < Connection , 'id' > ) : Promise < void > {
315
327
const connId = connection . id
316
328
const profile = this . store . getProfile ( connId )
@@ -343,6 +355,7 @@ export class Auth implements AuthService, ConnectionManager {
343
355
*
344
356
* Forget about a connection without logging out, invalidating it, or deleting it from disk.
345
357
*/
358
+ @withTelemetryContext ( { name : 'forgetConnection' , class : authClassName } )
346
359
public async forgetConnection ( connection : Pick < Connection , 'id' > ) : Promise < void > {
347
360
const connId = connection . id
348
361
const profile = this . store . getProfile ( connId )
@@ -355,6 +368,7 @@ export class Auth implements AuthService, ConnectionManager {
355
368
this . #onDidDeleteConnection. fire ( { connId, storedProfile : profile } )
356
369
}
357
370
371
+ @withTelemetryContext ( { name : 'clearStaleLinkedIamConnections' , class : authClassName } )
358
372
private async clearStaleLinkedIamConnections ( ) {
359
373
// Updates our store, evicting stale IAM credential profiles if the
360
374
// SSO they are linked to was removed.
@@ -373,6 +387,7 @@ export class Auth implements AuthService, ConnectionManager {
373
387
*
374
388
* Put the SSO connection in to an expired state
375
389
*/
390
+ @withTelemetryContext ( { name : 'expireConnection' , class : authClassName } )
376
391
public async expireConnection ( conn : Pick < SsoConnection , 'id' > ) : Promise < void > {
377
392
getLogger ( ) . info ( `auth: Expiring connection ${ conn . id } ` )
378
393
const profile = this . store . getProfileOrThrow ( conn . id )
@@ -398,6 +413,7 @@ export class Auth implements AuthService, ConnectionManager {
398
413
* Alternatively you can use the `getToken()` call on an SSO connection to do the same thing,
399
414
* but it will additionally prompt for reauthentication if the connection is invalid.
400
415
*/
416
+ @withTelemetryContext ( { name : 'refreshConnectionState' , class : authClassName } )
401
417
public async refreshConnectionState ( connection ?: Pick < Connection , 'id' > ) : Promise < undefined > {
402
418
if ( connection === undefined ) {
403
419
return
@@ -416,6 +432,7 @@ export class Auth implements AuthService, ConnectionManager {
416
432
profile : SsoProfile ,
417
433
invalidate ?: boolean
418
434
) : Promise < SsoConnection >
435
+ @withTelemetryContext ( { name : 'updateConnection' , class : authClassName } )
419
436
public async updateConnection (
420
437
connection : Pick < Connection , 'id' > ,
421
438
profile : Profile ,
@@ -458,6 +475,7 @@ export class Auth implements AuthService, ConnectionManager {
458
475
*
459
476
* @returns undefined if authentication succeeds, otherwise object with error info
460
477
*/
478
+ @withTelemetryContext ( { name : 'authenticateData' , class : authClassName } )
461
479
public async authenticateData ( data : StaticProfile ) : Promise < StaticProfileKeyErrorMessage | undefined > {
462
480
const tempId = await this . addTempCredential ( data )
463
481
const tempIdString = asString ( tempId )
@@ -507,6 +525,7 @@ export class Auth implements AuthService, ConnectionManager {
507
525
* For SSO, this involves an API call to clear server-side state. The call happens
508
526
* before the local token(s) are cleared as they are needed in the request.
509
527
*/
528
+ @withTelemetryContext ( { name : 'invalidateConnection' , class : authClassName } )
510
529
private async invalidateConnection ( id : Connection [ 'id' ] , opt ?: { skipGlobalLogout ?: boolean } ) {
511
530
getLogger ( ) . info ( `auth: Invalidating connection: ${ id } ` )
512
531
const profile = this . store . getProfileOrThrow ( id )
@@ -534,6 +553,7 @@ export class Auth implements AuthService, ConnectionManager {
534
553
await this . updateConnectionState ( id , 'invalid' )
535
554
}
536
555
556
+ @withTelemetryContext ( { name : 'updateConnectionState' , class : authClassName } )
537
557
private async updateConnectionState ( id : Connection [ 'id' ] , connectionState : ProfileMetadata [ 'connectionState' ] ) {
538
558
getLogger ( ) . info ( `auth: Updating connection state of ${ id } to ${ connectionState } ` )
539
559
@@ -544,24 +564,44 @@ export class Auth implements AuthService, ConnectionManager {
544
564
545
565
const oldProfile = this . store . getProfileOrThrow ( id )
546
566
if ( oldProfile . metadata . connectionState === connectionState ) {
567
+ // new state is same as old state, no need to do anything
547
568
return oldProfile
548
569
}
549
570
550
- const profile = await this . store . updateMetadata ( id , { connectionState } )
551
- if ( connectionState !== 'invalid' ) {
552
- this . #validationErrors. delete ( id )
553
- this . #invalidCredentialsTimeouts. get ( id ) ?. dispose ( )
554
- }
571
+ // Emit an event for when the connection changes state. This the the root of the
572
+ // state change. We rely on functions higher in the stack to have added their contexts
573
+ // so this event has useful information in the `source` field.
574
+ return telemetry . auth_modifyConnection . run ( async ( span ) => {
575
+ // if we have an Sso session that became invalid, we will add its session duration to the event
576
+ const ssoSessionDuration =
577
+ connectionState === 'invalid' && oldProfile . type === 'sso'
578
+ ? this . getSsoTokenProvider ( id , oldProfile ) . getSessionDuration ( )
579
+ : undefined
580
+ span . record ( {
581
+ action : 'updateConnectionState' ,
582
+ connectionState,
583
+ source : asStringifiedStack ( telemetry . getFunctionStack ( ) ) ,
584
+ credentialStartUrl : oldProfile . type === 'sso' ? oldProfile . startUrl : undefined ,
585
+ sessionDuration : ssoSessionDuration ,
586
+ } )
555
587
556
- if ( this . #activeConnection ?. id === id ) {
557
- this . #activeConnection . state = connectionState
558
- this . #onDidChangeActiveConnection . fire ( this . #activeConnection )
559
- }
560
- this . #onDidChangeConnectionState . fire ( { id , state : connectionState } )
588
+ const profile = await this . store . updateMetadata ( id , { connectionState } )
589
+ if ( connectionState !== 'invalid' ) {
590
+ this . #validationErrors . delete ( id )
591
+ this . #invalidCredentialsTimeouts . get ( id ) ?. dispose ( )
592
+ }
561
593
562
- return profile
594
+ if ( this . #activeConnection?. id === id ) {
595
+ this . #activeConnection. state = connectionState
596
+ this . #onDidChangeActiveConnection. fire ( this . #activeConnection)
597
+ }
598
+ this . #onDidChangeConnectionState. fire ( { id, state : connectionState } )
599
+
600
+ return profile
601
+ } )
563
602
}
564
603
604
+ @withTelemetryContext ( { name : 'validateConnection' , class : authClassName } )
565
605
private async validateConnection < T extends Profile > ( id : Connection [ 'id' ] , profile : StoredProfile < T > ) {
566
606
const runCheck = async ( ) => {
567
607
if ( profile . type === 'sso' ) {
@@ -747,6 +787,7 @@ export class Auth implements AuthService, ConnectionManager {
747
787
}
748
788
749
789
private readonly authenticate = keyedDebounce ( this . _authenticate . bind ( this ) )
790
+ @withTelemetryContext ( { name : 'authenticate' , class : authClassName } )
750
791
private async _authenticate < T > ( id : Connection [ 'id' ] , callback : ( ) => Promise < T > , invalidate ?: boolean ) : Promise < T > {
751
792
const originalState = this . getConnectionState ( { id } ) ?? 'unauthenticated'
752
793
await this . updateConnectionState ( id , 'authenticating' )
@@ -780,6 +821,7 @@ export class Auth implements AuthService, ConnectionManager {
780
821
}
781
822
782
823
private readonly getToken = keyedDebounce ( this . _getToken . bind ( this ) )
824
+ @withTelemetryContext ( { name : '_getToken' , class : authClassName } )
783
825
private async _getToken ( id : Connection [ 'id' ] , provider : SsoAccessTokenProvider ) : Promise < SsoToken > {
784
826
const token = await provider . getToken ( ) . catch ( ( err ) => {
785
827
this . throwOnNetworkError ( err )
@@ -817,6 +859,7 @@ export class Auth implements AuthService, ConnectionManager {
817
859
}
818
860
}
819
861
862
+ @withTelemetryContext ( { name : 'handleInvalidCredentials' , class : authClassName } )
820
863
private async handleInvalidCredentials < T > ( id : Connection [ 'id' ] , refresh : ( ) => Promise < T > ) : Promise < T > {
821
864
getLogger ( ) . info ( `auth: Handling invalid credentials of connection: ${ id } ` )
822
865
const profile = this . store . getProfile ( id )
@@ -861,7 +904,9 @@ export class Auth implements AuthService, ConnectionManager {
861
904
return this . authenticate ( id , refresh )
862
905
}
863
906
864
- public readonly tryAutoConnect = once ( async ( ) => {
907
+ public readonly tryAutoConnect = once ( async ( ) => this . _tryAutoConnect ( ) )
908
+ @withTelemetryContext ( { name : 'tryAutoConnect' , class : authClassName } )
909
+ private async _tryAutoConnect ( ) {
865
910
if ( this . activeConnection !== undefined ) {
866
911
return
867
912
}
@@ -940,7 +985,7 @@ export class Auth implements AuthService, ConnectionManager {
940
985
}
941
986
}
942
987
}
943
- } )
988
+ }
944
989
945
990
static #instance: Auth | undefined
946
991
public static get instance ( ) {
0 commit comments