From 9cd8e904852e8b2ac7f94f2dc58ae74649800327 Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Thu, 3 Sep 2020 07:58:42 +1200 Subject: [PATCH 01/11] WIP --- common/config/rush/pnpm-lock.yaml | 81 ++++++- sdk/identity/identity/package.json | 1 + .../src/credentials/deviceCodeCredential.ts | 222 +++--------------- .../interactiveBrowserCredential.ts | 125 +++++++++- .../interactiveBrowserCredentialOptions.ts | 3 +- 5 files changed, 235 insertions(+), 197 deletions(-) diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index da41d472de34..d6b96fc51619 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -199,6 +199,23 @@ packages: dev: false resolution: integrity: sha512-aFHRw/IHhg3I9ZJW+Va4L+sCirFHMVIu6B7lFdL5mGLfG3xC5vDIdd957LRXFgy2OiKFRUC0QaKknd0YCsQIqA== + /@azure/msal-common/1.2.0: + dependencies: + debug: 4.1.1 + dev: false + engines: + node: '>=0.8.0' + resolution: + integrity: sha512-5MjxcUoalIxGo29MBHCdwMo6HCsCBt25HDkg+Du7x6nG7Y3NAUb9jM8oibLTth9iIRDaWYVvLfxnpXTKwBGs+A== + /@azure/msal-node/1.0.0-alpha.5: + dependencies: + '@azure/msal-common': 1.2.0 + axios: 0.19.2 + debug: 4.1.1 + jsonwebtoken: 8.5.1 + dev: false + resolution: + integrity: sha512-DkoEmnGy+PF5UZbViuLrO8qJVKRBftIojEP3xf8ck6q/vjOY18NUGXxrcKkRXfhRmTe4P2mRGCFuiil8+12IbA== /@babel/code-frame/7.10.4: dependencies: '@babel/highlight': 7.10.4 @@ -4319,6 +4336,24 @@ packages: '0': node >= 0.2.0 resolution: integrity: sha1-XAxWhRBxYOcv50ib3eoLRMK8Z70= + /jsonwebtoken/8.5.1: + dependencies: + jws: 3.2.2 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.2 + semver: 5.7.1 + dev: false + engines: + node: '>=4' + npm: '>=1.4.28' + resolution: + integrity: sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== /jsprim/1.4.1: dependencies: assert-plus: 1.0.0 @@ -4667,6 +4702,10 @@ packages: dev: false resolution: integrity: sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= + /lodash.includes/4.3.0: + dev: false + resolution: + integrity: sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= /lodash.isarguments/3.1.0: dev: false resolution: @@ -4675,10 +4714,30 @@ packages: dev: false resolution: integrity: sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U= + /lodash.isboolean/3.0.3: + dev: false + resolution: + integrity: sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= /lodash.isequal/4.5.0: dev: false resolution: integrity: sha1-QVxEePK8wwEgwizhDtMib30+GOA= + /lodash.isinteger/4.0.4: + dev: false + resolution: + integrity: sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= + /lodash.isnumber/3.0.3: + dev: false + resolution: + integrity: sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= + /lodash.isplainobject/4.0.6: + dev: false + resolution: + integrity: sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= + /lodash.isstring/4.0.1: + dev: false + resolution: + integrity: sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= /lodash.keys/3.1.2: dependencies: lodash._getnative: 3.9.1 @@ -4687,6 +4746,10 @@ packages: dev: false resolution: integrity: sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo= + /lodash.once/4.1.1: + dev: false + resolution: + integrity: sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= /lodash.restparam/3.6.1: dev: false resolution: @@ -8039,17 +8102,17 @@ packages: eslint-plugin-no-only-tests: 2.4.0 eslint-plugin-promise: 4.2.1 inherits: 2.0.4 - karma: 5.1.0 + karma: 5.1.1 karma-chrome-launcher: 3.1.0 karma-coverage: 2.0.2 - karma-edge-launcher: 0.4.2_karma@5.1.0 + karma-edge-launcher: 0.4.2_karma@5.1.1 karma-env-preprocessor: 0.1.1 karma-firefox-launcher: 1.3.0 - karma-ie-launcher: 1.0.0_karma@5.1.0 - karma-junit-reporter: 2.0.1_karma@5.1.0 + karma-ie-launcher: 1.0.0_karma@5.1.1 + karma-junit-reporter: 2.0.1_karma@5.1.1 karma-mocha: 2.0.1 - karma-mocha-reporter: 2.2.5_karma@5.1.0 - karma-remap-istanbul: 0.6.0_karma@5.1.0 + karma-mocha-reporter: 2.2.5_karma@5.1.1 + karma-remap-istanbul: 0.6.0_karma@5.1.1 mocha: 7.2.0 mocha-junit-reporter: 1.23.3_mocha@7.2.0 nyc: 14.1.1 @@ -8816,6 +8879,7 @@ packages: version: 0.0.0 'file:projects/data-tables.tgz': dependencies: + '@azure/core-tracing': 1.0.0-preview.9 '@microsoft/api-extractor': 7.7.11 '@opentelemetry/api': 0.10.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 @@ -8865,7 +8929,7 @@ packages: dev: false name: '@rush-temp/data-tables' resolution: - integrity: sha512-+pNxj98zvsdNPut5Z/stj9537/2V7waySEpDuI2JCAQPPEmYJ886aacWvhl2ELXOVhhZXMktxBTB5wAgjwkIeg== + integrity: sha512-GRiOgEupabCZemjZqdWhL3JXLoK8191/yPwsjlAZxJ9T3zGwdzmUaciZCKUzW6ElMyKyabVPqtw8GQqje9E2Rg== tarball: 'file:projects/data-tables.tgz' version: 0.0.0 'file:projects/dev-tool.tgz': @@ -9187,6 +9251,7 @@ packages: 'file:projects/identity.tgz': dependencies: '@azure/core-tracing': 1.0.0-preview.9 + '@azure/msal-node': 1.0.0-alpha.5 '@microsoft/api-extractor': 7.7.11 '@opentelemetry/api': 0.10.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 @@ -9241,7 +9306,7 @@ packages: optionalDependencies: keytar: 5.6.0 resolution: - integrity: sha512-geIV8bm9XISGgFz6nA51Ne2dPOTpzV4dZ0YsrOeXfC4qhe9TzbjW1AJMOR/e86hmHY+Gi/sWEptUBh5AA8Gc6A== + integrity: sha512-FOC4qS5ZLrDEQI+53EDCBfz3qYTfc+6Mgm+jWsZ8mCHpcDbAlRm2XamgSC/JePg4byDhKYy5F5RAp98sVkZtgA== tarball: 'file:projects/identity.tgz' version: 0.0.0 'file:projects/keyvault-admin.tgz': diff --git a/sdk/identity/identity/package.json b/sdk/identity/identity/package.json index ff67f43755a5..a431a2c828b3 100644 --- a/sdk/identity/identity/package.json +++ b/sdk/identity/identity/package.json @@ -82,6 +82,7 @@ "@azure/core-http": "^1.1.6", "@azure/core-tracing": "1.0.0-preview.9", "@azure/logger": "^1.0.0", + "@azure/msal-node": "^1.0.0-alpha.5", "@opentelemetry/api": "^0.10.2", "events": "^3.0.0", "jws": "^4.0.0", diff --git a/sdk/identity/identity/src/credentials/deviceCodeCredential.ts b/sdk/identity/identity/src/credentials/deviceCodeCredential.ts index 878fa301c79f..40d2012b0116 100644 --- a/sdk/identity/identity/src/credentials/deviceCodeCredential.ts +++ b/sdk/identity/identity/src/credentials/deviceCodeCredential.ts @@ -1,28 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. - -import qs from "qs"; -import { TokenCredential, GetTokenOptions, AccessToken } from "@azure/core-http"; -import * as coreHttp from "@azure/core-http"; -import { IdentityClient, TokenResponse, TokenCredentialOptions } from "../client/identityClient"; -import { AuthenticationError, AuthenticationErrorName } from "../client/errors"; +import { AccessToken, TokenCredential, GetTokenOptions, delay } from "@azure/core-http"; +import { TokenCredentialOptions, IdentityClient } from "../client/identityClient"; import { createSpan } from "../util/tracing"; -import { CanonicalCode } from "@opentelemetry/api"; import { credentialLogger, formatSuccess } from "../util/logging"; +import { AuthenticationError, AuthenticationErrorName } from "../client/errors"; +import { CanonicalCode } from "@opentelemetry/api"; -/** - * An internal interface that contains the verbatim devicecode response. - * This interface does not get exported from the public interface of the - * library. - */ -export interface DeviceCodeResponse { - device_code: string; - user_code: string; - verification_uri: string; - expires_in: number; - interval: number; - message: string; -} +import msal from "@azure/msal-node"; /** * Provides the user code and verification URI where the code must be @@ -63,10 +48,10 @@ const logger = credentialLogger("DeviceCodeCredential"); */ export class DeviceCodeCredential implements TokenCredential { private identityClient: IdentityClient; + private pca: msal.PublicClientApplication; private tenantId: string; private clientId: string; private userPromptCallback: DeviceCodePromptCallback; - private lastTokenResponse: TokenResponse | null = null; /** * Creates an instance of DeviceCodeCredential with the details needed @@ -89,138 +74,18 @@ export class DeviceCodeCredential implements TokenCredential { this.tenantId = tenantId; this.clientId = clientId; this.userPromptCallback = userPromptCallback; - } - - private async sendDeviceCodeRequest( - scope: string, - options?: GetTokenOptions - ): Promise { - const { span, options: newOptions } = createSpan( - "DeviceCodeCredential-sendDeviceCodeRequest", - options - ); - try { - const webResource = this.identityClient.createWebResource({ - url: `${this.identityClient.authorityHost}/${this.tenantId}/oauth2/v2.0/devicecode`, - method: "POST", - disableJsonStringifyOnBody: true, - deserializationMapper: undefined, - body: qs.stringify({ - client_id: this.clientId, - scope - }), - headers: { - Accept: "application/json", - "Content-Type": "application/x-www-form-urlencoded" - }, - abortSignal: options && options.abortSignal, - spanOptions: newOptions.tracingOptions && newOptions.tracingOptions.spanOptions - }); - - logger.info("Sending devicecode request"); - - const response = await this.identityClient.sendRequest(webResource); - if (!(response.status === 200 || response.status === 201)) { - throw new AuthenticationError(response.status, response.bodyAsText); - } - - return response.parsedBody as DeviceCodeResponse; - } catch (err) { - const code = - err.name === AuthenticationErrorName - ? CanonicalCode.UNAUTHENTICATED - : CanonicalCode.UNKNOWN; - - if (err.name === AuthenticationErrorName) { - logger.info( - `Failed to authenticate ${(err as AuthenticationError).errorResponse.errorDescription}` - ); - } else { - logger.info(`Failed to authenticate ${err}`); - } - - span.setStatus({ - code, - message: err.message - }); - throw err; - } finally { - span.end(); - } - } - - private async pollForToken( - deviceCodeResponse: DeviceCodeResponse, - options?: GetTokenOptions - ): Promise { - let tokenResponse: TokenResponse | null = null; - const { span, options: newOptions } = createSpan("DeviceCodeCredential-pollForToken", options); - - try { - const webResource = this.identityClient.createWebResource({ - url: `${this.identityClient.authorityHost}/${this.tenantId}/oauth2/v2.0/token`, - method: "POST", - disableJsonStringifyOnBody: true, - deserializationMapper: undefined, - body: qs.stringify({ - grant_type: "urn:ietf:params:oauth:grant-type:device_code", - client_id: this.clientId, - device_code: deviceCodeResponse.device_code - }), - headers: { - Accept: "application/json", - "Content-Type": "application/x-www-form-urlencoded" - }, - abortSignal: options && options.abortSignal, - spanOptions: newOptions.tracingOptions && newOptions.tracingOptions.spanOptions - }); - - while (tokenResponse === null) { - try { - // Referencing delay from core-http this way for testing purposes. - await coreHttp.delay(deviceCodeResponse.interval * 1000); - // Check the abort signal before sending the request - if (options && options.abortSignal && options.abortSignal.aborted) { - return null; - } - - tokenResponse = await this.identityClient.sendTokenRequest(webResource); - } catch (err) { - if (err.name === AuthenticationErrorName) { - switch (err.errorResponse.error) { - case "authorization_pending": - break; - case "authorization_declined": - return null; - case "expired_token": - throw err; - case "bad_verification_code": - throw err; - default: - // Any other error should be rethrown - throw err; - } - } else { - throw err; - } - } - } - - return tokenResponse; - } catch (err) { - const code = - err.name === AuthenticationErrorName - ? CanonicalCode.UNAUTHENTICATED - : CanonicalCode.UNKNOWN; - span.setStatus({ - code, - message: err.message - }); - throw err; - } finally { - span.end(); - } + const publicClientConfig = { + auth: { + clientId: this.clientId, + authority: "https://login.microsoftonline.com/" + this.tenantId, + }, + cache: { + cachePlugin: undefined + }, + }; + + this.pca = new msal.PublicClientApplication(publicClientConfig); } /** @@ -233,46 +98,23 @@ export class DeviceCodeCredential implements TokenCredential { * @param options The options used to configure any requests this * TokenCredential implementation might make. */ - public async getToken( + getToken( scopes: string | string[], options?: GetTokenOptions ): Promise { const { span, options: newOptions } = createSpan("DeviceCodeCredential-getToken", options); - try { - let tokenResponse: TokenResponse | null = null; - let scopeString = typeof scopes === "string" ? scopes : scopes.join(" "); - if (scopeString.indexOf("offline_access") < 0) { - scopeString += " offline_access"; - } - // Try to use the refresh token first - if (this.lastTokenResponse && this.lastTokenResponse.refreshToken) { - tokenResponse = await this.identityClient.refreshAccessToken( - this.tenantId, - this.clientId, - scopeString, - this.lastTokenResponse.refreshToken, - undefined, // clientSecret not needed for device code auth - undefined, - newOptions - ); - } + const scopeArray = typeof scopes === "object" ? scopes : [scopes]; - if (tokenResponse === null) { - const deviceCodeResponse = await this.sendDeviceCodeRequest(scopeString, newOptions); + const deviceCodeRequest = { + deviceCodeCallback: this.userPromptCallback, + scopes: scopeArray, + }; - this.userPromptCallback({ - userCode: deviceCodeResponse.user_code, - verificationUri: deviceCodeResponse.verification_uri, - message: deviceCodeResponse.message - }); + logger.info("Sending devicecode request"); - tokenResponse = await this.pollForToken(deviceCodeResponse, newOptions); - } - - this.lastTokenResponse = tokenResponse; - logger.getToken.info(formatSuccess(scopes)); - return (tokenResponse && tokenResponse.accessToken) || null; + try { + return this.acquireTokenByDeviceCode(deviceCodeRequest); } catch (err) { const code = err.name === AuthenticationErrorName @@ -288,4 +130,16 @@ export class DeviceCodeCredential implements TokenCredential { span.end(); } } + + private async acquireTokenByDeviceCode(deviceCodeRequest: msal.DeviceCodeRequest): Promise { + try { + const deviceResponse = await this.pca.acquireTokenByDeviceCode(deviceCodeRequest); + return({ + expiresOnTimestamp: deviceResponse.expiresOn.getTime(), + token: deviceResponse.accessToken + }); + } catch (error) { + throw new Error(`Device Authentication Error "${JSON.stringify(error)}"`); + } + } } diff --git a/sdk/identity/identity/src/credentials/interactiveBrowserCredential.ts b/sdk/identity/identity/src/credentials/interactiveBrowserCredential.ts index accfb595ef5b..322627b04aa6 100644 --- a/sdk/identity/identity/src/credentials/interactiveBrowserCredential.ts +++ b/sdk/identity/identity/src/credentials/interactiveBrowserCredential.ts @@ -6,21 +6,64 @@ import { TokenCredential, GetTokenOptions, AccessToken } from "@azure/core-http"; import { InteractiveBrowserCredentialOptions } from "./interactiveBrowserCredentialOptions"; import { credentialLogger, formatError } from "../util/logging"; +import { TokenCredentialOptions, IdentityClient } from "../client/identityClient"; +import { DefaultTenantId, DeveloperSignOnClientId } from "../constants"; +import { Socket } from "net"; + +const SERVER_PORT = process.env.PORT || 80; + +import express from "express"; +import msal from "@azure/msal-node"; +import open from "open"; +import path from "path"; +import http from "http"; const BrowserNotSupportedError = new Error( "InteractiveBrowserCredential is not supported in Node.js." ); const logger = credentialLogger("InteractiveBrowserCredential"); +interface AuthenticationRecord { + authority?: string, + homeAccountId: string, + environment: string, + tenantId: string, + username: string, +} + /** * Enables authentication to Azure Active Directory inside of the web browser * using the interactive login flow, either via browser redirects or a popup * window. This credential is not currently supported in Node.js. */ export class InteractiveBrowserCredential implements TokenCredential { + private identityClient: IdentityClient; + private pca: msal.PublicClientApplication; + private msalCacheManager: msal.TokenCache; + private tenantId: string; + private clientId: string; + private persistenceEnabled: boolean; + private authenticationRecord: AuthenticationRecord | undefined; + constructor(options?: InteractiveBrowserCredentialOptions) { - logger.info(formatError(BrowserNotSupportedError)); - throw BrowserNotSupportedError; + this.identityClient = new IdentityClient(options); + this.tenantId = (options && options.tenantId) || DefaultTenantId; + this.clientId = (options && options.clientId) || DeveloperSignOnClientId; + + // Future update: this is for persistent caching + this.persistenceEnabled = false; + this.authenticationRecord = undefined; + + const publicClientConfig = { + auth: { + clientId: this.clientId, + authority: "https://login.microsoftonline.com/" + this.tenantId, + redirectUri: "http://localhost" + }, + cache: undefined + }; + this.pca = new msal.PublicClientApplication(publicClientConfig); + this.msalCacheManager = this.pca.getTokenCache(); } /** @@ -37,7 +80,81 @@ export class InteractiveBrowserCredential implements TokenCredential { scopes: string | string[], options?: GetTokenOptions ): Promise { - logger.getToken.info(formatError(BrowserNotSupportedError)); - throw BrowserNotSupportedError; + const scopeArray = typeof scopes === "object" ? scopes : [scopes]; + + return this.acquireTokenFromBrowser(scopeArray); + } + + private async openAuthCodeUrl(scopeArray: string[]): Promise { + const authCodeUrlParameters = { + scopes: scopeArray, + redirectUri: "http://localhost" + }; + + const response = await this.pca.getAuthCodeUrl(authCodeUrlParameters); + await open(response); + } + + private async acquireTokenFromBrowser(scopeArray: string[]): Promise { + return new Promise(async (resolve, reject) => { + let listen: http.Server | undefined; + let socketToDestroy: Socket | undefined; + + function cleanup() { + if (listen) { + listen.close(); + } + if (socketToDestroy) { + socketToDestroy.destroy(); + } + } + + // Create Express App and Routes + const app = express(); + + app.get("/", async (req, res) => { + const tokenRequest: msal.AuthorizationCodeRequest = { + code: req.query.code as string, + redirectUri: "http://localhost", + scopes: scopeArray + }; + + try { + const authResponse = await this.pca.acquireTokenByCode(tokenRequest); + res.sendStatus(200); + logger.info(`authResponse: ${authResponse}`); + if (this.persistenceEnabled) { + this.msalCacheManager.writeToPersistence(); + } + + resolve({ + expiresOnTimestamp: authResponse.expiresOn.valueOf(), + token: authResponse.accessToken + }); + } catch (error) { + res.status(500).send(error); + + reject( + new Error( + `Authentication Error "${req.query["error"]}":\n\n${req.query["error_description"]}` + ) + ); + } finally { + cleanup(); + } + }); + + listen = app.listen(SERVER_PORT, () => + logger.info(`Msal Node Auth Code Sample app listening on port ${SERVER_PORT}!`) + ); + listen.on("connection", (socket) => (socketToDestroy = socket)); + + try { + await this.openAuthCodeUrl(scopeArray); + } catch(e) { + cleanup(); + throw e; + } + }); } } diff --git a/sdk/identity/identity/src/credentials/interactiveBrowserCredentialOptions.ts b/sdk/identity/identity/src/credentials/interactiveBrowserCredentialOptions.ts index d84bb3a5e751..f6ebff746e91 100644 --- a/sdk/identity/identity/src/credentials/interactiveBrowserCredentialOptions.ts +++ b/sdk/identity/identity/src/credentials/interactiveBrowserCredentialOptions.ts @@ -20,7 +20,8 @@ export interface InteractiveBrowserCredentialOptions extends TokenCredentialOpti /** * Specifies whether a redirect or a popup window should be used to * initiate the user authentication flow. Possible values are "redirect" - * or "popup" (default). + * or "popup" (default) for browser and "popup" (default) for node. + * */ loginStyle?: BrowserLoginStyle; From 039655e98138aca64ea4d26fa1a3bae4482fdc75 Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Thu, 3 Sep 2020 07:58:53 +1200 Subject: [PATCH 02/11] WIP --- .../src/credentials/deviceCodeCredential.ts | 181 ++++++++++-------- 1 file changed, 96 insertions(+), 85 deletions(-) diff --git a/sdk/identity/identity/src/credentials/deviceCodeCredential.ts b/sdk/identity/identity/src/credentials/deviceCodeCredential.ts index 40d2012b0116..9d7949f21ce1 100644 --- a/sdk/identity/identity/src/credentials/deviceCodeCredential.ts +++ b/sdk/identity/identity/src/credentials/deviceCodeCredential.ts @@ -47,13 +47,13 @@ const logger = credentialLogger("DeviceCodeCredential"); * that the user can enter into https://microsoft.com/devicelogin. */ export class DeviceCodeCredential implements TokenCredential { - private identityClient: IdentityClient; - private pca: msal.PublicClientApplication; - private tenantId: string; - private clientId: string; - private userPromptCallback: DeviceCodePromptCallback; + private identityClient: IdentityClient; + private pca: msal.PublicClientApplication; + private tenantId: string; + private clientId: string; + private userPromptCallback: DeviceCodePromptCallback; - /** + /** * Creates an instance of DeviceCodeCredential with the details needed * to initiate the device code authorization flow with Azure Active Directory. * @@ -64,82 +64,93 @@ export class DeviceCodeCredential implements TokenCredential { {@link DeviceCodeInfo} to the user. * @param options Options for configuring the client which makes the authentication request. */ - constructor( - tenantId: string | "organizations", - clientId: string, - userPromptCallback: DeviceCodePromptCallback, - options?: TokenCredentialOptions - ) { - this.identityClient = new IdentityClient(options); - this.tenantId = tenantId; - this.clientId = clientId; - this.userPromptCallback = userPromptCallback; - - const publicClientConfig = { - auth: { - clientId: this.clientId, - authority: "https://login.microsoftonline.com/" + this.tenantId, - }, - cache: { - cachePlugin: undefined - }, - }; - - this.pca = new msal.PublicClientApplication(publicClientConfig); - } - - /** - * Authenticates with Azure Active Directory and returns an access token if - * successful. If authentication cannot be performed at this time, this method may - * return null. If an error occurs during authentication, an {@link AuthenticationError} - * containing failure details will be thrown. - * - * @param scopes The list of scopes for which the token will have access. - * @param options The options used to configure any requests this - * TokenCredential implementation might make. - */ - getToken( - scopes: string | string[], - options?: GetTokenOptions - ): Promise { - const { span, options: newOptions } = createSpan("DeviceCodeCredential-getToken", options); - - const scopeArray = typeof scopes === "object" ? scopes : [scopes]; - - const deviceCodeRequest = { - deviceCodeCallback: this.userPromptCallback, - scopes: scopeArray, - }; - - logger.info("Sending devicecode request"); - - try { - return this.acquireTokenByDeviceCode(deviceCodeRequest); - } catch (err) { - const code = - err.name === AuthenticationErrorName - ? CanonicalCode.UNAUTHENTICATED - : CanonicalCode.UNKNOWN; - span.setStatus({ - code, - message: err.message - }); - logger.getToken.info(err); - throw err; - } finally { - span.end(); - } - } - - private async acquireTokenByDeviceCode(deviceCodeRequest: msal.DeviceCodeRequest): Promise { - try { - const deviceResponse = await this.pca.acquireTokenByDeviceCode(deviceCodeRequest); - return({ - expiresOnTimestamp: deviceResponse.expiresOn.getTime(), - token: deviceResponse.accessToken - }); - } catch (error) { - throw new Error(`Device Authentication Error "${JSON.stringify(error)}"`); - } - } -} + constructor( + tenantId: string | "organizations", + clientId: string, + userPromptCallback: DeviceCodePromptCallback, + options?: TokenCredentialOptions + ) { + this.identityClient = new IdentityClient(options); + this.tenantId = tenantId; + this.clientId = clientId + this.userPromptCallback = userPromptCallback; + + const publicClientConfig = { + auth: { + clientId: this.clientId + authority: "https://login.microsoftonline.com/" + this.tenantId + } , + cache: { + cachePlugin: undefined + } + }; + + this.pca = new msal.PublicClientApplication(publicClientConfig); + } + + /** + * Authenticates with Azure Active Directory and returns an access token if + * successful. If authentication cannot be performed at this time, this method may + * return null. If an error occurs during authentication, an {@link AuthenticationError} + * containing failure details will be thrown. + * + * @param scopes The list of scopes for which the token will have access. + * @param options The options used to configure any requests this + * TokenCredential implementation might make. + */ + getToken( + scopes: string | string[] + options?: GetToken + Options + + ): Promise { + const { span, options: newOptions } = createSpa + "D eviceCodeCredential-getToken", + options + ); + + const scopeArray = typeof scopes === "object" ? scopes : [scopes]; + + const deviceCodeRequest = { + deviceCodeCallback: this.userPromptCallback, + scopes: scopeArray + }; + + logger.info("Sending devicecode request"); + + try { + return this.acquireTokenByDeviceCode( + + deviceCodeRequest + + ); + } catch (err) { + const code = + err.name === AuthenticationErrorName + ? CanonicalCode.UNAUTHENTICATED + : CanonicalCode.UNKNOWN; + span.setStatus({ + c ode, + message: err.message + }) ; + logger.getToken.info(err); + throw err; + } finally { + span.end(); + } + } + + private async acquireTokenByDeviceCode( + deviceCodeRequest: msal.DeviceCodeRequest + ): Promise { + try { + const deviceResponse = await this.pca.acquireTokenByDeviceCode(deviceCodeRequest); + return { + expiresOnTimestamp: deviceResponse.expiresOn.getTime(), + token: deviceResponse.accessToken + }; + } catch (error) { + throw new Error(`Device Authentication Error "${JSON.stringify(error)}"`); + } + } + } From 0f216dc9bf98095a13cb42596be77662f9b246cf Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Thu, 3 Sep 2020 08:09:59 +1200 Subject: [PATCH 03/11] WIP --- common/config/rush/pnpm-lock.yaml | 3 +- sdk/identity/identity/package.json | 5 +- .../src/credentials/deviceCodeCredential.ts | 181 ++++++++---------- 3 files changed, 91 insertions(+), 98 deletions(-) diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index d6b96fc51619..17663b27de3c 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -9269,6 +9269,7 @@ packages: '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452 '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6 assert: 1.5.0 + axios: 0.19.2 cross-env: 7.0.2 eslint: 6.8.0 events: 3.1.0 @@ -9306,7 +9307,7 @@ packages: optionalDependencies: keytar: 5.6.0 resolution: - integrity: sha512-FOC4qS5ZLrDEQI+53EDCBfz3qYTfc+6Mgm+jWsZ8mCHpcDbAlRm2XamgSC/JePg4byDhKYy5F5RAp98sVkZtgA== + integrity: sha512-p2S2EGVa4KNgoTYoBYBKogF8arkQ843Rybs+dy6lLxootEKXWKE3N5q0gRP5FXuXvigpQKt3IE1jVqx3UuddoQ== tarball: 'file:projects/identity.tgz' version: 0.0.0 'file:projects/keyvault-admin.tgz': diff --git a/sdk/identity/identity/package.json b/sdk/identity/identity/package.json index a431a2c828b3..59e04de4d190 100644 --- a/sdk/identity/identity/package.json +++ b/sdk/identity/identity/package.json @@ -87,9 +87,12 @@ "events": "^3.0.0", "jws": "^4.0.0", "msal": "^1.0.2", + "open": "^7.0.0", "qs": "^6.7.0", "tslib": "^2.0.0", - "uuid": "^8.1.0" + "uuid": "^8.1.0", + "@rollup/plugin-json": "^4.0.0", + "axios": "^0.19.2" }, "optionalDependencies": { "keytar": "^5.4.0" diff --git a/sdk/identity/identity/src/credentials/deviceCodeCredential.ts b/sdk/identity/identity/src/credentials/deviceCodeCredential.ts index 9d7949f21ce1..40d2012b0116 100644 --- a/sdk/identity/identity/src/credentials/deviceCodeCredential.ts +++ b/sdk/identity/identity/src/credentials/deviceCodeCredential.ts @@ -47,13 +47,13 @@ const logger = credentialLogger("DeviceCodeCredential"); * that the user can enter into https://microsoft.com/devicelogin. */ export class DeviceCodeCredential implements TokenCredential { - private identityClient: IdentityClient; - private pca: msal.PublicClientApplication; - private tenantId: string; - private clientId: string; - private userPromptCallback: DeviceCodePromptCallback; + private identityClient: IdentityClient; + private pca: msal.PublicClientApplication; + private tenantId: string; + private clientId: string; + private userPromptCallback: DeviceCodePromptCallback; - /** + /** * Creates an instance of DeviceCodeCredential with the details needed * to initiate the device code authorization flow with Azure Active Directory. * @@ -64,93 +64,82 @@ export class DeviceCodeCredential implements TokenCredential { {@link DeviceCodeInfo} to the user. * @param options Options for configuring the client which makes the authentication request. */ - constructor( - tenantId: string | "organizations", - clientId: string, - userPromptCallback: DeviceCodePromptCallback, - options?: TokenCredentialOptions - ) { - this.identityClient = new IdentityClient(options); - this.tenantId = tenantId; - this.clientId = clientId - this.userPromptCallback = userPromptCallback; - - const publicClientConfig = { - auth: { - clientId: this.clientId - authority: "https://login.microsoftonline.com/" + this.tenantId - } , - cache: { - cachePlugin: undefined - } - }; - - this.pca = new msal.PublicClientApplication(publicClientConfig); - } - - /** - * Authenticates with Azure Active Directory and returns an access token if - * successful. If authentication cannot be performed at this time, this method may - * return null. If an error occurs during authentication, an {@link AuthenticationError} - * containing failure details will be thrown. - * - * @param scopes The list of scopes for which the token will have access. - * @param options The options used to configure any requests this - * TokenCredential implementation might make. - */ - getToken( - scopes: string | string[] - options?: GetToken - Options - - ): Promise { - const { span, options: newOptions } = createSpa - "D eviceCodeCredential-getToken", - options - ); - - const scopeArray = typeof scopes === "object" ? scopes : [scopes]; - - const deviceCodeRequest = { - deviceCodeCallback: this.userPromptCallback, - scopes: scopeArray - }; - - logger.info("Sending devicecode request"); - - try { - return this.acquireTokenByDeviceCode( - - deviceCodeRequest - - ); - } catch (err) { - const code = - err.name === AuthenticationErrorName - ? CanonicalCode.UNAUTHENTICATED - : CanonicalCode.UNKNOWN; - span.setStatus({ - c ode, - message: err.message - }) ; - logger.getToken.info(err); - throw err; - } finally { - span.end(); - } - } - - private async acquireTokenByDeviceCode( - deviceCodeRequest: msal.DeviceCodeRequest - ): Promise { - try { - const deviceResponse = await this.pca.acquireTokenByDeviceCode(deviceCodeRequest); - return { - expiresOnTimestamp: deviceResponse.expiresOn.getTime(), - token: deviceResponse.accessToken - }; - } catch (error) { - throw new Error(`Device Authentication Error "${JSON.stringify(error)}"`); - } - } - } + constructor( + tenantId: string | "organizations", + clientId: string, + userPromptCallback: DeviceCodePromptCallback, + options?: TokenCredentialOptions + ) { + this.identityClient = new IdentityClient(options); + this.tenantId = tenantId; + this.clientId = clientId; + this.userPromptCallback = userPromptCallback; + + const publicClientConfig = { + auth: { + clientId: this.clientId, + authority: "https://login.microsoftonline.com/" + this.tenantId, + }, + cache: { + cachePlugin: undefined + }, + }; + + this.pca = new msal.PublicClientApplication(publicClientConfig); + } + + /** + * Authenticates with Azure Active Directory and returns an access token if + * successful. If authentication cannot be performed at this time, this method may + * return null. If an error occurs during authentication, an {@link AuthenticationError} + * containing failure details will be thrown. + * + * @param scopes The list of scopes for which the token will have access. + * @param options The options used to configure any requests this + * TokenCredential implementation might make. + */ + getToken( + scopes: string | string[], + options?: GetTokenOptions + ): Promise { + const { span, options: newOptions } = createSpan("DeviceCodeCredential-getToken", options); + + const scopeArray = typeof scopes === "object" ? scopes : [scopes]; + + const deviceCodeRequest = { + deviceCodeCallback: this.userPromptCallback, + scopes: scopeArray, + }; + + logger.info("Sending devicecode request"); + + try { + return this.acquireTokenByDeviceCode(deviceCodeRequest); + } catch (err) { + const code = + err.name === AuthenticationErrorName + ? CanonicalCode.UNAUTHENTICATED + : CanonicalCode.UNKNOWN; + span.setStatus({ + code, + message: err.message + }); + logger.getToken.info(err); + throw err; + } finally { + span.end(); + } + } + + private async acquireTokenByDeviceCode(deviceCodeRequest: msal.DeviceCodeRequest): Promise { + try { + const deviceResponse = await this.pca.acquireTokenByDeviceCode(deviceCodeRequest); + return({ + expiresOnTimestamp: deviceResponse.expiresOn.getTime(), + token: deviceResponse.accessToken + }); + } catch (error) { + throw new Error(`Device Authentication Error "${JSON.stringify(error)}"`); + } + } +} From af6b3154a1b1c160d8f825c9042d668d854cd6af Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Thu, 3 Sep 2020 09:15:33 +1200 Subject: [PATCH 04/11] Fix rollup --- sdk/identity/identity/package.json | 8 +++----- sdk/identity/identity/rollup.base.config.js | 2 ++ sdk/identity/identity/tsconfig.json | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/sdk/identity/identity/package.json b/sdk/identity/identity/package.json index 59e04de4d190..866041abf048 100644 --- a/sdk/identity/identity/package.json +++ b/sdk/identity/identity/package.json @@ -89,10 +89,10 @@ "msal": "^1.0.2", "open": "^7.0.0", "qs": "^6.7.0", - "tslib": "^2.0.0", - "uuid": "^8.1.0", "@rollup/plugin-json": "^4.0.0", - "axios": "^0.19.2" + "axios": "^0.19.2", + "tslib": "^2.0.0", + "uuid": "^8.1.0" }, "optionalDependencies": { "keytar": "^5.4.0" @@ -102,7 +102,6 @@ "@azure/abort-controller": "^1.0.0", "@microsoft/api-extractor": "7.7.11", "@rollup/plugin-commonjs": "11.0.2", - "@rollup/plugin-json": "^4.0.0", "@rollup/plugin-multi-entry": "^3.0.0", "@rollup/plugin-node-resolve": "^8.0.0", "@rollup/plugin-replace": "^2.2.0", @@ -129,7 +128,6 @@ "karma-remap-istanbul": "^0.6.0", "mocha": "^7.1.1", "mocha-junit-reporter": "^1.18.0", - "open": "^7.0.0", "prettier": "^1.16.4", "puppeteer": "^3.3.0", "rimraf": "^3.0.0", diff --git a/sdk/identity/identity/rollup.base.config.js b/sdk/identity/identity/rollup.base.config.js index 94bd31e2a0b8..af2dfaad9aa6 100644 --- a/sdk/identity/identity/rollup.base.config.js +++ b/sdk/identity/identity/rollup.base.config.js @@ -1,6 +1,7 @@ import nodeResolve from "@rollup/plugin-node-resolve"; import multiEntry from "@rollup/plugin-multi-entry"; import cjs from "@rollup/plugin-commonjs"; +import json from "@rollup/plugin-json"; import replace from "@rollup/plugin-replace"; import { terser } from "rollup-plugin-terser"; import sourcemaps from "rollup-plugin-sourcemaps"; @@ -27,6 +28,7 @@ export function nodeConfig(test = false) { "if (isNode)": "if (true)" }), nodeResolve({ preferBuiltins: true }), + json(), cjs() ] }; diff --git a/sdk/identity/identity/tsconfig.json b/sdk/identity/identity/tsconfig.json index 9aefb35323a5..bf0d06868733 100644 --- a/sdk/identity/identity/tsconfig.json +++ b/sdk/identity/identity/tsconfig.json @@ -43,6 +43,7 @@ // "types": [], /* Type declaration files to be included in compilation. */ "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */, "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, + "resolveJsonModule": true, // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ /* Source Map Options */ // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ From 59d2fa3ce1b2e4983b71f6803e26badce990b4a7 Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Thu, 3 Sep 2020 10:42:10 +1200 Subject: [PATCH 05/11] linting --- .../interactiveBrowserCredential.ts | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/sdk/identity/identity/src/credentials/interactiveBrowserCredential.ts b/sdk/identity/identity/src/credentials/interactiveBrowserCredential.ts index 322627b04aa6..98482a82219e 100644 --- a/sdk/identity/identity/src/credentials/interactiveBrowserCredential.ts +++ b/sdk/identity/identity/src/credentials/interactiveBrowserCredential.ts @@ -43,6 +43,8 @@ export class InteractiveBrowserCredential implements TokenCredential { private tenantId: string; private clientId: string; private persistenceEnabled: boolean; + private redirectUri: string; + private authorityHost: string; private authenticationRecord: AuthenticationRecord | undefined; constructor(options?: InteractiveBrowserCredentialOptions) { @@ -54,11 +56,27 @@ export class InteractiveBrowserCredential implements TokenCredential { this.persistenceEnabled = false; this.authenticationRecord = undefined; + if (options && options.redirectUri) { + if (typeof options.redirectUri === "string") { + this.redirectUri = options.redirectUri; + } else { + this.redirectUri = options.redirectUri(); + } + } else { + this.redirectUri = "http://localhost"; + } + + if (options && options.authorityHost) { + this.authorityHost = options.authorityHost; + } else { + this.authorityHost = "https://login.microsoftonline.com/" + this.tenantId; + } + const publicClientConfig = { auth: { clientId: this.clientId, - authority: "https://login.microsoftonline.com/" + this.tenantId, - redirectUri: "http://localhost" + authority: this.authorityHost, + redirectUri: this.redirectUri }, cache: undefined }; @@ -88,7 +106,7 @@ export class InteractiveBrowserCredential implements TokenCredential { private async openAuthCodeUrl(scopeArray: string[]): Promise { const authCodeUrlParameters = { scopes: scopeArray, - redirectUri: "http://localhost" + redirectUri: this.redirectUri }; const response = await this.pca.getAuthCodeUrl(authCodeUrlParameters); @@ -96,7 +114,9 @@ export class InteractiveBrowserCredential implements TokenCredential { } private async acquireTokenFromBrowser(scopeArray: string[]): Promise { + // eslint-disable-next-line return new Promise(async (resolve, reject) => { + // eslint-disable-next-line let listen: http.Server | undefined; let socketToDestroy: Socket | undefined; @@ -115,7 +135,7 @@ export class InteractiveBrowserCredential implements TokenCredential { app.get("/", async (req, res) => { const tokenRequest: msal.AuthorizationCodeRequest = { code: req.query.code as string, - redirectUri: "http://localhost", + redirectUri: this.redirectUri, scopes: scopeArray }; From d681ac28a18142b06ccc6ce9f3631191e75bff35 Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Thu, 3 Sep 2020 10:59:44 +1200 Subject: [PATCH 06/11] Address feedback --- .../src/credentials/interactiveBrowserCredential.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sdk/identity/identity/src/credentials/interactiveBrowserCredential.ts b/sdk/identity/identity/src/credentials/interactiveBrowserCredential.ts index 98482a82219e..0b0d04f2108c 100644 --- a/sdk/identity/identity/src/credentials/interactiveBrowserCredential.ts +++ b/sdk/identity/identity/src/credentials/interactiveBrowserCredential.ts @@ -67,7 +67,11 @@ export class InteractiveBrowserCredential implements TokenCredential { } if (options && options.authorityHost) { - this.authorityHost = options.authorityHost; + if (options.authorityHost.endsWith("/")) { + this.authorityHost = options.authorityHost + this.tenantId; + } else { + this.authorityHost = options.authorityHost + "/" + this.tenantId; + } } else { this.authorityHost = "https://login.microsoftonline.com/" + this.tenantId; } From 45e05c557fafc8990ada22eda26079016bf2a288 Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Fri, 4 Sep 2020 06:34:07 +1200 Subject: [PATCH 07/11] Attempt to fix build --- .../identity/src/credentials/deviceCodeCredential.ts | 8 ++++---- .../src/credentials/interactiveBrowserCredential.ts | 10 +++++----- sdk/keyvault/keyvault-certificates/package.json | 1 + .../keyvault-certificates/rollup.base.config.js | 2 ++ sdk/keyvault/keyvault-keys/package.json | 1 + sdk/keyvault/keyvault-keys/rollup.base.config.js | 2 ++ sdk/keyvault/keyvault-secrets/package.json | 1 + sdk/keyvault/keyvault-secrets/rollup.base.config.js | 2 ++ 8 files changed, 18 insertions(+), 9 deletions(-) diff --git a/sdk/identity/identity/src/credentials/deviceCodeCredential.ts b/sdk/identity/identity/src/credentials/deviceCodeCredential.ts index 40d2012b0116..6c24aae2313b 100644 --- a/sdk/identity/identity/src/credentials/deviceCodeCredential.ts +++ b/sdk/identity/identity/src/credentials/deviceCodeCredential.ts @@ -7,7 +7,7 @@ import { credentialLogger, formatSuccess } from "../util/logging"; import { AuthenticationError, AuthenticationErrorName } from "../client/errors"; import { CanonicalCode } from "@opentelemetry/api"; -import msal from "@azure/msal-node"; +import { PublicClientApplication, DeviceCodeRequest } from "@azure/msal-node"; /** * Provides the user code and verification URI where the code must be @@ -48,7 +48,7 @@ const logger = credentialLogger("DeviceCodeCredential"); */ export class DeviceCodeCredential implements TokenCredential { private identityClient: IdentityClient; - private pca: msal.PublicClientApplication; + private pca: PublicClientApplication; private tenantId: string; private clientId: string; private userPromptCallback: DeviceCodePromptCallback; @@ -85,7 +85,7 @@ export class DeviceCodeCredential implements TokenCredential { }, }; - this.pca = new msal.PublicClientApplication(publicClientConfig); + this.pca = new PublicClientApplication(publicClientConfig); } /** @@ -131,7 +131,7 @@ export class DeviceCodeCredential implements TokenCredential { } } - private async acquireTokenByDeviceCode(deviceCodeRequest: msal.DeviceCodeRequest): Promise { + private async acquireTokenByDeviceCode(deviceCodeRequest: DeviceCodeRequest): Promise { try { const deviceResponse = await this.pca.acquireTokenByDeviceCode(deviceCodeRequest); return({ diff --git a/sdk/identity/identity/src/credentials/interactiveBrowserCredential.ts b/sdk/identity/identity/src/credentials/interactiveBrowserCredential.ts index 0b0d04f2108c..d2b839fc594e 100644 --- a/sdk/identity/identity/src/credentials/interactiveBrowserCredential.ts +++ b/sdk/identity/identity/src/credentials/interactiveBrowserCredential.ts @@ -13,7 +13,7 @@ import { Socket } from "net"; const SERVER_PORT = process.env.PORT || 80; import express from "express"; -import msal from "@azure/msal-node"; +import {PublicClientApplication, TokenCache, AuthorizationCodeRequest} from "@azure/msal-node"; import open from "open"; import path from "path"; import http from "http"; @@ -38,8 +38,8 @@ interface AuthenticationRecord { */ export class InteractiveBrowserCredential implements TokenCredential { private identityClient: IdentityClient; - private pca: msal.PublicClientApplication; - private msalCacheManager: msal.TokenCache; + private pca: PublicClientApplication; + private msalCacheManager: TokenCache; private tenantId: string; private clientId: string; private persistenceEnabled: boolean; @@ -84,7 +84,7 @@ export class InteractiveBrowserCredential implements TokenCredential { }, cache: undefined }; - this.pca = new msal.PublicClientApplication(publicClientConfig); + this.pca = new PublicClientApplication(publicClientConfig); this.msalCacheManager = this.pca.getTokenCache(); } @@ -137,7 +137,7 @@ export class InteractiveBrowserCredential implements TokenCredential { const app = express(); app.get("/", async (req, res) => { - const tokenRequest: msal.AuthorizationCodeRequest = { + const tokenRequest: AuthorizationCodeRequest = { code: req.query.code as string, redirectUri: this.redirectUri, scopes: scopeArray diff --git a/sdk/keyvault/keyvault-certificates/package.json b/sdk/keyvault/keyvault-certificates/package.json index e1d35505c19e..aa843bc8918b 100644 --- a/sdk/keyvault/keyvault-certificates/package.json +++ b/sdk/keyvault/keyvault-certificates/package.json @@ -108,6 +108,7 @@ "@azure/test-utils-recorder": "^1.0.0", "@microsoft/api-extractor": "7.7.11", "@rollup/plugin-commonjs": "11.0.2", + "@rollup/plugin-json": "^4.0.0", "@rollup/plugin-multi-entry": "^3.0.0", "@rollup/plugin-node-resolve": "^8.0.0", "@rollup/plugin-replace": "^2.2.0", diff --git a/sdk/keyvault/keyvault-certificates/rollup.base.config.js b/sdk/keyvault/keyvault-certificates/rollup.base.config.js index 5c7c1f06bcc8..d84228da1505 100644 --- a/sdk/keyvault/keyvault-certificates/rollup.base.config.js +++ b/sdk/keyvault/keyvault-certificates/rollup.base.config.js @@ -4,6 +4,7 @@ import nodeResolve from "@rollup/plugin-node-resolve"; import multiEntry from "@rollup/plugin-multi-entry"; import cjs from "@rollup/plugin-commonjs"; +import json from "@rollup/plugin-json"; import replace from "@rollup/plugin-replace"; import { terser } from "rollup-plugin-terser"; import sourcemaps from "rollup-plugin-sourcemaps"; @@ -50,6 +51,7 @@ export function nodeConfig(test = false) { "if (isNode)": "if (true)" }), nodeResolve({ preferBuiltins: true }), + json(), cjs() ] }; diff --git a/sdk/keyvault/keyvault-keys/package.json b/sdk/keyvault/keyvault-keys/package.json index 337e94006149..e2c2f573880b 100644 --- a/sdk/keyvault/keyvault-keys/package.json +++ b/sdk/keyvault/keyvault-keys/package.json @@ -98,6 +98,7 @@ "@azure/test-utils-recorder": "^1.0.0", "@microsoft/api-extractor": "7.7.11", "@rollup/plugin-commonjs": "11.0.2", + "@rollup/plugin-json": "^4.0.0", "@rollup/plugin-multi-entry": "^3.0.0", "@rollup/plugin-node-resolve": "^8.0.0", "@rollup/plugin-replace": "^2.2.0", diff --git a/sdk/keyvault/keyvault-keys/rollup.base.config.js b/sdk/keyvault/keyvault-keys/rollup.base.config.js index 3c054d40359f..d5837c4243ed 100644 --- a/sdk/keyvault/keyvault-keys/rollup.base.config.js +++ b/sdk/keyvault/keyvault-keys/rollup.base.config.js @@ -4,6 +4,7 @@ import nodeResolve from "@rollup/plugin-node-resolve"; import multiEntry from "@rollup/plugin-multi-entry"; import cjs from "@rollup/plugin-commonjs"; +import json from "@rollup/plugin-json"; import replace from "@rollup/plugin-replace"; import { terser } from "rollup-plugin-terser"; import sourcemaps from "rollup-plugin-sourcemaps"; @@ -50,6 +51,7 @@ export function nodeConfig(test = false) { "if (isNode)": "if (true)" }), nodeResolve({ preferBuiltins: true }), + json(), cjs() ] }; diff --git a/sdk/keyvault/keyvault-secrets/package.json b/sdk/keyvault/keyvault-secrets/package.json index 84492bdd878f..d3a8498944d6 100644 --- a/sdk/keyvault/keyvault-secrets/package.json +++ b/sdk/keyvault/keyvault-secrets/package.json @@ -106,6 +106,7 @@ "@azure/test-utils-recorder": "^1.0.0", "@microsoft/api-extractor": "7.7.11", "@rollup/plugin-commonjs": "11.0.2", + "@rollup/plugin-json": "^4.0.0", "@rollup/plugin-multi-entry": "^3.0.0", "@rollup/plugin-node-resolve": "^8.0.0", "@rollup/plugin-replace": "^2.2.0", diff --git a/sdk/keyvault/keyvault-secrets/rollup.base.config.js b/sdk/keyvault/keyvault-secrets/rollup.base.config.js index 05d224d158af..b5b8bc2d925f 100644 --- a/sdk/keyvault/keyvault-secrets/rollup.base.config.js +++ b/sdk/keyvault/keyvault-secrets/rollup.base.config.js @@ -4,6 +4,7 @@ import nodeResolve from "@rollup/plugin-node-resolve"; import multiEntry from "@rollup/plugin-multi-entry"; import cjs from "@rollup/plugin-commonjs"; +import json from "@rollup/plugin-json"; import replace from "@rollup/plugin-replace"; import { terser } from "rollup-plugin-terser"; import sourcemaps from "rollup-plugin-sourcemaps"; @@ -50,6 +51,7 @@ export function nodeConfig(test = false) { "if (isNode)": "if (true)" }), nodeResolve({ preferBuiltins: true }), + json(), cjs() ] }; From d62d58300999bf3e5af9e8118fe171ff385185b9 Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Fri, 4 Sep 2020 06:52:11 +1200 Subject: [PATCH 08/11] Attempt to fix build --- .../src/credentials/deviceCodeCredential.ts | 179 ++++---- .../interactiveBrowserCredential.ts | 292 ++++++------ .../test/public/deviceCodeCredential.spec.ts | 415 ------------------ 3 files changed, 240 insertions(+), 646 deletions(-) delete mode 100644 sdk/identity/identity/test/public/deviceCodeCredential.spec.ts diff --git a/sdk/identity/identity/src/credentials/deviceCodeCredential.ts b/sdk/identity/identity/src/credentials/deviceCodeCredential.ts index 6c24aae2313b..be95196b1fb1 100644 --- a/sdk/identity/identity/src/credentials/deviceCodeCredential.ts +++ b/sdk/identity/identity/src/credentials/deviceCodeCredential.ts @@ -47,13 +47,13 @@ const logger = credentialLogger("DeviceCodeCredential"); * that the user can enter into https://microsoft.com/devicelogin. */ export class DeviceCodeCredential implements TokenCredential { - private identityClient: IdentityClient; - private pca: PublicClientApplication; - private tenantId: string; - private clientId: string; - private userPromptCallback: DeviceCodePromptCallback; + private identityClient: IdentityClient; + private pca: PublicClientApplication; + private tenantId: string; + private clientId: string; + private userPromptCallback: DeviceCodePromptCallback; - /** + /** * Creates an instance of DeviceCodeCredential with the details needed * to initiate the device code authorization flow with Azure Active Directory. * @@ -64,82 +64,91 @@ export class DeviceCodeCredential implements TokenCredential { {@link DeviceCodeInfo} to the user. * @param options Options for configuring the client which makes the authentication request. */ - constructor( - tenantId: string | "organizations", - clientId: string, - userPromptCallback: DeviceCodePromptCallback, - options?: TokenCredentialOptions - ) { - this.identityClient = new IdentityClient(options); - this.tenantId = tenantId; - this.clientId = clientId; - this.userPromptCallback = userPromptCallback; - - const publicClientConfig = { - auth: { - clientId: this.clientId, - authority: "https://login.microsoftonline.com/" + this.tenantId, - }, - cache: { - cachePlugin: undefined - }, - }; - - this.pca = new PublicClientApplication(publicClientConfig); - } - - /** - * Authenticates with Azure Active Directory and returns an access token if - * successful. If authentication cannot be performed at this time, this method may - * return null. If an error occurs during authentication, an {@link AuthenticationError} - * containing failure details will be thrown. - * - * @param scopes The list of scopes for which the token will have access. - * @param options The options used to configure any requests this - * TokenCredential implementation might make. - */ - getToken( - scopes: string | string[], - options?: GetTokenOptions - ): Promise { - const { span, options: newOptions } = createSpan("DeviceCodeCredential-getToken", options); - - const scopeArray = typeof scopes === "object" ? scopes : [scopes]; - - const deviceCodeRequest = { - deviceCodeCallback: this.userPromptCallback, - scopes: scopeArray, - }; - - logger.info("Sending devicecode request"); - - try { - return this.acquireTokenByDeviceCode(deviceCodeRequest); - } catch (err) { - const code = - err.name === AuthenticationErrorName - ? CanonicalCode.UNAUTHENTICATED - : CanonicalCode.UNKNOWN; - span.setStatus({ - code, - message: err.message - }); - logger.getToken.info(err); - throw err; - } finally { - span.end(); - } - } - - private async acquireTokenByDeviceCode(deviceCodeRequest: DeviceCodeRequest): Promise { - try { - const deviceResponse = await this.pca.acquireTokenByDeviceCode(deviceCodeRequest); - return({ - expiresOnTimestamp: deviceResponse.expiresOn.getTime(), - token: deviceResponse.accessToken - }); - } catch (error) { - throw new Error(`Device Authentication Error "${JSON.stringify(error)}"`); - } - } -} + constructor( + tenantId: string | "organizations", + clientId: string, + userPromptCallback: DeviceCodePromptCallback, + options?: TokenCredentialOptions + ) { + this.identityClient = new IdentityClient(options); + this.tenantId = tenantId; + this.clientId = clientId; + this.userPromptCallback = userPromptCallback; + + const publicClientConfig = { + auth: { + clientId: this.clientId + authority: "https://login.microsoftonline.com/" + this.tenantId + }, + cache: { + cachePlugin: undefined + } + }; + + this.pca = new PublicClientApplication(publicClientConfig); + } + + /** + * Authenticates with Azure Active Directory and returns an access token if + * successful. If authentication cannot be performed at this time, this method may + * return null. If an error occurs during authentication, an {@link AuthenticationError} + * containing failure details will be thrown. + * + * @param scopes The list of scopes for which the token will have access. + * @param options The options used to configure any requests this + * TokenCredential implementation might make. + */ + getToken( + scopes: string | string[], + options?: GetToken + Options + + ): Promise { + const { span, options: newOptions } = createSpan( + "DeviceCodeCredential-getToken", + options + ); + + const scopeArray = typeof scopes === "object" ? scopes : [scopes]; + + const deviceCodeRequest = { + deviceCodeCallback: this.userPromptCallback, + scopes: scopeArray + }; + + logger.info("Sending devicecode request"); + + try { + return this.acquireTokenByDeviceCode( + deviceCodeRequest + ); + } catch (err) { + const co de = + err.name === AuthenticationErrorName + ? CanonicalCode.UNAUTHENTICATED + : CanonicalCode.UNKNOWN; + span.setStatus({ + code, + message: err.message + }); + logger.getToken.info(err); + throw err; + } finally { + span.end(); + } + } + + private async acquireTokenByDeviceCode( + deviceCodeRequest: DeviceCodeRequest + ): Promise { + try { + const deviceResponse = await this.pca.acquireTokenByDeviceCode(deviceCodeRequest); + return { + expiresOnTimestamp: deviceResponse.expiresOn.getTime(), + token: deviceResponse.accessToken + }; + } catch (error) { + throw new Error(`Device Authentication Error "${JSON.stringify(error)}"`); + } + } + } diff --git a/sdk/identity/identity/src/credentials/interactiveBrowserCredential.ts b/sdk/identity/identity/src/credentials/interactiveBrowserCredential.ts index d2b839fc594e..acd69de60625 100644 --- a/sdk/identity/identity/src/credentials/interactiveBrowserCredential.ts +++ b/sdk/identity/identity/src/credentials/interactiveBrowserCredential.ts @@ -13,7 +13,7 @@ import { Socket } from "net"; const SERVER_PORT = process.env.PORT || 80; import express from "express"; -import {PublicClientApplication, TokenCache, AuthorizationCodeRequest} from "@azure/msal-node"; +import { PublicClientApplication, TokenCache, AuthorizationCodeRequest } from "@azure/msal-node"; import open from "open"; import path from "path"; import http from "http"; @@ -37,148 +37,148 @@ interface AuthenticationRecord { * window. This credential is not currently supported in Node.js. */ export class InteractiveBrowserCredential implements TokenCredential { - private identityClient: IdentityClient; - private pca: PublicClientApplication; - private msalCacheManager: TokenCache; - private tenantId: string; - private clientId: string; - private persistenceEnabled: boolean; - private redirectUri: string; - private authorityHost: string; - private authenticationRecord: AuthenticationRecord | undefined; - - constructor(options?: InteractiveBrowserCredentialOptions) { - this.identityClient = new IdentityClient(options); - this.tenantId = (options && options.tenantId) || DefaultTenantId; - this.clientId = (options && options.clientId) || DeveloperSignOnClientId; - - // Future update: this is for persistent caching - this.persistenceEnabled = false; - this.authenticationRecord = undefined; - - if (options && options.redirectUri) { - if (typeof options.redirectUri === "string") { - this.redirectUri = options.redirectUri; - } else { - this.redirectUri = options.redirectUri(); - } - } else { - this.redirectUri = "http://localhost"; - } - - if (options && options.authorityHost) { - if (options.authorityHost.endsWith("/")) { - this.authorityHost = options.authorityHost + this.tenantId; - } else { - this.authorityHost = options.authorityHost + "/" + this.tenantId; - } - } else { - this.authorityHost = "https://login.microsoftonline.com/" + this.tenantId; - } - - const publicClientConfig = { - auth: { - clientId: this.clientId, - authority: this.authorityHost, - redirectUri: this.redirectUri - }, - cache: undefined - }; - this.pca = new PublicClientApplication(publicClientConfig); - this.msalCacheManager = this.pca.getTokenCache(); - } - - /** - * Authenticates with Azure Active Directory and returns an access token if - * successful. If authentication cannot be performed at this time, this method may - * return null. If an error occurs during authentication, an {@link AuthenticationError} - * containing failure details will be thrown. - * - * @param scopes The list of scopes for which the token will have access. - * @param options The options used to configure any requests this - * TokenCredential implementation might make. - */ - public getToken( - scopes: string | string[], - options?: GetTokenOptions - ): Promise { - const scopeArray = typeof scopes === "object" ? scopes : [scopes]; - - return this.acquireTokenFromBrowser(scopeArray); - } - - private async openAuthCodeUrl(scopeArray: string[]): Promise { - const authCodeUrlParameters = { - scopes: scopeArray, - redirectUri: this.redirectUri - }; - - const response = await this.pca.getAuthCodeUrl(authCodeUrlParameters); - await open(response); - } - - private async acquireTokenFromBrowser(scopeArray: string[]): Promise { - // eslint-disable-next-line - return new Promise(async (resolve, reject) => { - // eslint-disable-next-line - let listen: http.Server | undefined; - let socketToDestroy: Socket | undefined; - - function cleanup() { - if (listen) { - listen.close(); - } - if (socketToDestroy) { - socketToDestroy.destroy(); - } - } - - // Create Express App and Routes - const app = express(); - - app.get("/", async (req, res) => { - const tokenRequest: AuthorizationCodeRequest = { - code: req.query.code as string, - redirectUri: this.redirectUri, - scopes: scopeArray - }; - - try { - const authResponse = await this.pca.acquireTokenByCode(tokenRequest); - res.sendStatus(200); - logger.info(`authResponse: ${authResponse}`); - if (this.persistenceEnabled) { - this.msalCacheManager.writeToPersistence(); - } - - resolve({ - expiresOnTimestamp: authResponse.expiresOn.valueOf(), - token: authResponse.accessToken - }); - } catch (error) { - res.status(500).send(error); - - reject( - new Error( - `Authentication Error "${req.query["error"]}":\n\n${req.query["error_description"]}` - ) - ); - } finally { - cleanup(); - } - }); - - listen = app.listen(SERVER_PORT, () => - logger.info(`Msal Node Auth Code Sample app listening on port ${SERVER_PORT}!`) - ); - listen.on("connection", (socket) => (socketToDestroy = socket)); - - try { - await this.openAuthCodeUrl(scopeArray); - } catch(e) { - cleanup(); - throw e; - } - }); - } -} + private identityClient: IdentityClient; + private pca: PublicClientApplication; + private msalCacheManager: TokenCache; + private tenantId: string; + private clientId: string; + private persistenceEnabled: boolean; + private redirectUri: string; + private authorityHost: string; + private authenticationRecord: AuthenticationRecord | undefined; + + constructor(options?: InteractiveBrowserCredentialOptions) { + this.identityClient = new IdentityClient(options); + this.tenantId = (options && options.tenantId) || DefaultTenantId; + this.clientId = (options && options.clientId) || DeveloperSignOnClientId; + + // Future update: this is for persistent caching + this.persistenceEnabled = false; + this.authenticationRecord = undefined; + + if (options && options.redirectUri) { + if (typeof options.redirectUri === "string") { + this.redirectUri = options.redirectUri; + } else { + this.redirectUri = options.redirectUri(); + } + } else { + this.redirectUri = "http://localhost"; + } + + if (options && options.authorityHost) { + if (options.authorityHost.endsWith("/")) { + this.authorityHost = options.authorityHost + this.tenantId; + } else { + this.authorityHost = options.authorityHost + "/" + this.tenantId; + } + } else { + this.authorityHost = "https://login.microsoftonline.com/" + this.tenantId; + } + + const publicClientConfig = { + auth: { + clientId: this.clientId, + authority: this.authorityHost, + redirectUri: this.redirectUri + }, + cache: undefined + }; + this.pca = new PublicClientApplication(publicClientConfig); + this.msalCacheManager = this.pca.getTokenCache(); + } + + /** + * Authenticates with Azure Active Directory and returns an access token if + * successful. If authentication cannot be performed at this time, this method may + * return null. If an error occurs during authentication, an {@link AuthenticationError} + * containing failure details will be thrown. + * + * @param scopes The list of scopes for which the token will have access. + * @param options The options used to configure any requests this + * TokenCredential implementation might make. + */ + public getToken( + scopes: string | string[], + options?: GetTokenOptions + ): Promise { + const scopeArray = typeof scopes === "object" ? scopes : [scopes]; + + return this.acquireTokenFromBrowser(scopeArray); + } + + private async openAuthCodeUrl(scopeArray: string[]): Promise { + const authCodeUrlParameters = { + scopes: scopeArray, + redirectUri: this.redirectUri + }; + + const response = await this.pca.getAuthCodeUrl(authCodeUrlParameters); + await open(response); + } + + private async acquireTokenFromBrowser(scopeArray: string[]): Promise { + // eslint-disable-next-line + return new Promise(async (resolve, reject) => { + // eslint-disable-next-line + let listen: http.Server | undefined; + let socketToDestroy: Socket | undefined; + + function cleanup() { + if (listen) { + listen.close(); + } + if (socketToDestroy) { + socketToDestroy.destroy(); + } + } + + // Create Express App and Routes + const app = express(); + + app.get("/", async (req, res) => { + const tokenRequest: AuthorizationCodeRequest = { + code: req.query.code as string, + redirectUri: this.redirectUri, + scopes: scopeArray + }; + + try { + const authResponse = await this.pca.acquireTokenByCode(tokenRequest); + res.sendStatus(200); + logger.info(`authResponse: ${authResponse}`); + if (this.persistenceEnabled) { + this.msalCacheManager.writeToPersistence(); + } + + resolve({ + expiresOnTimestamp: authResponse.expiresOn.valueOf(), + token: authResponse.accessToken + }); + } catch (error) { + res.status(500).send(error); + + reject( + new Error( + `Authentication Error "${req.query["error"]}":\n\n${req.query["error_description"]}` + ) + ); + } finally { + cleanup(); + } + }); + + listen = app.listen(SERVER_PORT, () => + logger.info(`Msal Node Auth Code Sample app listening on port ${SERVER_PORT}!`) + ); + listen.on("connection", (socket) => (socketToDestroy = socket)); + + try { + await this.openAuthCodeUrl(scopeArray); + } catch (e) { + cleanup(); + throw e; + } + }); + } + } diff --git a/sdk/identity/identity/test/public/deviceCodeCredential.spec.ts b/sdk/identity/identity/test/public/deviceCodeCredential.spec.ts deleted file mode 100644 index 3ee72fcce43d..000000000000 --- a/sdk/identity/identity/test/public/deviceCodeCredential.spec.ts +++ /dev/null @@ -1,415 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import assert from "assert"; -import { TestTracer, setTracer, SpanGraph } from "@azure/core-tracing"; -import { AbortController } from "@azure/abort-controller"; -import { - MockAuthHttpClient, - assertRejects, - setDelayInstantlyCompletes, - restoreDelayBehavior, - createDelayController, - DelayController -} from "../authTestUtils"; -import { AuthenticationError, DeviceCodeCredential } from "../../src"; - -interface DeviceCodeResponse { - device_code: string; - user_code: string; - verification_uri: string; - expires_in: number; - interval: number; - message: string; -} - -interface OAuthErrorResponse { - error: string; - error_description: string; - error_codes?: number[]; - timestamp?: string; - trace_id?: string; - correlation_id?: string; -} - -const deviceCodeResponse: DeviceCodeResponse = { - interval: 1, - expires_in: 20, - verification_uri: "https://contoso.com/devicelogin", - device_code: "XXXXXXXXXXXXXXXXXX", - user_code: "B3920934", - message: "Visit https://contoso.com/devicelogin and enter code B3920934" -}; - -const pendingResponse: OAuthErrorResponse = { - error: "authorization_pending", - error_description: "Waiting for user to authenticate" -}; - -describe("DeviceCodeCredential", function() { - before(() => { - setDelayInstantlyCompletes(); - }); - after(() => { - restoreDelayBehavior(); - }); - - it("sends a device code request and returns a token when the user completes it", async function() { - const mockHttpClient = new MockAuthHttpClient({ - authResponse: [ - { status: 200, parsedBody: deviceCodeResponse }, - { status: 400, parsedBody: pendingResponse }, - { status: 400, parsedBody: pendingResponse }, - { status: 400, parsedBody: pendingResponse }, - { status: 200, parsedBody: { access_token: "token", expires_in: 5 } } - ] - }); - - const credential = new DeviceCodeCredential( - "tenant", - "client", - (details) => assert.equal(details.message, deviceCodeResponse.message), - { ...mockHttpClient.tokenCredentialOptions } - ); - - const accessToken = await credential.getToken("scope"); - const currentTimestamp = Date.now() + 5000; - - if (accessToken === null) { - assert.fail("getToken did not return an AccessToken"); - } else { - assert.strictEqual(accessToken.token, "token"); - assert.ok( - accessToken.expiresOnTimestamp >= currentTimestamp - 1000 && - accessToken.expiresOnTimestamp <= currentTimestamp, - `AccessToken.expiresOnTimestamp is not ~${currentTimestamp}: ${accessToken.expiresOnTimestamp}` - ); - } - }); - - it("refreshes the access token on subsequent getToken requests", async function() { - const mockHttpClient = new MockAuthHttpClient({ - authResponse: [ - { status: 200, parsedBody: deviceCodeResponse }, - { - status: 200, - parsedBody: { access_token: "token", expires_in: 5, refresh_token: "ABC123" } - }, - { - status: 200, - parsedBody: { access_token: "token", expires_in: 5, refresh_token: "ABC123" } - } - ] - }); - - const credential = new DeviceCodeCredential( - "tenant", - "client", - (details) => assert.equal(details.message, deviceCodeResponse.message), - { ...mockHttpClient.tokenCredentialOptions } - ); - - await credential.getToken("scope"); - const refreshedToken = await credential.getToken("scope"); - - if (refreshedToken === null) { - assert.fail("getToken did not return a refreshed AccessToken"); - } else { - // Basic verification that a refresh request was made with the - // refresh_token returned by the previous request - const refreshRequest = mockHttpClient.requests[2]; - assert.ok( - refreshRequest.body.indexOf(`grant_type=refresh_token`) > -1, - "Request does not contain refresh_token grant type" - ); - assert.ok( - refreshRequest.body.indexOf(`refresh_token=ABC123`) > -1, - "Request does not contain refresh token" - ); - } - }); - - it("re-initiates the device code flow when the refresh token expires", async function() { - const mockHttpClient = new MockAuthHttpClient({ - authResponse: [ - { status: 200, parsedBody: deviceCodeResponse }, - { - status: 200, - parsedBody: { access_token: "token", expires_in: 5, refresh_token: "ABC123" } - }, - { - status: 400, - parsedBody: { error: "interaction_required", error_description: "Interaction required" } - }, - { status: 200, parsedBody: deviceCodeResponse }, - { status: 200, parsedBody: { access_token: "token", expires_in: 5 } } - ] - }); - - const credential = new DeviceCodeCredential( - "tenant", - "client", - (details) => assert.equal(details.message, deviceCodeResponse.message), - { ...mockHttpClient.tokenCredentialOptions } - ); - - await credential.getToken("scope"); - const refreshedToken = await credential.getToken("scope"); - - if (refreshedToken === null) { - assert.fail("getToken did not return a refreshed AccessToken"); - } else { - // Basic verification that the device code flow was re-initiated - // once the refresh token request failed with "interaction_required" - const refreshRequest = mockHttpClient.requests[3]; - assert.ok( - refreshRequest.url.endsWith("devicecode"), - "Device code authorization request was not re-initiated" - ); - } - }); - - it("throws an AuthenticationError when the user declines the authorization flow", async function() { - const mockHttpClient = new MockAuthHttpClient({ - authResponse: [ - { status: 200, parsedBody: deviceCodeResponse }, - { status: 400, parsedBody: pendingResponse }, - { status: 400, parsedBody: pendingResponse }, - { - status: 400, - parsedBody: { - error: "authorization_declined", - error_description: "", - correlation_id: "correlation_id", - trace_id: "trace_id", - error_codes: [1, 2, 3], - timestamp: "timestamp" - } as OAuthErrorResponse - } - ] - }); - - const credential = new DeviceCodeCredential( - "tenant", - "client", - (details) => assert.equal(details.message, deviceCodeResponse.message), - { ...mockHttpClient.tokenCredentialOptions } - ); - - await assertRejects(credential.getToken("scope"), (error) => { - const authError = error as AuthenticationError; - assert.strictEqual(error.name, "AuthenticationError"); - assert.strictEqual(authError.errorResponse.error, "authorization_declined"); - assert.strictEqual(authError.errorResponse.correlationId, "correlation_id"); - assert.strictEqual(authError.errorResponse.traceId, "trace_id"); - assert.strictEqual(authError.errorResponse.timestamp, "timestamp"); - assert.deepStrictEqual(authError.errorResponse.errorCodes, [1, 2, 3]); - return true; - }); - }); - - it("throws an AuthenticationError when the authorization token expires", async function() { - const mockHttpClient = new MockAuthHttpClient({ - authResponse: [ - { status: 200, parsedBody: deviceCodeResponse }, - { status: 400, parsedBody: pendingResponse }, - { status: 400, parsedBody: pendingResponse }, - { status: 400, parsedBody: { error: "expired_token", error_description: "" } } - ] - }); - - const credential = new DeviceCodeCredential( - "tenant", - "client", - (details) => assert.equal(details.message, deviceCodeResponse.message), - { ...mockHttpClient.tokenCredentialOptions } - ); - - await assertRejects(credential.getToken("scope"), (error) => { - const authError = error as AuthenticationError; - assert.strictEqual(error.name, "AuthenticationError"); - assert.strictEqual(authError.errorResponse.error, "expired_token"); - return true; - }); - }); - - it("throws an AuthenticationError when the client sends the wrong device code", async function() { - const mockHttpClient = new MockAuthHttpClient({ - authResponse: [ - { status: 200, parsedBody: deviceCodeResponse }, - { status: 400, parsedBody: { error: "bad_verification_code", error_description: "" } } - ] - }); - - const credential = new DeviceCodeCredential( - "tenant", - "client", - (details) => assert.equal(details.message, deviceCodeResponse.message), - { ...mockHttpClient.tokenCredentialOptions } - ); - - await assertRejects(credential.getToken("scope"), (error) => { - const authError = error as AuthenticationError; - assert.strictEqual(error.name, "AuthenticationError"); - assert.strictEqual(authError.errorResponse.error, "bad_verification_code"); - return true; - }); - }); - - it("rethrows an unexpected AuthenticationError", async function() { - const mockHttpClient = new MockAuthHttpClient({ - authResponse: [ - { status: 200, parsedBody: deviceCodeResponse }, - { status: 400, parsedBody: pendingResponse }, - { status: 400, parsedBody: pendingResponse }, - { - status: 401, - parsedBody: { - error: "invalid_client", - error_description: "The request body must contain..." - } - } - ] - }); - - const credential = new DeviceCodeCredential( - "tenant", - "client", - (details) => assert.equal(details.message, deviceCodeResponse.message), - { ...mockHttpClient.tokenCredentialOptions } - ); - - await assertRejects(credential.getToken("scope"), (error) => { - const authError = error as AuthenticationError; - assert.strictEqual(error.name, "AuthenticationError"); - assert.strictEqual(authError.errorResponse.error, "invalid_client"); - return true; - }); - }); - - describe("tests with delays", function() { - let delayController: DelayController; - before(() => { - delayController = createDelayController(); - }); - - it("cancels polling when abort signal is raised", async function() { - const mockHttpClient = new MockAuthHttpClient({ - authResponse: [ - { status: 200, parsedBody: deviceCodeResponse }, - { status: 400, parsedBody: pendingResponse }, - { status: 400, parsedBody: pendingResponse }, - { status: 200, parsedBody: { access_token: "token", expires_in: 5 } } - ] - }); - - const credential = new DeviceCodeCredential( - "tenant", - "client", - (details) => assert.equal(details.message, deviceCodeResponse.message), - { ...mockHttpClient.tokenCredentialOptions } - ); - - const abortController = new AbortController(); - const getTokenPromise = credential.getToken("scope", { abortSignal: abortController.signal }); - // getToken ends up calling pollForToken which normally has a 1000ms delay. - // This code allows us to control the delay programatically in the test. - let delay = await delayController.waitForDelay(); - delay.resolve(); - delay = await delayController.waitForDelay(); - // abort the request before the second poll is allowed to complete - abortController.abort(); - delay.resolve(); - const token = await getTokenPromise; - assert.strictEqual(token, null); - assert.strictEqual(mockHttpClient.requests.length, 2); - }); - }); - - it("sends a device code request and returns a token with tracing", async function() { - const tracer = new TestTracer(); - setTracer(tracer); - const mockHttpClient = new MockAuthHttpClient({ - authResponse: [ - { status: 200, parsedBody: deviceCodeResponse }, - { status: 400, parsedBody: pendingResponse }, - { status: 400, parsedBody: pendingResponse }, - { status: 400, parsedBody: pendingResponse }, - { status: 200, parsedBody: { access_token: "token", expires_in: 5 } } - ] - }); - - const rootSpan = tracer.startSpan("root"); - - const credential = new DeviceCodeCredential( - "tenant", - "client", - (details) => assert.equal(details.message, deviceCodeResponse.message), - { ...mockHttpClient.tokenCredentialOptions } - ); - - await credential.getToken("scope", { - tracingOptions: { - spanOptions: { - parent: rootSpan.context() - } - } - }); - - rootSpan.end(); - - const rootSpans = tracer.getRootSpans(); - assert.strictEqual(rootSpans.length, 1, "Should only have one root span."); - assert.strictEqual(rootSpan, rootSpans[0], "The root span should match what was passed in."); - - const expectedGraph: SpanGraph = { - roots: [ - { - name: rootSpan.name, - children: [ - { - name: "Azure.Identity.DeviceCodeCredential-getToken", - children: [ - { - name: "Azure.Identity.DeviceCodeCredential-sendDeviceCodeRequest", - children: [ - { - children: [], - name: "/tenant/oauth2/v2.0/devicecode" - } - ] - }, - { - name: "Azure.Identity.DeviceCodeCredential-pollForToken", - children: [ - // We see 4 traces from core-http here because the client in - // this test polls 4 times for the authorization code. - { - children: [], - name: "/tenant/oauth2/v2.0/token" - }, - { - children: [], - name: "/tenant/oauth2/v2.0/token" - }, - { - children: [], - name: "/tenant/oauth2/v2.0/token" - }, - { - children: [], - name: "/tenant/oauth2/v2.0/token" - } - ] - } - ] - } - ] - } - ] - }; - - assert.deepStrictEqual(tracer.getSpanGraph(rootSpan.context().traceId), expectedGraph); - assert.strictEqual(tracer.getActiveSpans().length, 0, "All spans should have had end called"); - }); -}); From 518d0fcdd9c54eb25284e356b1e73afc959bf654 Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Fri, 4 Sep 2020 06:53:27 +1200 Subject: [PATCH 09/11] Attempt to fix build --- .../src/credentials/deviceCodeCredential.ts | 179 +++++++++--------- 1 file changed, 85 insertions(+), 94 deletions(-) diff --git a/sdk/identity/identity/src/credentials/deviceCodeCredential.ts b/sdk/identity/identity/src/credentials/deviceCodeCredential.ts index be95196b1fb1..6c24aae2313b 100644 --- a/sdk/identity/identity/src/credentials/deviceCodeCredential.ts +++ b/sdk/identity/identity/src/credentials/deviceCodeCredential.ts @@ -47,13 +47,13 @@ const logger = credentialLogger("DeviceCodeCredential"); * that the user can enter into https://microsoft.com/devicelogin. */ export class DeviceCodeCredential implements TokenCredential { - private identityClient: IdentityClient; - private pca: PublicClientApplication; - private tenantId: string; - private clientId: string; - private userPromptCallback: DeviceCodePromptCallback; + private identityClient: IdentityClient; + private pca: PublicClientApplication; + private tenantId: string; + private clientId: string; + private userPromptCallback: DeviceCodePromptCallback; - /** + /** * Creates an instance of DeviceCodeCredential with the details needed * to initiate the device code authorization flow with Azure Active Directory. * @@ -64,91 +64,82 @@ export class DeviceCodeCredential implements TokenCredential { {@link DeviceCodeInfo} to the user. * @param options Options for configuring the client which makes the authentication request. */ - constructor( - tenantId: string | "organizations", - clientId: string, - userPromptCallback: DeviceCodePromptCallback, - options?: TokenCredentialOptions - ) { - this.identityClient = new IdentityClient(options); - this.tenantId = tenantId; - this.clientId = clientId; - this.userPromptCallback = userPromptCallback; - - const publicClientConfig = { - auth: { - clientId: this.clientId - authority: "https://login.microsoftonline.com/" + this.tenantId - }, - cache: { - cachePlugin: undefined - } - }; - - this.pca = new PublicClientApplication(publicClientConfig); - } - - /** - * Authenticates with Azure Active Directory and returns an access token if - * successful. If authentication cannot be performed at this time, this method may - * return null. If an error occurs during authentication, an {@link AuthenticationError} - * containing failure details will be thrown. - * - * @param scopes The list of scopes for which the token will have access. - * @param options The options used to configure any requests this - * TokenCredential implementation might make. - */ - getToken( - scopes: string | string[], - options?: GetToken - Options - - ): Promise { - const { span, options: newOptions } = createSpan( - "DeviceCodeCredential-getToken", - options - ); - - const scopeArray = typeof scopes === "object" ? scopes : [scopes]; - - const deviceCodeRequest = { - deviceCodeCallback: this.userPromptCallback, - scopes: scopeArray - }; - - logger.info("Sending devicecode request"); - - try { - return this.acquireTokenByDeviceCode( - deviceCodeRequest - ); - } catch (err) { - const co de = - err.name === AuthenticationErrorName - ? CanonicalCode.UNAUTHENTICATED - : CanonicalCode.UNKNOWN; - span.setStatus({ - code, - message: err.message - }); - logger.getToken.info(err); - throw err; - } finally { - span.end(); - } - } - - private async acquireTokenByDeviceCode( - deviceCodeRequest: DeviceCodeRequest - ): Promise { - try { - const deviceResponse = await this.pca.acquireTokenByDeviceCode(deviceCodeRequest); - return { - expiresOnTimestamp: deviceResponse.expiresOn.getTime(), - token: deviceResponse.accessToken - }; - } catch (error) { - throw new Error(`Device Authentication Error "${JSON.stringify(error)}"`); - } - } - } + constructor( + tenantId: string | "organizations", + clientId: string, + userPromptCallback: DeviceCodePromptCallback, + options?: TokenCredentialOptions + ) { + this.identityClient = new IdentityClient(options); + this.tenantId = tenantId; + this.clientId = clientId; + this.userPromptCallback = userPromptCallback; + + const publicClientConfig = { + auth: { + clientId: this.clientId, + authority: "https://login.microsoftonline.com/" + this.tenantId, + }, + cache: { + cachePlugin: undefined + }, + }; + + this.pca = new PublicClientApplication(publicClientConfig); + } + + /** + * Authenticates with Azure Active Directory and returns an access token if + * successful. If authentication cannot be performed at this time, this method may + * return null. If an error occurs during authentication, an {@link AuthenticationError} + * containing failure details will be thrown. + * + * @param scopes The list of scopes for which the token will have access. + * @param options The options used to configure any requests this + * TokenCredential implementation might make. + */ + getToken( + scopes: string | string[], + options?: GetTokenOptions + ): Promise { + const { span, options: newOptions } = createSpan("DeviceCodeCredential-getToken", options); + + const scopeArray = typeof scopes === "object" ? scopes : [scopes]; + + const deviceCodeRequest = { + deviceCodeCallback: this.userPromptCallback, + scopes: scopeArray, + }; + + logger.info("Sending devicecode request"); + + try { + return this.acquireTokenByDeviceCode(deviceCodeRequest); + } catch (err) { + const code = + err.name === AuthenticationErrorName + ? CanonicalCode.UNAUTHENTICATED + : CanonicalCode.UNKNOWN; + span.setStatus({ + code, + message: err.message + }); + logger.getToken.info(err); + throw err; + } finally { + span.end(); + } + } + + private async acquireTokenByDeviceCode(deviceCodeRequest: DeviceCodeRequest): Promise { + try { + const deviceResponse = await this.pca.acquireTokenByDeviceCode(deviceCodeRequest); + return({ + expiresOnTimestamp: deviceResponse.expiresOn.getTime(), + token: deviceResponse.accessToken + }); + } catch (error) { + throw new Error(`Device Authentication Error "${JSON.stringify(error)}"`); + } + } +} From 960ba3f65916d5a5ba7b2ae1b2dacc325d28fb02 Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Fri, 4 Sep 2020 08:50:21 +1200 Subject: [PATCH 10/11] Address feedback --- .../identity/src/credentials/deviceCodeCredential.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/sdk/identity/identity/src/credentials/deviceCodeCredential.ts b/sdk/identity/identity/src/credentials/deviceCodeCredential.ts index 6c24aae2313b..1726bbacd8a9 100644 --- a/sdk/identity/identity/src/credentials/deviceCodeCredential.ts +++ b/sdk/identity/identity/src/credentials/deviceCodeCredential.ts @@ -52,6 +52,7 @@ export class DeviceCodeCredential implements TokenCredential { private tenantId: string; private clientId: string; private userPromptCallback: DeviceCodePromptCallback; + private authorityHost: string; /** * Creates an instance of DeviceCodeCredential with the details needed @@ -74,11 +75,20 @@ export class DeviceCodeCredential implements TokenCredential { this.tenantId = tenantId; this.clientId = clientId; this.userPromptCallback = userPromptCallback; + if (options && options.authorityHost) { + if (options.authorityHost.endsWith("/")) { + this.authorityHost = options.authorityHost + this.tenantId; + } else { + this.authorityHost = options.authorityHost + "/" + this.tenantId; + } + } else { + this.authorityHost = "https://login.microsoftonline.com/" + this.tenantId; + } const publicClientConfig = { auth: { clientId: this.clientId, - authority: "https://login.microsoftonline.com/" + this.tenantId, + authority: this.authorityHost, }, cache: { cachePlugin: undefined From a2cc52ed0471ea1d171771ac28c99c2ea22349e7 Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Fri, 4 Sep 2020 09:50:53 +1200 Subject: [PATCH 11/11] Bump version and add changelog --- common/config/rush/pnpm-lock.yaml | 94 ++++++++++++++++++++++++------ sdk/identity/identity/CHANGELOG.md | 7 +++ sdk/identity/identity/package.json | 2 +- 3 files changed, 84 insertions(+), 19 deletions(-) diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 17663b27de3c..e5e9bd923f76 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -114,6 +114,28 @@ packages: node: '>=8.0.0' resolution: integrity: sha512-A4xigW0YZZpkj1zK7dKuzbBpGwnhEcRk6WWuIshdHC32raR3EQ1j6VA9XZqE+RFsUgH6OAmIK5BWIz+mZjnd6Q== + /@azure/core-http/1.1.7: + dependencies: + '@azure/abort-controller': 1.0.1 + '@azure/core-auth': 1.1.3 + '@azure/core-tracing': 1.0.0-preview.9 + '@azure/logger': 1.0.0 + '@opentelemetry/api': 0.10.2 + '@types/node-fetch': 2.5.7 + '@types/tunnel': 0.0.1 + form-data: 3.0.0 + node-fetch: 2.6.0 + process: 0.11.10 + tough-cookie: 4.0.0 + tslib: 2.0.0 + tunnel: 0.0.6 + uuid: 8.2.0 + xml2js: 0.4.23 + dev: false + engines: + node: '>=8.0.0' + resolution: + integrity: sha512-UmYMY22Zczg/hCtYuM/0KoV2kVc6juj4mrb5uYgBmmxQ9NIIZrpjgCdVSlYQNClpyrvaIMnecRFMqrZywzhiJA== /@azure/core-tracing/1.0.0-preview.8: dependencies: '@opencensus/web-types': 0.0.7 @@ -162,6 +184,25 @@ packages: dev: false resolution: integrity: sha512-F/1jaTC9NxgNjMkO7SAs9Q9BndJ16AtRwQu0l21FNyRCN8kWl4Noiblsbsjtv+BPYa+ARrocR5POMlJ5eveR9w== + /@azure/identity/1.1.0: + dependencies: + '@azure/core-http': 1.1.7 + '@azure/core-tracing': 1.0.0-preview.9 + '@azure/logger': 1.0.0 + '@opentelemetry/api': 0.10.2 + events: 3.1.0 + jws: 4.0.0 + msal: 1.3.2 + qs: 6.9.4 + tslib: 2.0.0 + uuid: 8.2.0 + dev: false + engines: + node: '>=8.0.0' + optionalDependencies: + keytar: 5.6.0 + resolution: + integrity: sha512-S4jYqegLWXIwVnkiArFlcTA7KOZmv+LMhQeQJhnmYy/CxrJHyIAEQyJ7qsrSt58bSyDZI2NkmKUBKaYGZU3/5g== /@azure/logger-js/1.3.2: dependencies: tslib: 1.13.0 @@ -8081,6 +8122,7 @@ packages: 'file:projects/ai-anomaly-detector.tgz': dependencies: '@azure/core-tracing': 1.0.0-preview.9 + '@azure/identity': 1.1.0 '@microsoft/api-extractor': 7.7.11 '@opentelemetry/api': 0.10.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 @@ -8129,12 +8171,13 @@ packages: dev: false name: '@rush-temp/ai-anomaly-detector' resolution: - integrity: sha512-/Wx3xQn+7yw/RY2pmeYMfi3sfyZ5plIwzRBSdrpQ1cXZkNbYoF0Oi4qn3jn8DCT7tx/uhz74niusxU8tFRZDhw== + integrity: sha512-J6fb3uCGOsZfCg4OnH1S2tDToB378aSJCXCXdtJ4LKMHI48ciMIM5lHEq0Ee7/oNNGKslg0TruA/v6tAEL/CXg== tarball: 'file:projects/ai-anomaly-detector.tgz' version: 0.0.0 'file:projects/ai-form-recognizer.tgz': dependencies: '@azure/core-tracing': 1.0.0-preview.9 + '@azure/identity': 1.1.0 '@microsoft/api-extractor': 7.7.11 '@opentelemetry/api': 0.10.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 @@ -8189,12 +8232,13 @@ packages: dev: false name: '@rush-temp/ai-form-recognizer' resolution: - integrity: sha512-GnzdwbAcD57Y4atdPDiJHeunNdaPnoI7zpRa29iPy0E7bYcZw5mnitAdnu1yWIJo0E1nFZEDRAQs9pfrUBrZ+A== + integrity: sha512-yFx1KY5foauaTLXj7u6eN7E0Xp73ULm3sz7HeWVXE2bvPq+G8dfF2cGVDTor/wJ8hfeBltEtRJmR2oJyTh6S1g== tarball: 'file:projects/ai-form-recognizer.tgz' version: 0.0.0 'file:projects/ai-text-analytics.tgz': dependencies: '@azure/core-tracing': 1.0.0-preview.9 + '@azure/identity': 1.1.0 '@microsoft/api-extractor': 7.7.11 '@opentelemetry/api': 0.10.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 @@ -8248,12 +8292,13 @@ packages: dev: false name: '@rush-temp/ai-text-analytics' resolution: - integrity: sha512-4k3cK6AhruT5dDI2rhIUSKhkUDsIgD9IJehHKr5ze+iP25Lg5a3RC69hTGT9Be8GmSvYsRXWwDOXFkdelWjcUA== + integrity: sha512-dzKtF7SLzOzDdrwRDXidkewcR0TJe3AypIybQShD0XBXmzTmio0Vpp0WBpF2bMyAocTIL59PECBLYy95QTvXUA== tarball: 'file:projects/ai-text-analytics.tgz' version: 0.0.0 'file:projects/app-configuration.tgz': dependencies: '@azure/core-tracing': 1.0.0-preview.9 + '@azure/identity': 1.1.0 '@microsoft/api-extractor': 7.7.11 '@opentelemetry/api': 0.10.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 @@ -8305,11 +8350,12 @@ packages: dev: false name: '@rush-temp/app-configuration' resolution: - integrity: sha512-X1kaQCUT9/RzBVWI53/uMzx9o+/eDWFJxo47r0wAacjNQW9fUCeXH9ikUhq0uEsxP/fEd96vngJKyPNZ924kOw== + integrity: sha512-Dvua3Tobr3ppJqmYiEfDhOBd69Mz2RrONLDEz1//uL/FXNdxSuaZuMRrrD5Xhdfk2GnChivKp9jt+luKCfA/1A== tarball: 'file:projects/app-configuration.tgz' version: 0.0.0 'file:projects/core-amqp.tgz': dependencies: + '@azure/identity': 1.1.0 '@microsoft/api-extractor': 7.7.11 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-inject': 4.0.2_rollup@1.32.1 @@ -8371,7 +8417,7 @@ packages: dev: false name: '@rush-temp/core-amqp' resolution: - integrity: sha512-/O1+8bVqeUmDGGU/2E5Mp+KYUzuZ41AkxdXZI2pGt5EuIm9H/21KzBYaR1V3c/BLO0MlLAA57lC/Fp6PCYs6pw== + integrity: sha512-K0UxDvA6BmhISAJDXoIEIVQRTm7RbY51cNRg7fkMWRzl3dp/UxC7qUnJb/EAawgCOrYeiOS+PR0VLx5s5gDzaQ== tarball: 'file:projects/core-amqp.tgz' version: 0.0.0 'file:projects/core-arm.tgz': @@ -8963,6 +9009,7 @@ packages: version: 0.0.0 'file:projects/digitaltwins.tgz': dependencies: + '@azure/identity': 1.1.0 '@microsoft/api-extractor': 7.7.11 '@opentelemetry/api': 0.10.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 @@ -9017,7 +9064,7 @@ packages: dev: false name: '@rush-temp/digitaltwins' resolution: - integrity: sha512-zIqDmm0JG+qCa3d7SO5c/edw+vtrCv/XG0cvF+8GogIAXKG5SzTNu2fwnx/k6nh6HcccJ1Dh9Xik+OozgELkSA== + integrity: sha512-CgsAeYsFlnLjMI94bdMukbMvr1e2hh0oWqaLD6rsAAWRJGtA9hgwzJbOm5+Xf4FC+cMHFNp9o4EO0f/tPw8tLQ== tarball: 'file:projects/digitaltwins.tgz' version: 0.0.0 'file:projects/eslint-plugin-azure-sdk.tgz': @@ -9056,6 +9103,7 @@ packages: 'file:projects/event-hubs.tgz': dependencies: '@azure/core-tracing': 1.0.0-preview.9 + '@azure/identity': 1.1.0 '@microsoft/api-extractor': 7.7.11 '@opentelemetry/api': 0.10.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 @@ -9124,7 +9172,7 @@ packages: dev: false name: '@rush-temp/event-hubs' resolution: - integrity: sha512-JuIyZxR1FK9yE3B6DMkxzIhMKhO6a0n711WLTl3oDi23jyzYXQsBtuzsY9BWHHpJWRbwqo2brmzJG0PrVET19A== + integrity: sha512-N9NwpcM2OZ/H/63xYaGo+0FGxhFi7CmUQW+CW7RdEMt/nnm+ssWoCvVdHvDflBBwxFqgRfqQevgiBHmB/EQ5uQ== tarball: 'file:projects/event-hubs.tgz' version: 0.0.0 'file:projects/event-processor-host.tgz': @@ -9342,12 +9390,13 @@ packages: dev: false name: '@rush-temp/keyvault-admin' resolution: - integrity: sha512-gYXpl3PLPnONAxy+vH2HNs7PZSYXvbxwKr+dRZf901aBhgMEG9HaEFV6fVJlE56lNY3Qr/H7ZgRomDmPyqeQMw== + integrity: sha512-zS+QZh+wHSnZhRTWpQcTgPlHzbm+Djb+pSL8tT6dTBo3cV1xFWHeYaenVT7gjQWgAJ41PREmPmSjBsLHnwSI7g== tarball: 'file:projects/keyvault-admin.tgz' version: 0.0.0 'file:projects/keyvault-certificates.tgz': dependencies: '@azure/core-tracing': 1.0.0-preview.9 + '@azure/identity': 1.1.0 '@microsoft/api-extractor': 7.7.11 '@opentelemetry/api': 0.10.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 @@ -9407,7 +9456,7 @@ packages: dev: false name: '@rush-temp/keyvault-certificates' resolution: - integrity: sha512-3oCp5zid1F3fk65gvUtYvMVdY6IKgmWj8uBDwtkxU2dEPShSSg8KDp3P5/x/ev/nCqp9chc+Q2RxiT/Zk01TZQ== + integrity: sha512-+a86qI+0rcf+xiFjB3H4JH6htEoBP+JJvVNmoNtfcLRh4F//9PBzFxOoS0NKutNsdw6Qexlv9lF0zJhj2E9uAA== tarball: 'file:projects/keyvault-certificates.tgz' version: 0.0.0 'file:projects/keyvault-common.tgz': @@ -9423,6 +9472,7 @@ packages: 'file:projects/keyvault-keys.tgz': dependencies: '@azure/core-tracing': 1.0.0-preview.9 + '@azure/identity': 1.1.0 '@microsoft/api-extractor': 7.7.11 '@opentelemetry/api': 0.10.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 @@ -9482,12 +9532,13 @@ packages: dev: false name: '@rush-temp/keyvault-keys' resolution: - integrity: sha512-MBqJW6IPn3o0SeXZqPxzVcbKJT7lrbm5hPBj4mfmRX/qm46CHHcnBQPU4WZ5B4GtDxbyA6IN+Le542DmghSYsw== + integrity: sha512-W4iVFbYVGW+2c/7IIw4D2O0qqRq7BaHKjmsNAxVJi1PSjoxFofSzOfEk/7QSKtnPW8PBwhEaWvjPDq/+5+QeyA== tarball: 'file:projects/keyvault-keys.tgz' version: 0.0.0 'file:projects/keyvault-secrets.tgz': dependencies: '@azure/core-tracing': 1.0.0-preview.9 + '@azure/identity': 1.1.0 '@microsoft/api-extractor': 7.7.11 '@opentelemetry/api': 0.10.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 @@ -9547,7 +9598,7 @@ packages: dev: false name: '@rush-temp/keyvault-secrets' resolution: - integrity: sha512-GpqIpqdGZ/WfkmR/J9jn6aH892G97KKdPt0i+217Sp/WUd6A0IbHsdD5NcO0I80IFTA7Jm9VrC6XLwONjlr1qw== + integrity: sha512-E+Z8eglyBz/UOXKAzRn8KcuIWEjjGdW69ZSlm3uvxE1KGTb72p3z03Fis19WaBLE3IzMpJWhK9RJkp8emoG46Q== tarball: 'file:projects/keyvault-secrets.tgz' version: 0.0.0 'file:projects/logger.tgz': @@ -9638,6 +9689,7 @@ packages: version: 0.0.0 'file:projects/schema-registry.tgz': dependencies: + '@azure/identity': 1.1.0 '@microsoft/api-extractor': 7.7.11 '@opentelemetry/api': 0.10.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 @@ -9689,7 +9741,7 @@ packages: dev: false name: '@rush-temp/schema-registry' resolution: - integrity: sha512-qwbyFTgo/2U854PJir4kL3Aa7LddIZdWZLjDfe9b7xj1zv5Hsne9EaAPhg+5FyifXeO9qM0TLDkER5Jlpu/YDQ== + integrity: sha512-GhcAmAIwEblMY6QAdmD6c0qF3H3iB+zPHYH9Ei07I6wTjh5DBHgrUcyjFG2oMWOTbtjud5Ead3YYEyURo9U+GQ== tarball: 'file:projects/schema-registry.tgz' version: 0.0.0 'file:projects/search-documents.tgz': @@ -9754,6 +9806,7 @@ packages: 'file:projects/service-bus.tgz': dependencies: '@azure/core-tracing': 1.0.0-preview.9 + '@azure/identity': 1.1.0 '@microsoft/api-extractor': 7.7.11 '@opentelemetry/api': 0.10.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 @@ -9827,12 +9880,13 @@ packages: dev: false name: '@rush-temp/service-bus' resolution: - integrity: sha512-zXQf+hJi9iuY31YJtL+TS1/+o5noCpEXvCVrUEXYM2a3pIuSnej44id3SKe0FFtodeYk4TTbS13d33+W9DZcng== + integrity: sha512-cYaXwp6B+xEGX4UT/CUvJvdjYrGMrktVgrNO2gHP4NBtR6du9wPoGYgOvN8AeHqnOUeafa304Pi3xr6BMW3RmA== tarball: 'file:projects/service-bus.tgz' version: 0.0.0 'file:projects/storage-blob-changefeed.tgz': dependencies: '@azure/core-tracing': 1.0.0-preview.9 + '@azure/identity': 1.1.0 '@microsoft/api-extractor': 7.7.11 '@opentelemetry/api': 0.10.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 @@ -9890,12 +9944,13 @@ packages: dev: false name: '@rush-temp/storage-blob-changefeed' resolution: - integrity: sha512-Put5TF4GoQegj0h35/aH/mTtrLNlDvGnL/wOpBa2UvJ4tIb5PRCXk514AtWjg8i0QmueTWpJGbhEPZseTl/wdg== + integrity: sha512-rSXYJEjYfgl9QnlPGYjHBr/w+mN7307df5nddNqwtCeQ4HGS4Z5ycT501vMZ5OSYU4pIdUQpWpoNqYS1npSk3g== tarball: 'file:projects/storage-blob-changefeed.tgz' version: 0.0.0 'file:projects/storage-blob.tgz': dependencies: '@azure/core-tracing': 1.0.0-preview.9 + '@azure/identity': 1.1.0 '@microsoft/api-extractor': 7.7.11 '@opentelemetry/api': 0.10.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 @@ -9951,12 +10006,13 @@ packages: dev: false name: '@rush-temp/storage-blob' resolution: - integrity: sha512-6GzgYaLjr2KMOxBb4AquO77K8N20WR/oLxiiULubwZOM3z+XU480LVSbzsTMUsiDTwJ6H8Qxesj8TNr2wrzkOg== + integrity: sha512-LgTbZCpuxCffYP45vW6aJiEjl3WSsdhk0eZqgGwSLeSDVx/4+bHjRAWJOgVNG49jUNl4ijV5VJbJy0Rjs1uAKQ== tarball: 'file:projects/storage-blob.tgz' version: 0.0.0 'file:projects/storage-file-datalake.tgz': dependencies: '@azure/core-tracing': 1.0.0-preview.9 + '@azure/identity': 1.1.0 '@microsoft/api-extractor': 7.7.11 '@opentelemetry/api': 0.10.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 @@ -10017,7 +10073,7 @@ packages: dev: false name: '@rush-temp/storage-file-datalake' resolution: - integrity: sha512-DLvaNC73IQ0yGvnqzuoM2eNHFCmehwNsEg8f6UJpwgZe41EY9dC7Q2Zj+dxLgdzxtS9oUe1LK7vOvmiZ3iwC1Q== + integrity: sha512-kr4Mann4jmvHJ5jrDYz7iuaPLXU22DytwownUq9OgFd0Z2vVrMXtTX1SVeqDw4byNpYv3o+kPtpR3YWO8ikpsQ== tarball: 'file:projects/storage-file-datalake.tgz' version: 0.0.0 'file:projects/storage-file-share.tgz': @@ -10083,6 +10139,7 @@ packages: version: 0.0.0 'file:projects/storage-internal-avro.tgz': dependencies: + '@azure/identity': 1.1.0 '@microsoft/api-extractor': 7.7.11 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1 @@ -10136,12 +10193,13 @@ packages: dev: false name: '@rush-temp/storage-internal-avro' resolution: - integrity: sha512-CCdxBliMo4B06r9zRMJQ9X6kaHnyjKn6LOuSqEhNoBxp4IdgDUseZz1M5btLSXulPnNsYSC4eSDD9CxDk7KgeA== + integrity: sha512-smoHPrzksDgYbKE/tXEitmNCScUURIJEMXhVsXaJn48EZ/d60Fklkb2izYqcE/f6Y+FggHSrz+/wPotRqIyPgw== tarball: 'file:projects/storage-internal-avro.tgz' version: 0.0.0 'file:projects/storage-queue.tgz': dependencies: '@azure/core-tracing': 1.0.0-preview.9 + '@azure/identity': 1.1.0 '@microsoft/api-extractor': 7.7.11 '@opentelemetry/api': 0.10.2 '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1 @@ -10196,7 +10254,7 @@ packages: dev: false name: '@rush-temp/storage-queue' resolution: - integrity: sha512-+1wXrigsmKD9OxW0ll/HGRMi0YWFLDvtg6gdfJ1yRfPcnq1WK6XKZ/2SR5AHt36yR+pnuGY34m+oHCBjC+KlFw== + integrity: sha512-op35DDM5BDADPBVZJ52spPgulRrN7kExhcF0OaBa95EJULDPUaQCMghiLROs2Nf/vZHMvcn6G0xZNrWztO8g/A== tarball: 'file:projects/storage-queue.tgz' version: 0.0.0 'file:projects/template.tgz': diff --git a/sdk/identity/identity/CHANGELOG.md b/sdk/identity/identity/CHANGELOG.md index dd186972356a..3d2894126d2d 100644 --- a/sdk/identity/identity/CHANGELOG.md +++ b/sdk/identity/identity/CHANGELOG.md @@ -1,5 +1,12 @@ # Release History +## 1.2.0-beta.1 (2020-09-08) + +- A new `InteractiveBrowserCredential` for node which will spawn a web server, start a web browser, and allow the user to interactively authenticate with the browser. +- With 1.2.0-beta.1, Identity will now use [MSAL](https://www.npmjs.com/package/@azure/msal-node) to perform authentication. With this beta, DeviceCodeCredential and a new InteractiveBrowserCredential for node are powered by MSAL. +- Identity now supports Subject Name/Issuer (SNI) as part of authentication for ClientCertificateCredential +- Upgraded App Services MSI API version + ## 1.1.0 (2020-08-11) ### Changes since 1.0.* diff --git a/sdk/identity/identity/package.json b/sdk/identity/identity/package.json index 866041abf048..e7714ae45d04 100644 --- a/sdk/identity/identity/package.json +++ b/sdk/identity/identity/package.json @@ -1,7 +1,7 @@ { "name": "@azure/identity", "sdk-type": "client", - "version": "1.1.0", + "version": "1.2.0-beta.1", "description": "Provides credential implementations for Azure SDK libraries that can authenticate with Azure Active Directory", "main": "dist/index.js", "module": "dist-esm/src/index.js",