@@ -9,15 +9,21 @@ import {
99 type InteractiveBrowserCredentialInBrowserOptions ,
1010 InteractiveBrowserCredential ,
1111 type InteractiveBrowserCredentialNodeOptions ,
12- type TokenCredential
12+ type TokenCredential ,
13+ ChainedTokenCredential ,
14+ VisualStudioCodeCredential ,
15+ AzureCliCredential ,
16+ AzureDeveloperCliCredential ,
17+ AzurePowerShellCredential
1318} from '@azure/identity' ;
19+ import type { TokenCredentialOptions } from '@azure/identity' ;
20+ import { AdoCodespacesAuthCredential } from './AdoCodespacesAuthCredential' ;
1421import type { ITerminal } from '@rushstack/terminal' ;
1522import { CredentialCache } from '@rushstack/rush-sdk' ;
1623// Use a separate import line so the .d.ts file ends up with an `import type { ... }`
1724// See https://github.com/microsoft/rushstack/issues/3432
1825import type { ICredentialCacheEntry } from '@rushstack/rush-sdk' ;
1926import { PrintUtilities } from '@rushstack/terminal' ;
20- import { AdoCodespacesAuthCredential } from './AdoCodespacesAuthCredential' ;
2127
2228/**
2329 * @public
@@ -80,7 +86,14 @@ export type AzureEnvironmentName = keyof typeof AzureAuthorityHosts;
8086/**
8187 * @public
8288 */
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' ;
8497
8598/**
8699 * @public
@@ -97,13 +110,19 @@ export interface IAzureAuthenticationBaseOptions {
97110 * @defaultValue
98111 * ```json
99112 * {
100- * "AdoCodespacesAuth": "InteractiveBrowser",
113+ * "AdoCodespacesAuth": "VisualStudioCode",
114+ * "VisualStudioCode": "AzureCli",
115+ * "AzureCli": "AzureDeveloperCli",
116+ * "AzureDeveloperCli": "AzurePowerShell",
117+ * "AzurePowerShell": "InteractiveBrowser",
101118 * "InteractiveBrowser": "DeviceCode",
102- * "DeviceCode": null
119+ * "DeviceCode": undefined
103120 * }
104121 * ```
105122 */
106- loginFlowFailover ?: Record < LoginFlowType , LoginFlowType | undefined > ;
123+ loginFlowFailover ?: {
124+ [ key in LoginFlowType ] ?: LoginFlowType ;
125+ } ;
107126}
108127
109128/**
@@ -128,7 +147,11 @@ export abstract class AzureAuthenticationBase {
128147
129148 protected readonly _azureEnvironment : AzureEnvironmentName ;
130149 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 ;
132155
133156 private __credentialCacheId : string | undefined ;
134157 protected get _credentialCacheId ( ) : string {
@@ -148,13 +171,17 @@ export abstract class AzureAuthenticationBase {
148171 public constructor ( options : IAzureAuthenticationBaseOptions ) {
149172 const {
150173 azureEnvironment = 'AzurePublicCloud' ,
151- loginFlow = process . env . CODESPACES === 'true' ? 'AdoCodespacesAuth' : 'InteractiveBrowser '
174+ loginFlow = process . env . CODESPACES === 'true' ? 'AdoCodespacesAuth' : 'VisualStudioCode '
152175 } = options ;
153176 this . _azureEnvironment = azureEnvironment ;
154177 this . _credentialUpdateCommandForLogging = options . credentialUpdateCommandForLogging ;
155178 this . _loginFlow = loginFlow ;
156179 this . _failoverOrder = options . loginFlowFailover || {
157- AdoCodespacesAuth : 'InteractiveBrowser' ,
180+ AdoCodespacesAuth : 'VisualStudioCode' ,
181+ VisualStudioCode : 'AzureCli' ,
182+ AzureCli : 'AzureDeveloperCli' ,
183+ AzureDeveloperCli : 'AzurePowerShell' ,
184+ AzurePowerShell : 'InteractiveBrowser' ,
158185 InteractiveBrowser : 'DeviceCode' ,
159186 DeviceCode : undefined
160187 } ;
@@ -294,8 +321,6 @@ export abstract class AzureAuthenticationBase {
294321 throw new Error ( `Unexpected Azure environment: ${ this . _azureEnvironment } ` ) ;
295322 }
296323
297- let tokenCredential : TokenCredential ;
298-
299324 const interactiveCredentialOptions : (
300325 | InteractiveBrowserCredentialNodeOptions
301326 | InteractiveBrowserCredentialInBrowserOptions
@@ -305,40 +330,59 @@ export abstract class AzureAuthenticationBase {
305330 authorityHost
306331 } ;
307332
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 ) ;
316338 }
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 ) ;
328347 }
329348 }
330349
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+
331381 try {
332382 return await this . _getCredentialFromTokenAsync ( terminal , tokenCredential , credentialsCache ) ;
333383 } catch ( error ) {
334384 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 ;
342386 }
343387 }
344388}
0 commit comments