@@ -9,15 +9,21 @@ import {
9
9
type InteractiveBrowserCredentialInBrowserOptions ,
10
10
InteractiveBrowserCredential ,
11
11
type InteractiveBrowserCredentialNodeOptions ,
12
- type TokenCredential
12
+ type TokenCredential ,
13
+ ChainedTokenCredential ,
14
+ VisualStudioCodeCredential ,
15
+ AzureCliCredential ,
16
+ AzureDeveloperCliCredential ,
17
+ AzurePowerShellCredential
13
18
} from '@azure/identity' ;
19
+ import type { TokenCredentialOptions } from '@azure/identity' ;
20
+ import { AdoCodespacesAuthCredential } from './AdoCodespacesAuthCredential' ;
14
21
import type { ITerminal } from '@rushstack/terminal' ;
15
22
import { CredentialCache } from '@rushstack/rush-sdk' ;
16
23
// Use a separate import line so the .d.ts file ends up with an `import type { ... }`
17
24
// See https://github.com/microsoft/rushstack/issues/3432
18
25
import type { ICredentialCacheEntry } from '@rushstack/rush-sdk' ;
19
26
import { PrintUtilities } from '@rushstack/terminal' ;
20
- import { AdoCodespacesAuthCredential } from './AdoCodespacesAuthCredential' ;
21
27
22
28
/**
23
29
* @public
@@ -80,7 +86,14 @@ export type AzureEnvironmentName = keyof typeof AzureAuthorityHosts;
80
86
/**
81
87
* @public
82
88
*/
83
- export type LoginFlowType = 'DeviceCode' | 'InteractiveBrowser' | 'AdoCodespacesAuth' ;
89
+ export type LoginFlowType =
90
+ | 'DeviceCode'
91
+ | 'InteractiveBrowser'
92
+ | 'AdoCodespacesAuth'
93
+ | 'VisualStudioCode'
94
+ | 'AzureCli'
95
+ | 'AzureDeveloperCli'
96
+ | 'AzurePowerShell' ;
84
97
85
98
/**
86
99
* @public
@@ -97,13 +110,19 @@ export interface IAzureAuthenticationBaseOptions {
97
110
* @defaultValue
98
111
* ```json
99
112
* {
100
- * "AdoCodespacesAuth": "InteractiveBrowser",
113
+ * "AdoCodespacesAuth": "VisualStudioCode",
114
+ * "VisualStudioCode": "AzureCli",
115
+ * "AzureCli": "AzureDeveloperCli",
116
+ * "AzureDeveloperCli": "AzurePowerShell",
117
+ * "AzurePowerShell": "InteractiveBrowser",
101
118
* "InteractiveBrowser": "DeviceCode",
102
- * "DeviceCode": null
119
+ * "DeviceCode": undefined
103
120
* }
104
121
* ```
105
122
*/
106
- loginFlowFailover ?: Record < LoginFlowType , LoginFlowType | undefined > ;
123
+ loginFlowFailover ?: {
124
+ [ key in LoginFlowType ] ?: LoginFlowType ;
125
+ } ;
107
126
}
108
127
109
128
/**
@@ -128,7 +147,11 @@ export abstract class AzureAuthenticationBase {
128
147
129
148
protected readonly _azureEnvironment : AzureEnvironmentName ;
130
149
protected readonly _loginFlow : LoginFlowType ;
131
- protected readonly _failoverOrder : Record < LoginFlowType , LoginFlowType | undefined > ;
150
+ protected readonly _failoverOrder :
151
+ | {
152
+ [ key in LoginFlowType ] ?: LoginFlowType ;
153
+ }
154
+ | undefined ;
132
155
133
156
private __credentialCacheId : string | undefined ;
134
157
protected get _credentialCacheId ( ) : string {
@@ -148,13 +171,17 @@ export abstract class AzureAuthenticationBase {
148
171
public constructor ( options : IAzureAuthenticationBaseOptions ) {
149
172
const {
150
173
azureEnvironment = 'AzurePublicCloud' ,
151
- loginFlow = process . env . CODESPACES === 'true' ? 'AdoCodespacesAuth' : 'InteractiveBrowser '
174
+ loginFlow = process . env . CODESPACES === 'true' ? 'AdoCodespacesAuth' : 'VisualStudioCode '
152
175
} = options ;
153
176
this . _azureEnvironment = azureEnvironment ;
154
177
this . _credentialUpdateCommandForLogging = options . credentialUpdateCommandForLogging ;
155
178
this . _loginFlow = loginFlow ;
156
179
this . _failoverOrder = options . loginFlowFailover || {
157
- AdoCodespacesAuth : 'InteractiveBrowser' ,
180
+ AdoCodespacesAuth : 'VisualStudioCode' ,
181
+ VisualStudioCode : 'AzureCli' ,
182
+ AzureCli : 'AzureDeveloperCli' ,
183
+ AzureDeveloperCli : 'AzurePowerShell' ,
184
+ AzurePowerShell : 'InteractiveBrowser' ,
158
185
InteractiveBrowser : 'DeviceCode' ,
159
186
DeviceCode : undefined
160
187
} ;
@@ -294,8 +321,6 @@ export abstract class AzureAuthenticationBase {
294
321
throw new Error ( `Unexpected Azure environment: ${ this . _azureEnvironment } ` ) ;
295
322
}
296
323
297
- let tokenCredential : TokenCredential ;
298
-
299
324
const interactiveCredentialOptions : (
300
325
| InteractiveBrowserCredentialNodeOptions
301
326
| InteractiveBrowserCredentialInBrowserOptions
@@ -305,40 +330,59 @@ export abstract class AzureAuthenticationBase {
305
330
authorityHost
306
331
} ;
307
332
308
- switch ( loginFlow ) {
309
- case 'AdoCodespacesAuth' : {
310
- tokenCredential = new AdoCodespacesAuthCredential ( ) ;
311
- break ;
312
- }
313
- case 'InteractiveBrowser' : {
314
- tokenCredential = new InteractiveBrowserCredential ( interactiveCredentialOptions ) ;
315
- break ;
333
+ const deviceCodeCredentialOptions : DeviceCodeCredentialOptions = {
334
+ ...this . _additionalDeviceCodeCredentialOptions ,
335
+ ...interactiveCredentialOptions ,
336
+ userPromptCallback : ( deviceCodeInfo : DeviceCodeInfo ) => {
337
+ PrintUtilities . printMessageInBox ( deviceCodeInfo . message , terminal ) ;
316
338
}
317
- case 'DeviceCode' : {
318
- tokenCredential = new DeviceCodeCredential ( {
319
- ...interactiveCredentialOptions ,
320
- userPromptCallback : ( deviceCodeInfo : DeviceCodeInfo ) => {
321
- PrintUtilities . printMessageInBox ( deviceCodeInfo . message , terminal ) ;
322
- }
323
- } ) ;
324
- break ;
325
- }
326
- default : {
327
- throw new Error ( `Unsupported login flow: ${ loginFlow } ` ) ;
339
+ } ;
340
+
341
+ const options : TokenCredentialOptions = { authorityHost } ;
342
+ const priority : Set < LoginFlowType > = new Set ( [ loginFlow ] ) ;
343
+ for ( const credType of priority ) {
344
+ const next : LoginFlowType | undefined = this . _failoverOrder ?. [ credType ] ;
345
+ if ( next ) {
346
+ priority . add ( next ) ;
328
347
}
329
348
}
330
349
350
+ const knownCredentialTypes : Record <
351
+ LoginFlowType ,
352
+ new ( options : TokenCredentialOptions ) => TokenCredential
353
+ > = {
354
+ DeviceCode : class extends DeviceCodeCredential {
355
+ public new ( credentialOptions : DeviceCodeCredentialOptions ) : DeviceCodeCredential {
356
+ return new DeviceCodeCredential ( {
357
+ ...deviceCodeCredentialOptions ,
358
+ ...credentialOptions
359
+ } ) ;
360
+ }
361
+ } ,
362
+ InteractiveBrowser : class extends InteractiveBrowserCredential {
363
+ public new ( credentialOptions : InteractiveBrowserCredentialNodeOptions ) : InteractiveBrowserCredential {
364
+ return new InteractiveBrowserCredential ( { ...interactiveCredentialOptions , ...credentialOptions } ) ;
365
+ }
366
+ } ,
367
+ AdoCodespacesAuth : AdoCodespacesAuthCredential ,
368
+ VisualStudioCode : VisualStudioCodeCredential ,
369
+ AzureCli : AzureCliCredential ,
370
+ AzureDeveloperCli : AzureDeveloperCliCredential ,
371
+ AzurePowerShell : AzurePowerShellCredential
372
+ } ;
373
+
374
+ const credentials : TokenCredential [ ] = Array . from (
375
+ priority ,
376
+ ( credType ) => new knownCredentialTypes [ credType ] ( options )
377
+ ) ;
378
+
379
+ const tokenCredential : TokenCredential = new ChainedTokenCredential ( ...credentials ) ;
380
+
331
381
try {
332
382
return await this . _getCredentialFromTokenAsync ( terminal , tokenCredential , credentialsCache ) ;
333
383
} catch ( error ) {
334
384
terminal . writeVerbose ( `Failed to get credentials with ${ loginFlow } : ${ error } ` ) ;
335
- const fallbackFlow : LoginFlowType | undefined = this . _failoverOrder [ loginFlow ] ;
336
- if ( fallbackFlow ) {
337
- terminal . writeVerbose ( `Falling back to ${ fallbackFlow } login flow` ) ;
338
- return this . _getCredentialAsync ( terminal , fallbackFlow , credentialsCache ) ;
339
- } else {
340
- throw error ;
341
- }
385
+ throw error ;
342
386
}
343
387
}
344
388
}
0 commit comments