diff --git a/Composer/packages/electron-server/__tests__/auth/oneAuthService.test.ts b/Composer/packages/electron-server/__tests__/auth/oneAuthService.test.ts index b1b6af6476..401cc15bc7 100644 --- a/Composer/packages/electron-server/__tests__/auth/oneAuthService.test.ts +++ b/Composer/packages/electron-server/__tests__/auth/oneAuthService.test.ts @@ -25,8 +25,10 @@ describe('OneAuth Serivce', () => { id: 'myAccount', realm: 'myTenant', }; + + const tomorrow = Date.now() + 1000 * 60 * 60 * 24; const mockCredential = { - expiresOn: 9999, + expiresOn: tomorrow, value: 'someToken', }; const mockOneAuth = { @@ -255,15 +257,19 @@ describe('OneAuth Serivce', () => { }); it('should get an ARM token for a tenant', async () => { - mockOneAuth.acquireCredentialSilently.mockReturnValueOnce({ credential: { value: 'someARMToken' } }); - (oneAuthService as any).signedInARMAccount = {}; + mockOneAuth.acquireCredentialSilently.mockReturnValueOnce({ + credential: { value: 'someARMToken', expiresOn: tomorrow }, + }); + (oneAuthService as any).signedInARMAccount = { id: 'someAccount' }; const result = await oneAuthService.getARMTokenForTenant('someTenant'); expect(result).toBe('someARMToken'); }); it('should get an ARM token for a tenant interactively if interaction is required', async () => { - mockOneAuth.acquireCredentialInteractively.mockReturnValueOnce({ credential: { value: 'someARMToken' } }); + mockOneAuth.acquireCredentialInteractively.mockReturnValueOnce({ + credential: { value: 'someARMToken', expiresOn: tomorrow }, + }); mockOneAuth.acquireCredentialSilently.mockRejectedValueOnce({ error: { status: 2 /* Interaction Required */ } }); (oneAuthService as any).signedInARMAccount = {}; const result = await oneAuthService.getARMTokenForTenant('someTenant'); diff --git a/Composer/packages/electron-server/src/auth/oneAuthService.ts b/Composer/packages/electron-server/src/auth/oneAuthService.ts index a29e9b8759..488e85bb61 100644 --- a/Composer/packages/electron-server/src/auth/oneAuthService.ts +++ b/Composer/packages/electron-server/src/auth/oneAuthService.ts @@ -62,8 +62,10 @@ export class OneAuthInstance extends OneAuthBase { private _oneAuth: typeof OneAuth | null = null; //eslint-disable-line private signedInAccount: OneAuth.Account | undefined; private signedInARMAccount: OneAuth.Account | undefined; + /** Token solely used to fetch tenants */ private tenantToken: string | undefined; + private tenantTokenExpiresOn: number | undefined; constructor() { super(); @@ -192,13 +194,18 @@ export class OneAuthInstance extends OneAuthBase { this.initialize(); } - if (!this.signedInARMAccount || !this.tenantToken) { + if ( + !this.signedInARMAccount || + !this.tenantToken || + (this.tenantTokenExpiresOn && Date.now() >= this.tenantTokenExpiresOn) + ) { // log the user into the infrastructure tenant to get a token that can be used on the "tenants" API log('Logging user into ARM...'); const signInParams = new this.oneAuth.AuthParameters(DEFAULT_AUTH_SCHEME, ARM_AUTHORITY, ARM_RESOURCE, '', ''); const result: OneAuth.AuthResult = await this.oneAuth.signInInteractively(undefined, signInParams, ''); this.signedInARMAccount = result.account; this.tenantToken = result.credential.value; + this.tenantTokenExpiresOn = result.credential.expiresOn; } // call the tenants API @@ -218,47 +225,64 @@ export class OneAuthInstance extends OneAuthBase { if (!this.initialized) { this.initialize(); } - // sign in arm account. + + // if not signed into the ARM account, sign in. if (!this.signedInARMAccount) { const signInParams = new this.oneAuth.AuthParameters(DEFAULT_AUTH_SCHEME, ARM_AUTHORITY, ARM_RESOURCE, '', ''); const result: OneAuth.AuthResult = await this.oneAuth.signInInteractively(undefined, signInParams, ''); + if (!result.account) { + return ''; + } + this.signedInARMAccount = result.account; } - if (this.signedInARMAccount) { - try { - log('Getting an ARM token for tenant %s', tenantId); - const tokenParams = new this.oneAuth.AuthParameters( - DEFAULT_AUTH_SCHEME, - `https://login.microsoftonline.com/${tenantId}`, - ARM_RESOURCE, - '', - '' - ); - const result = await this.oneAuth.acquireCredentialSilently(this.signedInARMAccount.id, tokenParams, ''); + + // try to get the tenant token silently + try { + log('Getting an ARM token for tenant %s', tenantId); + const tokenParams = new this.oneAuth.AuthParameters( + DEFAULT_AUTH_SCHEME, + `https://login.microsoftonline.com/${tenantId}`, + ARM_RESOURCE, + '', + '' + ); + const result = await this.oneAuth.acquireCredentialSilently(this.signedInARMAccount.id, tokenParams, ''); + if (result.credential.value && Date.now() <= result.credential.expiresOn) { log('Acquired ARM token for tenant: %s', result.credential.value); return result.credential.value; - } catch (e) { - if (e.error?.status === Status.InteractionRequired) { - // try again but interactively - log('Acquiring ARM token failed: Interaction required. Trying again interactively to get access token.'); - const tokenParams = new this.oneAuth.AuthParameters( - DEFAULT_AUTH_SCHEME, - `https://login.microsoftonline.com/${tenantId}`, - ARM_RESOURCE, - '', - '' - ); - const result = await this.oneAuth.acquireCredentialInteractively(this.signedInARMAccount.id, tokenParams, ''); - if (result.credential && result.credential.value) { - log('Acquired access token interactively. %s', result.credential.value); - return result.credential.value; - } - } - log('There was an error trying to get an ARM token for tenant %s: %O', tenantId, e); + } + } catch (e) { + if (e.error?.status === Status.InteractionRequired) { + log( + 'There was an error trying to silently get an ARM token for tenant %s: %O. Trying again interactively to get access token.', + tenantId, + e + ); + } else { throw e; } } - return ''; + + // get the tenant token interactively + try { + const tokenParams = new this.oneAuth.AuthParameters( + DEFAULT_AUTH_SCHEME, + `https://login.microsoftonline.com/${tenantId}`, + ARM_RESOURCE, + '', + '' + ); + const result = await this.oneAuth.acquireCredentialInteractively(this.signedInARMAccount.id, tokenParams, ''); + if (!result.credential.value) { + throw new Error('Interactive sign on returned an empty credential value.'); + } + log('Acquired ARM token for tenant: %s', result.credential.value); + return result.credential.value; + } catch (e) { + log('There was an error trying to get an ARM token interactively for tenant %s: %O', tenantId, e); + throw e; + } } public shutdown() { @@ -275,6 +299,7 @@ export class OneAuthInstance extends OneAuthBase { this.signedInAccount = undefined; this.signedInARMAccount = undefined; this.tenantToken = undefined; + this.tenantTokenExpiresOn = undefined; log('Signed out user.'); }