diff --git a/.github/workflows/msal-node-e2e.yml b/.github/workflows/msal-node-e2e.yml index 490020c2ba..cdd16796ad 100644 --- a/.github/workflows/msal-node-e2e.yml +++ b/.github/workflows/msal-node-e2e.yml @@ -32,7 +32,7 @@ jobs: strategy: fail-fast: false matrix: - node: [16, 18, 20] + node: [16, 18, 20, 22] sample: - "auth-code" - "auth-code-cli-app" diff --git a/change/@azure-msal-browser-25f36e2f-923d-4228-b378-780627a862f4.json b/change/@azure-msal-browser-25f36e2f-923d-4228-b378-780627a862f4.json deleted file mode 100644 index 9936ba152d..0000000000 --- a/change/@azure-msal-browser-25f36e2f-923d-4228-b378-780627a862f4.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "minor", - "comment": "Instrument scenario id for tracking custom user prompts #7043", - "packageName": "@azure/msal-browser", - "email": "kshabelko@microsoft.com", - "dependentChangeType": "patch" -} diff --git a/change/@azure-msal-browser-2e85fe15-6556-4439-ae1e-6999a0c982a5.json b/change/@azure-msal-browser-2e85fe15-6556-4439-ae1e-6999a0c982a5.json deleted file mode 100644 index f122014e73..0000000000 --- a/change/@azure-msal-browser-2e85fe15-6556-4439-ae1e-6999a0c982a5.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "minor", - "comment": "Discard empty redirect telemetry events with no error codes #7058", - "packageName": "@azure/msal-browser", - "email": "kshabelko@microsoft.com", - "dependentChangeType": "patch" -} diff --git a/change/@azure-msal-browser-7b8679dc-1a5c-44b1-8f0a-bb52821145a9.json b/change/@azure-msal-browser-7b8679dc-1a5c-44b1-8f0a-bb52821145a9.json deleted file mode 100644 index 90f8d583ec..0000000000 --- a/change/@azure-msal-browser-7b8679dc-1a5c-44b1-8f0a-bb52821145a9.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "patch", - "comment": "Add getAccount API to IPublicClientApplication (#7019)", - "packageName": "@azure/msal-browser", - "email": "dasau@microsoft.com", - "dependentChangeType": "patch" -} diff --git a/change/@azure-msal-browser-a546ca68-6953-4f1d-85b3-a3d2dea68126.json b/change/@azure-msal-browser-a546ca68-6953-4f1d-85b3-a3d2dea68126.json deleted file mode 100644 index dbf37d435b..0000000000 --- a/change/@azure-msal-browser-a546ca68-6953-4f1d-85b3-a3d2dea68126.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "minor", - "comment": "Instrument account type #7049", - "packageName": "@azure/msal-browser", - "email": "kshabelko@microsoft.com", - "dependentChangeType": "patch" -} diff --git a/change/@azure-msal-common-3f8f5255-eca7-4c41-a592-eea0ad443f4f.json b/change/@azure-msal-common-3f8f5255-eca7-4c41-a592-eea0ad443f4f.json deleted file mode 100644 index 29108dc45a..0000000000 --- a/change/@azure-msal-common-3f8f5255-eca7-4c41-a592-eea0ad443f4f.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "minor", - "comment": "Instrument scenario id for tracking custom user prompts #7043", - "packageName": "@azure/msal-common", - "email": "kshabelko@microsoft.com", - "dependentChangeType": "patch" -} diff --git a/change/@azure-msal-common-655eddc1-0ede-4459-96b3-51f9f9e9d0c9.json b/change/@azure-msal-common-655eddc1-0ede-4459-96b3-51f9f9e9d0c9.json deleted file mode 100644 index bd47a7bcc2..0000000000 --- a/change/@azure-msal-common-655eddc1-0ede-4459-96b3-51f9f9e9d0c9.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "minor", - "comment": "Instrument account type #7049", - "packageName": "@azure/msal-common", - "email": "kshabelko@microsoft.com", - "dependentChangeType": "patch" -} diff --git a/change/@azure-msal-common-9c4bcecb-a0ee-497f-b18d-7df5a7b9fd09.json b/change/@azure-msal-common-9c4bcecb-a0ee-497f-b18d-7df5a7b9fd09.json deleted file mode 100644 index 8a0b92c254..0000000000 --- a/change/@azure-msal-common-9c4bcecb-a0ee-497f-b18d-7df5a7b9fd09.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "minor", - "comment": "Instrument server error number #7036", - "packageName": "@azure/msal-common", - "email": "kshabelko@microsoft.com", - "dependentChangeType": "patch" -} diff --git a/change/@azure-msal-common-e0ac26de-f149-4faf-adef-154f60c566b8.json b/change/@azure-msal-common-e0ac26de-f149-4faf-adef-154f60c566b8.json deleted file mode 100644 index c6e509982a..0000000000 --- a/change/@azure-msal-common-e0ac26de-f149-4faf-adef-154f60c566b8.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "minor", - "comment": "Make performanceClient.discardMeasurements() flush aux cache data in addition to measurements #7061", - "packageName": "@azure/msal-common", - "email": "kshabelko@microsoft.com", - "dependentChangeType": "patch" -} diff --git a/change/@azure-msal-react-5355b5e3-0c1c-4e23-ae9d-a6d96ef4285d.json b/change/@azure-msal-react-5355b5e3-0c1c-4e23-ae9d-a6d96ef4285d.json deleted file mode 100644 index cde9c32890..0000000000 --- a/change/@azure-msal-react-5355b5e3-0c1c-4e23-ae9d-a6d96ef4285d.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "patch", - "comment": "Fix useIsAuthenticated returning incorrect value during useEffect update #7057", - "packageName": "@azure/msal-react", - "email": "kade@hatchedlabs.com", - "dependentChangeType": "patch" -} diff --git a/extensions/msal-node-extensions/CHANGELOG.json b/extensions/msal-node-extensions/CHANGELOG.json index 7f9377d359..3c6e7b18e0 100644 --- a/extensions/msal-node-extensions/CHANGELOG.json +++ b/extensions/msal-node-extensions/CHANGELOG.json @@ -1,6 +1,27 @@ { "name": "@azure/msal-node-extensions", "entries": [ + { + "date": "Mon, 06 May 2024 23:48:17 GMT", + "version": "1.0.16", + "tag": "@azure/msal-node-extensions_v1.0.16", + "comments": { + "patch": [ + { + "author": "beachball", + "package": "@azure/msal-node-extensions", + "comment": "Bump @azure/msal-common to v14.10.0", + "commit": "not available" + }, + { + "author": "beachball", + "package": "@azure/msal-node-extensions", + "comment": "Bump eslint-config-msal to v0.0.0", + "commit": "not available" + } + ] + } + }, { "date": "Thu, 11 Apr 2024 21:46:57 GMT", "version": "1.0.15", diff --git a/extensions/msal-node-extensions/CHANGELOG.md b/extensions/msal-node-extensions/CHANGELOG.md index 5d27e6d117..d69d28afba 100644 --- a/extensions/msal-node-extensions/CHANGELOG.md +++ b/extensions/msal-node-extensions/CHANGELOG.md @@ -1,9 +1,18 @@ # Change Log - @azure/msal-node-extensions -This log was last generated on Thu, 11 Apr 2024 21:46:57 GMT and should not be manually modified. +This log was last generated on Mon, 06 May 2024 23:48:17 GMT and should not be manually modified. +## 1.0.16 + +Mon, 06 May 2024 23:48:17 GMT + +### Patches + +- Bump @azure/msal-common to v14.10.0 +- Bump eslint-config-msal to v0.0.0 + ## 1.0.15 Thu, 11 Apr 2024 21:46:57 GMT diff --git a/extensions/msal-node-extensions/package.json b/extensions/msal-node-extensions/package.json index afe861235d..825d5a501b 100644 --- a/extensions/msal-node-extensions/package.json +++ b/extensions/msal-node-extensions/package.json @@ -1,6 +1,6 @@ { "name": "@azure/msal-node-extensions", - "version": "1.0.15", + "version": "1.0.16", "repository": { "type": "git", "url": "https://github.com/AzureAD/microsoft-authentication-library-for-js.git" @@ -58,7 +58,7 @@ ] }, "dependencies": { - "@azure/msal-common": "14.9.0", + "@azure/msal-common": "14.10.0", "@azure/msal-node-runtime": "^0.13.6-alpha.0", "keytar": "^7.8.0" }, diff --git a/extensions/msal-node-extensions/src/packageMetadata.ts b/extensions/msal-node-extensions/src/packageMetadata.ts index 64a72db97d..d00de8a4af 100644 --- a/extensions/msal-node-extensions/src/packageMetadata.ts +++ b/extensions/msal-node-extensions/src/packageMetadata.ts @@ -1,3 +1,3 @@ /* eslint-disable header/header */ export const name = "@azure/msal-node-extensions"; -export const version = "1.0.15"; +export const version = "1.0.16"; diff --git a/lib/msal-angular/CHANGELOG.json b/lib/msal-angular/CHANGELOG.json index 95fa07e40d..8bdc567393 100644 --- a/lib/msal-angular/CHANGELOG.json +++ b/lib/msal-angular/CHANGELOG.json @@ -1,6 +1,27 @@ { "name": "@azure/msal-angular", "entries": [ + { + "date": "Mon, 06 May 2024 23:48:17 GMT", + "version": "3.0.17", + "tag": "@azure/msal-angular_v3.0.17", + "comments": { + "patch": [ + { + "author": "beachball", + "package": "@azure/msal-angular", + "comment": "Bump @azure/msal-browser to v3.14.0", + "commit": "not available" + }, + { + "author": "beachball", + "package": "@azure/msal-angular", + "comment": "Bump eslint-config-msal to v0.0.0", + "commit": "not available" + } + ] + } + }, { "date": "Thu, 11 Apr 2024 21:46:57 GMT", "version": "3.0.16", diff --git a/lib/msal-angular/CHANGELOG.md b/lib/msal-angular/CHANGELOG.md index 929acd8f92..a1a2664891 100644 --- a/lib/msal-angular/CHANGELOG.md +++ b/lib/msal-angular/CHANGELOG.md @@ -1,9 +1,18 @@ # Change Log - @azure/msal-angular -This log was last generated on Thu, 11 Apr 2024 21:46:57 GMT and should not be manually modified. +This log was last generated on Mon, 06 May 2024 23:48:17 GMT and should not be manually modified. +## 3.0.17 + +Mon, 06 May 2024 23:48:17 GMT + +### Patches + +- Bump @azure/msal-browser to v3.14.0 +- Bump eslint-config-msal to v0.0.0 + ## 3.0.16 Thu, 11 Apr 2024 21:46:57 GMT diff --git a/lib/msal-angular/package.json b/lib/msal-angular/package.json index fa9cbdf351..dc69ea4cc0 100644 --- a/lib/msal-angular/package.json +++ b/lib/msal-angular/package.json @@ -1,6 +1,6 @@ { "name": "@azure/msal-angular", - "version": "3.0.16", + "version": "3.0.17", "author": { "name": "Microsoft", "email": "nugetaad@microsoft.com", @@ -50,7 +50,7 @@ "@angular/platform-browser": "^15.1.4", "@angular/platform-browser-dynamic": "^15.1.4", "@angular/router": "^15.1.4", - "@azure/msal-browser": "^3.13.0", + "@azure/msal-browser": "^3.14.0", "@types/jasmine": "~3.6.0", "@types/jasminewd2": "~2.0.3", "@types/node": "^12.11.1", @@ -71,7 +71,7 @@ "zone.js": "~0.11.8" }, "peerDependencies": { - "@azure/msal-browser": "^3.13.0", + "@azure/msal-browser": "^3.14.0", "rxjs": "^7.0.0" } } diff --git a/lib/msal-angular/src/packageMetadata.ts b/lib/msal-angular/src/packageMetadata.ts index e86588c75b..d19a758a56 100644 --- a/lib/msal-angular/src/packageMetadata.ts +++ b/lib/msal-angular/src/packageMetadata.ts @@ -1,3 +1,3 @@ /* eslint-disable header/header */ export const name = "@azure/msal-angular"; -export const version = "3.0.16"; +export const version = "3.0.17"; diff --git a/lib/msal-browser/CHANGELOG.json b/lib/msal-browser/CHANGELOG.json index 0b0ad3a22f..cb5d122996 100644 --- a/lib/msal-browser/CHANGELOG.json +++ b/lib/msal-browser/CHANGELOG.json @@ -1,6 +1,77 @@ { "name": "@azure/msal-browser", "entries": [ + { + "date": "Mon, 06 May 2024 23:48:17 GMT", + "version": "3.14.0", + "tag": "@azure/msal-browser_v3.14.0", + "comments": { + "minor": [ + { + "author": "kshabelko@microsoft.com", + "package": "@azure/msal-browser", + "commit": "e5fa16efec03ddf9ee66ebac83b8a958e5b4a384", + "comment": "Instrument scenario id for tracking custom user prompts #7043" + }, + { + "author": "kshabelko@microsoft.com", + "package": "@azure/msal-browser", + "commit": "e5fa16efec03ddf9ee66ebac83b8a958e5b4a384", + "comment": "Discard empty redirect telemetry events with no error codes #7058" + }, + { + "author": "kshabelko@microsoft.com", + "package": "@azure/msal-browser", + "commit": "e5fa16efec03ddf9ee66ebac83b8a958e5b4a384", + "comment": "Export invoke and invokeAsync functions #7065" + }, + { + "author": "kshabelko@microsoft.com", + "package": "@azure/msal-browser", + "commit": "e5fa16efec03ddf9ee66ebac83b8a958e5b4a384", + "comment": "Instrument account type #7049" + }, + { + "author": "beachball", + "package": "@azure/msal-browser", + "comment": "Bump @azure/msal-common to v14.10.0", + "commit": "not available" + }, + { + "author": "beachball", + "package": "@azure/msal-browser", + "comment": "Bump eslint-config-msal to v0.0.0", + "commit": "not available" + }, + { + "author": "beachball", + "package": "@azure/msal-browser", + "comment": "Bump msal-test-utils to v0.0.1", + "commit": "not available" + } + ], + "patch": [ + { + "author": "dasau@microsoft.com", + "package": "@azure/msal-browser", + "commit": "e5fa16efec03ddf9ee66ebac83b8a958e5b4a384", + "comment": "Add getAccount API to IPublicClientApplication (#7019)" + }, + { + "author": "thomas.norling@microsoft.com", + "package": "@azure/msal-browser", + "commit": "e5fa16efec03ddf9ee66ebac83b8a958e5b4a384", + "comment": "Fix uncaught exceptions in acquireTokenSilent #7073" + }, + { + "author": "dasau@microsoft.com", + "package": "@azure/msal-browser", + "commit": "e5fa16efec03ddf9ee66ebac83b8a958e5b4a384", + "comment": "Add additional logging for Nested App Auth initialization errors (#7064)" + } + ] + } + }, { "date": "Thu, 11 Apr 2024 21:46:57 GMT", "version": "3.13.0", diff --git a/lib/msal-browser/CHANGELOG.md b/lib/msal-browser/CHANGELOG.md index 5638671b35..6e346f3f5f 100644 --- a/lib/msal-browser/CHANGELOG.md +++ b/lib/msal-browser/CHANGELOG.md @@ -1,9 +1,29 @@ # Change Log - @azure/msal-browser -This log was last generated on Thu, 11 Apr 2024 21:46:57 GMT and should not be manually modified. +This log was last generated on Mon, 06 May 2024 23:48:17 GMT and should not be manually modified. +## 3.14.0 + +Mon, 06 May 2024 23:48:17 GMT + +### Minor changes + +- Instrument scenario id for tracking custom user prompts #7043 (kshabelko@microsoft.com) +- Discard empty redirect telemetry events with no error codes #7058 (kshabelko@microsoft.com) +- Export invoke and invokeAsync functions #7065 (kshabelko@microsoft.com) +- Instrument account type #7049 (kshabelko@microsoft.com) +- Bump @azure/msal-common to v14.10.0 +- Bump eslint-config-msal to v0.0.0 +- Bump msal-test-utils to v0.0.1 + +### Patches + +- Add getAccount API to IPublicClientApplication (#7019) (dasau@microsoft.com) +- Fix uncaught exceptions in acquireTokenSilent #7073 (thomas.norling@microsoft.com) +- Add additional logging for Nested App Auth initialization errors (#7064) (dasau@microsoft.com) + ## 3.13.0 Thu, 11 Apr 2024 21:46:57 GMT diff --git a/lib/msal-browser/package.json b/lib/msal-browser/package.json index 596c8d2cea..5650c20576 100644 --- a/lib/msal-browser/package.json +++ b/lib/msal-browser/package.json @@ -10,7 +10,7 @@ "type": "git", "url": "https://github.com/AzureAD/microsoft-authentication-library-for-js.git" }, - "version": "3.13.0", + "version": "3.14.0", "description": "Microsoft Authentication Library for js", "keywords": [ "implicit", @@ -101,6 +101,6 @@ "typescript": "^4.9.5" }, "dependencies": { - "@azure/msal-common": "14.9.0" + "@azure/msal-common": "14.10.0" } } diff --git a/lib/msal-browser/src/controllers/ControllerFactory.ts b/lib/msal-browser/src/controllers/ControllerFactory.ts index b9a521766f..447ad1ca47 100644 --- a/lib/msal-browser/src/controllers/ControllerFactory.ts +++ b/lib/msal-browser/src/controllers/ControllerFactory.ts @@ -29,10 +29,7 @@ export async function createController( await Promise.all(operatingContexts); - if ( - teamsApp.isAvailable() && - teamsApp.getConfig().auth.supportsNestedAppAuth - ) { + if (teamsApp.isAvailable()) { const controller = await import("./NestedAppAuthController"); return controller.NestedAppAuthController.createController(teamsApp); } else if (standard.isAvailable()) { diff --git a/lib/msal-browser/src/controllers/StandardController.ts b/lib/msal-browser/src/controllers/StandardController.ts index 89ccc8e0e5..0a0e328de2 100644 --- a/lib/msal-browser/src/controllers/StandardController.ts +++ b/lib/msal-browser/src/controllers/StandardController.ts @@ -158,7 +158,7 @@ export class StandardController implements IController { >; // Active Iframe request - private activeIframeRequest: [Promise, string] | undefined; + private activeIframeRequest: [Promise, string] | undefined; private ssoSilentMeasurement?: InProgressPerformanceEvent; private acquireTokenByCodeAsyncMeasurement?: InProgressPerformanceEvent; @@ -2039,13 +2039,11 @@ export class StandardController implements IController { if (shouldTryToResolveSilently) { if (!this.activeIframeRequest) { - let _resolve: () => void, - _reject: (reason?: AuthError | Error) => void; + let _resolve: (result: boolean) => void; // Always set the active request tracker immediately after checking it to prevent races this.activeIframeRequest = [ - new Promise((resolve, reject) => { + new Promise((resolve) => { _resolve = resolve; - _reject = reject; }), silentRequest.correlationId, ]; @@ -2061,11 +2059,11 @@ export class StandardController implements IController { silentRequest.correlationId )(silentRequest) .then((iframeResult) => { - _resolve(); + _resolve(true); return iframeResult; }) .catch((e) => { - _reject(e); + _resolve(false); throw e; }) .finally(() => { @@ -2087,20 +2085,11 @@ export class StandardController implements IController { awaitIframeCorrelationId: activeCorrelationId, }); - // Await for errors first so we can distinguish errors thrown by activePromise versus errors thrown by .then below - await activePromise.catch(() => { - awaitConcurrentIframeMeasure.end({ - success: false, - }); - this.logger.info( - `Iframe request with correlationId: ${activeCorrelationId} failed. Interaction is required.` - ); - // If previous iframe request failed, it's unlikely to succeed this time. Throw original error. - throw refreshTokenError; + const activePromiseResult = await activePromise; + awaitConcurrentIframeMeasure.end({ + success: activePromiseResult, }); - - return activePromise.then(() => { - awaitConcurrentIframeMeasure.end({ success: true }); + if (activePromiseResult) { this.logger.verbose( `Parallel iframe request with correlationId: ${activeCorrelationId} succeeded. Retrying cache and/or RT redemption`, silentRequest.correlationId @@ -2110,7 +2099,13 @@ export class StandardController implements IController { silentRequest, cacheLookupPolicy ); - }); + } else { + this.logger.info( + `Iframe request with correlationId: ${activeCorrelationId} failed. Interaction is required.` + ); + // If previous iframe request failed, it's unlikely to succeed this time. Throw original error. + throw refreshTokenError; + } } else { // Cache policy set to skip and another iframe request is already in progress this.logger.warning( diff --git a/lib/msal-browser/src/operatingcontext/TeamsAppOperatingContext.ts b/lib/msal-browser/src/operatingcontext/TeamsAppOperatingContext.ts index 7b5970749a..6e96d4509f 100644 --- a/lib/msal-browser/src/operatingcontext/TeamsAppOperatingContext.ts +++ b/lib/msal-browser/src/operatingcontext/TeamsAppOperatingContext.ts @@ -61,6 +61,11 @@ export class TeamsAppOperatingContext extends BaseOperatingContext { * TODO: Add implementation to check for presence of inject Nested App Auth Bridge JavaScript interface * */ + + if (!this.getConfig().auth.supportsNestedAppAuth) { + return false; + } + try { if (typeof window !== "undefined") { const bridgeProxy: IBridgeProxy = await BridgeProxy.create(); @@ -74,18 +79,19 @@ export class TeamsAppOperatingContext extends BaseOperatingContext { this.activeAccount = await bridgeProxy.getActiveAccount(); } - } catch (e) { - this.activeAccount = undefined; + } catch { + // Ignore errors } this.bridgeProxy = bridgeProxy; this.available = bridgeProxy !== undefined; - } else { - this.available = false; } - } catch (e) { - this.available = false; - } finally { - return this.available; + } catch (ex) { + this.logger.infoPii( + `Could not initialize Nested App Auth bridge (${ex})` + ); } + + this.logger.info(`Nested App Auth Bridge available: ${this.available}`); + return this.available; } } diff --git a/lib/msal-browser/src/packageMetadata.ts b/lib/msal-browser/src/packageMetadata.ts index 78abd9a2f2..350344a1fc 100644 --- a/lib/msal-browser/src/packageMetadata.ts +++ b/lib/msal-browser/src/packageMetadata.ts @@ -1,3 +1,3 @@ /* eslint-disable header/header */ export const name = "@azure/msal-browser"; -export const version = "3.13.0"; +export const version = "3.14.0"; diff --git a/lib/msal-browser/src/utils/BrowserUtils.ts b/lib/msal-browser/src/utils/BrowserUtils.ts index 14552a1291..8352de98e5 100644 --- a/lib/msal-browser/src/utils/BrowserUtils.ts +++ b/lib/msal-browser/src/utils/BrowserUtils.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { UrlString } from "@azure/msal-common"; +import { UrlString, invoke, invokeAsync } from "@azure/msal-common"; import { createBrowserAuthError, BrowserAuthErrorCodes, @@ -206,3 +206,6 @@ export function preconnect(authority: string): void { export function createGuid(): string { return BrowserCrypto.createNewGuid(); } + +export { invoke }; +export { invokeAsync }; diff --git a/lib/msal-browser/test/app/PublicClientApplication.spec.ts b/lib/msal-browser/test/app/PublicClientApplication.spec.ts index 1fd1a6b3ab..e9836d79a4 100644 --- a/lib/msal-browser/test/app/PublicClientApplication.spec.ts +++ b/lib/msal-browser/test/app/PublicClientApplication.spec.ts @@ -4817,6 +4817,43 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { }); }); + it("throws iframe error if iframe renewal throws", (done) => { + const testAccount: AccountInfo = { + homeAccountId: TEST_DATA_CLIENT_INFO.TEST_HOME_ACCOUNT_ID, + localAccountId: TEST_DATA_CLIENT_INFO.TEST_UID, + environment: "login.windows.net", + tenantId: "testTenantId", + username: "username@contoso.com", + }; + + jest.spyOn( + RefreshTokenClient.prototype, + "acquireTokenByRefreshToken" + ).mockRejectedValue( + createInteractionRequiredAuthError( + InteractionRequiredAuthErrorCodes.refreshTokenExpired + ) + ); + + const testIframeError = new InteractionRequiredAuthError( + "interaction_required", + "interaction is required" + ); + + jest.spyOn( + SilentIframeClient.prototype, + "acquireToken" + ).mockRejectedValue(testIframeError); + + pca.acquireTokenSilent({ + scopes: ["Scope1"], + account: testAccount, + }).catch((e) => { + expect(e).toEqual(testIframeError); + done(); + }); + }); + it("Falls back to silent handler if thrown error is a refresh token expired error", async () => { const invalidGrantError: ServerError = new ServerError( "invalid_grant", diff --git a/lib/msal-common/CHANGELOG.json b/lib/msal-common/CHANGELOG.json index ee9c1fc19a..d574d621df 100644 --- a/lib/msal-common/CHANGELOG.json +++ b/lib/msal-common/CHANGELOG.json @@ -1,6 +1,71 @@ { "name": "@azure/msal-common", "entries": [ + { + "date": "Mon, 06 May 2024 23:48:17 GMT", + "version": "14.10.0", + "tag": "@azure/msal-common_v14.10.0", + "comments": { + "patch": [ + { + "author": "rginsburg@microsoft.com", + "package": "@azure/msal-common", + "commit": "e5fa16efec03ddf9ee66ebac83b8a958e5b4a384", + "comment": " Fixed inconsistencies with cancellationToken (timeout)" + } + ], + "minor": [ + { + "author": "kshabelko@microsoft.com", + "package": "@azure/msal-common", + "commit": "e5fa16efec03ddf9ee66ebac83b8a958e5b4a384", + "comment": "Instrument scenario id for tracking custom user prompts #7043" + }, + { + "author": "kshabelko@microsoft.com", + "package": "@azure/msal-common", + "commit": "e5fa16efec03ddf9ee66ebac83b8a958e5b4a384", + "comment": "Instrument account type #7049" + }, + { + "author": "kshabelko@microsoft.com", + "package": "@azure/msal-common", + "commit": "e5fa16efec03ddf9ee66ebac83b8a958e5b4a384", + "comment": "Instrument server error number #7036" + }, + { + "author": "kshabelko@microsoft.com", + "package": "@azure/msal-common", + "commit": "e5fa16efec03ddf9ee66ebac83b8a958e5b4a384", + "comment": "Do not register duplicate performance callbacks #7069" + }, + { + "author": "rginsburg@microsoft.com", + "package": "@azure/msal-common", + "commit": "e5fa16efec03ddf9ee66ebac83b8a958e5b4a384", + "comment": "Client Assertion Implementation now accepts a callback instead of a string argument" + }, + { + "author": "kshabelko@microsoft.com", + "package": "@azure/msal-common", + "commit": "e5fa16efec03ddf9ee66ebac83b8a958e5b4a384", + "comment": "Make performanceClient.discardMeasurements() flush aux cache data in addition to measurements #7061" + }, + { + "author": "beachball", + "package": "@azure/msal-common", + "comment": "Bump eslint-config-msal to v0.0.0", + "commit": "not available" + }, + { + "author": "beachball", + "package": "@azure/msal-common", + "comment": "Bump msal-test-utils to v0.0.1", + "commit": "not available" + } + ] + } + }, { "date": "Thu, 11 Apr 2024 21:46:57 GMT", "version": "14.9.0", diff --git a/lib/msal-common/CHANGELOG.md b/lib/msal-common/CHANGELOG.md index e3b0ef1486..fa8b9e4f9b 100644 --- a/lib/msal-common/CHANGELOG.md +++ b/lib/msal-common/CHANGELOG.md @@ -1,9 +1,28 @@ # Change Log - @azure/msal-common -This log was last generated on Thu, 11 Apr 2024 21:46:57 GMT and should not be manually modified. +This log was last generated on Mon, 06 May 2024 23:48:17 GMT and should not be manually modified. +## 14.10.0 + +Mon, 06 May 2024 23:48:17 GMT + +### Minor changes + +- Instrument scenario id for tracking custom user prompts #7043 (kshabelko@microsoft.com) +- Instrument account type #7049 (kshabelko@microsoft.com) +- Instrument server error number #7036 (kshabelko@microsoft.com) +- Do not register duplicate performance callbacks #7069 (kshabelko@microsoft.com) +- Client Assertion Implementation now accepts a callback instead of a string argument (rginsburg@microsoft.com) +- Make performanceClient.discardMeasurements() flush aux cache data in addition to measurements #7061 (kshabelko@microsoft.com) +- Bump eslint-config-msal to v0.0.0 +- Bump msal-test-utils to v0.0.1 + +### Patches + +- Fixed inconsistencies with cancellationToken (timeout) (rginsburg@microsoft.com) + ## 14.9.0 Thu, 11 Apr 2024 21:46:57 GMT diff --git a/lib/msal-common/package.json b/lib/msal-common/package.json index b9cb3570b0..5c630895bd 100644 --- a/lib/msal-common/package.json +++ b/lib/msal-common/package.json @@ -10,7 +10,7 @@ "type": "git", "url": "https://github.com/AzureAD/microsoft-authentication-library-for-js.git" }, - "version": "14.9.0", + "version": "14.10.0", "description": "Microsoft Authentication Library for js", "keywords": [ "implicit", diff --git a/lib/msal-common/src/account/ClientCredentials.ts b/lib/msal-common/src/account/ClientCredentials.ts index 8d18446d0a..6c148cf4f0 100644 --- a/lib/msal-common/src/account/ClientCredentials.ts +++ b/lib/msal-common/src/account/ClientCredentials.ts @@ -3,11 +3,20 @@ * Licensed under the MIT License. */ +export type ClientAssertionConfig = { + clientId: string; + tokenEndpoint?: string; +}; + +export type ClientAssertionCallback = ( + config: ClientAssertionConfig +) => Promise; + /** * Client Assertion credential for Confidential Clients */ export type ClientAssertion = { - assertion: string; + assertion: string | ClientAssertionCallback; assertionType: string; }; diff --git a/lib/msal-common/src/client/AuthorizationCodeClient.ts b/lib/msal-common/src/client/AuthorizationCodeClient.ts index e83d477912..4e914da11a 100644 --- a/lib/msal-common/src/client/AuthorizationCodeClient.ts +++ b/lib/msal-common/src/client/AuthorizationCodeClient.ts @@ -50,6 +50,8 @@ import { RequestValidator } from "../request/RequestValidator"; import { IPerformanceClient } from "../telemetry/performance/IPerformanceClient"; import { PerformanceEvents } from "../telemetry/performance/PerformanceEvent"; import { invokeAsync } from "../utils/FunctionWrappers"; +import { ClientAssertion } from "../account/ClientCredentials"; +import { getClientAssertion } from "../utils/ClientAssertionUtils"; /** * Oauth2.0 Authorization Code client @@ -364,9 +366,16 @@ export class AuthorizationCodeClient extends BaseClient { } if (this.config.clientCredentials.clientAssertion) { - const clientAssertion = + const clientAssertion: ClientAssertion = this.config.clientCredentials.clientAssertion; - parameterBuilder.addClientAssertion(clientAssertion.assertion); + + parameterBuilder.addClientAssertion( + await getClientAssertion( + clientAssertion.assertion, + this.config.authOptions.clientId, + request.resourceRequestUri + ) + ); parameterBuilder.addClientAssertionType( clientAssertion.assertionType ); diff --git a/lib/msal-common/src/client/RefreshTokenClient.ts b/lib/msal-common/src/client/RefreshTokenClient.ts index fd89c0a061..0a34396174 100644 --- a/lib/msal-common/src/client/RefreshTokenClient.ts +++ b/lib/msal-common/src/client/RefreshTokenClient.ts @@ -48,6 +48,8 @@ import { PerformanceEvents } from "../telemetry/performance/PerformanceEvent"; import { IPerformanceClient } from "../telemetry/performance/IPerformanceClient"; import { invoke, invokeAsync } from "../utils/FunctionWrappers"; import { generateCredentialKey } from "../cache/utils/CacheHelpers"; +import { ClientAssertion } from "../account/ClientCredentials"; +import { getClientAssertion } from "../utils/ClientAssertionUtils"; const DEFAULT_REFRESH_TOKEN_EXPIRATION_OFFSET_SECONDS = 300; // 5 Minutes @@ -385,9 +387,16 @@ export class RefreshTokenClient extends BaseClient { } if (this.config.clientCredentials.clientAssertion) { - const clientAssertion = + const clientAssertion: ClientAssertion = this.config.clientCredentials.clientAssertion; - parameterBuilder.addClientAssertion(clientAssertion.assertion); + + parameterBuilder.addClientAssertion( + await getClientAssertion( + clientAssertion.assertion, + this.config.authOptions.clientId, + request.resourceRequestUri + ) + ); parameterBuilder.addClientAssertionType( clientAssertion.assertionType ); diff --git a/lib/msal-common/src/index.ts b/lib/msal-common/src/index.ts index 78116fa752..2bb6254320 100644 --- a/lib/msal-common/src/index.ts +++ b/lib/msal-common/src/index.ts @@ -133,7 +133,11 @@ export { NativeRequest } from "./request/NativeRequest"; export { NativeSignOutRequest } from "./request/NativeSignOutRequest"; export { RequestParameterBuilder } from "./request/RequestParameterBuilder"; export { StoreInCache } from "./request/StoreInCache"; -export { ClientAssertion } from "./account/ClientCredentials"; +export { + ClientAssertion, + ClientAssertionConfig, + ClientAssertionCallback, +} from "./account/ClientCredentials"; // Response export { AzureRegion } from "./authority/AzureRegion"; export { AzureRegionConfiguration } from "./authority/AzureRegionConfiguration"; @@ -182,6 +186,7 @@ export { createClientConfigurationError, } from "./error/ClientConfigurationError"; // Constants and Utils +export { getClientAssertion } from "./utils/ClientAssertionUtils"; export { Constants, OIDC_DEFAULT_SCOPES, @@ -218,6 +223,7 @@ export { } from "./utils/ProtocolUtils"; export * as TimeUtils from "./utils/TimeUtils"; export * as UrlUtils from "./utils/UrlUtils"; +export * as ClientAssertionUtils from "./utils/ClientAssertionUtils"; export * from "./utils/FunctionWrappers"; // Server Telemetry export { ServerTelemetryManager } from "./telemetry/server/ServerTelemetryManager"; diff --git a/lib/msal-common/src/network/INetworkModule.ts b/lib/msal-common/src/network/INetworkModule.ts index 3c9afe4dd6..15254ade3f 100644 --- a/lib/msal-common/src/network/INetworkModule.ts +++ b/lib/msal-common/src/network/INetworkModule.ts @@ -31,7 +31,7 @@ export interface INetworkModule { sendGetRequestAsync( url: string, options?: NetworkRequestOptions, - cancellationToken?: number + timeout?: number ): Promise>; /** diff --git a/lib/msal-common/src/packageMetadata.ts b/lib/msal-common/src/packageMetadata.ts index bf7b4bcd1f..cba79ff854 100644 --- a/lib/msal-common/src/packageMetadata.ts +++ b/lib/msal-common/src/packageMetadata.ts @@ -1,3 +1,3 @@ /* eslint-disable header/header */ export const name = "@azure/msal-common"; -export const version = "14.9.0"; +export const version = "14.10.0"; diff --git a/lib/msal-common/src/telemetry/performance/PerformanceClient.ts b/lib/msal-common/src/telemetry/performance/PerformanceClient.ts index 02edc27b5c..274945485d 100644 --- a/lib/msal-common/src/telemetry/performance/PerformanceClient.ts +++ b/lib/msal-common/src/telemetry/performance/PerformanceClient.ts @@ -818,6 +818,15 @@ export abstract class PerformanceClient implements IPerformanceClient { * @returns {string} */ addPerformanceCallback(callback: PerformanceCallbackFunction): string { + for (const [id, cb] of this.callbacks) { + if (cb.toString() === callback.toString()) { + this.logger.warning( + `PerformanceClient: Performance callback is already registered with id: ${id}` + ); + return id; + } + } + const callbackId = this.generateId(); this.callbacks.set(callbackId, callback); this.logger.verbose( diff --git a/lib/msal-common/src/utils/ClientAssertionUtils.ts b/lib/msal-common/src/utils/ClientAssertionUtils.ts new file mode 100644 index 0000000000..cb9c68c618 --- /dev/null +++ b/lib/msal-common/src/utils/ClientAssertionUtils.ts @@ -0,0 +1,25 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +import { + ClientAssertionCallback, + ClientAssertionConfig, +} from "../account/ClientCredentials"; + +export async function getClientAssertion( + clientAssertion: string | ClientAssertionCallback, + clientId: string, + tokenEndpoint?: string +): Promise { + if (typeof clientAssertion === "string") { + return clientAssertion; + } else { + const config: ClientAssertionConfig = { + clientId: clientId, + tokenEndpoint: tokenEndpoint, + }; + return clientAssertion(config); + } +} diff --git a/lib/msal-common/src/utils/Constants.ts b/lib/msal-common/src/utils/Constants.ts index 627baa97f3..8b5a35e1a1 100644 --- a/lib/msal-common/src/utils/Constants.ts +++ b/lib/msal-common/src/utils/Constants.ts @@ -64,10 +64,11 @@ export const Constants = { }; export const HttpStatus = { - SUCCESS_RANGE_START: 200, SUCCESS: 200, + SUCCESS_RANGE_START: 200, SUCCESS_RANGE_END: 299, REDIRECT: 302, + CLIENT_ERROR: 400, CLIENT_ERROR_RANGE_START: 400, BAD_REQUEST: 400, UNAUTHORIZED: 401, @@ -75,11 +76,12 @@ export const HttpStatus = { REQUEST_TIMEOUT: 408, TOO_MANY_REQUESTS: 429, CLIENT_ERROR_RANGE_END: 499, + SERVER_ERROR: 500, SERVER_ERROR_RANGE_START: 500, - INTERNAL_SERVER_ERROR: 500, SERVICE_UNAVAILABLE: 503, GATEWAY_TIMEOUT: 504, SERVER_ERROR_RANGE_END: 599, + MULTI_SIDED_ERROR: 600, } as const; export type HttpStatus = (typeof HttpStatus)[keyof typeof HttpStatus]; diff --git a/lib/msal-common/test/request/RequestParameterBuilder.spec.ts b/lib/msal-common/test/request/RequestParameterBuilder.spec.ts index dd7be85090..56a3134891 100644 --- a/lib/msal-common/test/request/RequestParameterBuilder.spec.ts +++ b/lib/msal-common/test/request/RequestParameterBuilder.spec.ts @@ -23,6 +23,9 @@ import { ClientConfigurationErrorMessage, createClientConfigurationError, } from "../../src/error/ClientConfigurationError"; +import { ClientAssertion, ClientAssertionCallback } from "../../src"; +import { getClientAssertion } from "../../src/utils/ClientAssertionUtils"; +import { ClientAssertionConfig } from "../../src/account/ClientCredentials"; describe("RequestParameterBuilder unit tests", () => { it("constructor", () => { @@ -379,14 +382,20 @@ describe("RequestParameterBuilder unit tests", () => { sinon.restore(); }); - it("adds clientAssertion and assertionType if they are passed in as strings", () => { - const clientAssertion = { + it("adds clientAssertion (string) and assertionType if they are provided by the developer", async () => { + const clientAssertion: ClientAssertion = { assertion: "testAssertion", assertionType: "jwt-bearer", }; const requestParameterBuilder = new RequestParameterBuilder(); - requestParameterBuilder.addClientAssertion(clientAssertion.assertion); + requestParameterBuilder.addClientAssertion( + await getClientAssertion( + clientAssertion.assertion, + "client_id", + "optional_token_endpoint" + ) + ); requestParameterBuilder.addClientAssertionType( clientAssertion.assertionType ); @@ -407,14 +416,94 @@ describe("RequestParameterBuilder unit tests", () => { ).toBe(true); }); - it("doesn't add client assertion and client assertion type if they are empty strings", () => { - const clientAssertion = { + it("doesn't add client assertion (string) and client assertion type if they are empty strings", async () => { + const clientAssertion: ClientAssertion = { assertion: "", assertionType: "", }; const requestParameterBuilder = new RequestParameterBuilder(); - requestParameterBuilder.addClientAssertion(clientAssertion.assertion); + requestParameterBuilder.addClientAssertion( + await getClientAssertion( + clientAssertion.assertion, + "client_id", + "optional_token_endpoint" + ) + ); + requestParameterBuilder.addClientAssertionType( + clientAssertion.assertionType + ); + const requestQueryString = requestParameterBuilder.createQueryString(); + expect( + requestQueryString.includes(AADServerParamKeys.CLIENT_ASSERTION) + ).toBe(false); + expect( + requestQueryString.includes( + AADServerParamKeys.CLIENT_ASSERTION_TYPE + ) + ).toBe(false); + }); + + it("adds clientAssertion (ClientAssertionCallback) and assertionType if they are provided by the developer", async () => { + const ClientAssertionCallback: ClientAssertionCallback = ( + _config: ClientAssertionConfig + ) => { + return Promise.resolve("testAssertion"); + }; + + const clientAssertion: ClientAssertion = { + assertion: ClientAssertionCallback, + assertionType: "jwt-bearer", + }; + + const requestParameterBuilder = new RequestParameterBuilder(); + requestParameterBuilder.addClientAssertion( + await getClientAssertion( + clientAssertion.assertion, + "client_id", + "optional_token_endpoint" + ) + ); + requestParameterBuilder.addClientAssertionType( + clientAssertion.assertionType + ); + const requestQueryString = requestParameterBuilder.createQueryString(); + expect( + requestQueryString.includes( + `${AADServerParamKeys.CLIENT_ASSERTION}=${encodeURIComponent( + "testAssertion" + )}` + ) + ).toBe(true); + expect( + requestQueryString.includes( + `${ + AADServerParamKeys.CLIENT_ASSERTION_TYPE + }=${encodeURIComponent("jwt-bearer")}` + ) + ).toBe(true); + }); + + it("doesn't add client assertion (ClientAssertionCallback) and client assertion type if they are empty strings", async () => { + const ClientAssertionCallback: ClientAssertionCallback = ( + _config: ClientAssertionConfig + ) => { + return Promise.resolve(""); + }; + + const clientAssertion: ClientAssertion = { + assertion: ClientAssertionCallback, + assertionType: "", + }; + + const requestParameterBuilder = new RequestParameterBuilder(); + requestParameterBuilder.addClientAssertion( + await getClientAssertion( + clientAssertion.assertion, + "client_id", + "optional_token_endpoint" + ) + ); requestParameterBuilder.addClientAssertionType( clientAssertion.assertionType ); diff --git a/lib/msal-common/test/telemetry/PerformanceClient.spec.ts b/lib/msal-common/test/telemetry/PerformanceClient.spec.ts index e640b2622c..8d0208509f 100644 --- a/lib/msal-common/test/telemetry/PerformanceClient.spec.ts +++ b/lib/msal-common/test/telemetry/PerformanceClient.spec.ts @@ -101,6 +101,22 @@ describe("PerformanceClient.spec.ts", () => { expect(result).toBe(true); }); + it("Does not register duplicate callbacks", () => { + const mockPerfClient = new MockPerformanceClient(); + + const callbackId = mockPerfClient.addPerformanceCallback((events) => { + console.log(events); + }); + + const callbackId2 = mockPerfClient.addPerformanceCallback((events) => { + console.log(events); + }); + + expect(callbackId).toEqual(callbackId2); + // @ts-ignore + expect(mockPerfClient.callbacks.size).toBe(1); + }); + it("starts, ends, and emits an event", (done) => { const mockPerfClient = new MockPerformanceClient(); diff --git a/lib/msal-node/CHANGELOG.json b/lib/msal-node/CHANGELOG.json index 2043beb863..1c77d0558f 100644 --- a/lib/msal-node/CHANGELOG.json +++ b/lib/msal-node/CHANGELOG.json @@ -1,6 +1,59 @@ { "name": "@azure/msal-node", "entries": [ + { + "date": "Mon, 06 May 2024 23:48:17 GMT", + "version": "2.8.0", + "tag": "@azure/msal-node_v2.8.0", + "comments": { + "patch": [ + { + "author": "rginsburg@microsoft.com", + "package": "@azure/msal-node", + "commit": "e5fa16efec03ddf9ee66ebac83b8a958e5b4a384", + "comment": " Fixed inconsistencies with cancellationToken (timeout)" + }, + { + "author": "rginsburg@microsoft.com", + "package": "@azure/msal-node", + "commit": "e5fa16efec03ddf9ee66ebac83b8a958e5b4a384", + "comment": "ClientCredential and OBO acquireToken requests with claims will now skip the cache" + }, + { + "author": "rginsburg@microsoft.com", + "package": "@azure/msal-node", + "commit": "e5fa16efec03ddf9ee66ebac83b8a958e5b4a384", + "comment": "Managed Identity: ManagedIdentityTokenResponse's expires_in is now calculated correctly" + }, + { + "author": "rginsburg@microsoft.com", + "package": "@azure/msal-node", + "commit": "e5fa16efec03ddf9ee66ebac83b8a958e5b4a384", + "comment": "Removed Managed Identity Resource URI Validation" + } + ], + "minor": [ + { + "author": "rginsburg@microsoft.com", + "package": "@azure/msal-node", + "commit": "e5fa16efec03ddf9ee66ebac83b8a958e5b4a384", + "comment": "Client Assertion Implementation now accepts a callback instead of a string argument" + }, + { + "author": "beachball", + "package": "@azure/msal-node", + "comment": "Bump @azure/msal-common to v14.10.0", + "commit": "not available" + }, + { + "author": "beachball", + "package": "@azure/msal-node", + "comment": "Bump eslint-config-msal to v0.0.0", + "commit": "not available" + } + ] + } + }, { "date": "Thu, 11 Apr 2024 21:46:57 GMT", "version": "2.7.0", diff --git a/lib/msal-node/CHANGELOG.md b/lib/msal-node/CHANGELOG.md index 94b993a443..e0f8aeacf6 100644 --- a/lib/msal-node/CHANGELOG.md +++ b/lib/msal-node/CHANGELOG.md @@ -1,9 +1,26 @@ # Change Log - @azure/msal-node -This log was last generated on Thu, 11 Apr 2024 21:46:57 GMT and should not be manually modified. +This log was last generated on Mon, 06 May 2024 23:48:17 GMT and should not be manually modified. +## 2.8.0 + +Mon, 06 May 2024 23:48:17 GMT + +### Minor changes + +- Client Assertion Implementation now accepts a callback instead of a string argument (rginsburg@microsoft.com) +- Bump @azure/msal-common to v14.10.0 +- Bump eslint-config-msal to v0.0.0 + +### Patches + +- Fixed inconsistencies with cancellationToken (timeout) (rginsburg@microsoft.com) +- ClientCredential and OBO acquireToken requests with claims will now skip the cache (rginsburg@microsoft.com) +- Managed Identity: ManagedIdentityTokenResponse's expires_in is now calculated correctly (rginsburg@microsoft.com) +- Removed Managed Identity Resource URI Validation (rginsburg@microsoft.com) + ## 2.7.0 Thu, 11 Apr 2024 21:46:57 GMT diff --git a/lib/msal-node/README.md b/lib/msal-node/README.md index 883c860af6..ddf83c7586 100644 --- a/lib/msal-node/README.md +++ b/lib/msal-node/README.md @@ -88,7 +88,7 @@ Any major MSAL Node release: | MSAL Node version | MSAL support status | Supported Node versions | | ----------------- | ------------------- | ----------------------- | -| 2.x.x | Active development | 16, 18, 20 | +| 2.x.x | Active development | 16, 18, 20, 22 | | 1.x.x | In maintenance | 10, 12, 14, 16, 18 | **Note:** There have been no functional changes in the MSAL Node v2 release. diff --git a/lib/msal-node/docs/initialize-confidential-client-application.md b/lib/msal-node/docs/initialize-confidential-client-application.md index 03cab92b07..9e4ae04568 100644 --- a/lib/msal-node/docs/initialize-confidential-client-application.md +++ b/lib/msal-node/docs/initialize-confidential-client-application.md @@ -29,6 +29,16 @@ See the MSAL sample: [auth-code-with-certs](../../../samples/msal-node-samples/a import * as msal from "@azure/msal-node"; import "dotenv/config"; // process.env now has the values defined in a .env file +const clientAssertionCallback: msal.ClientAssertionCallback = async ( + config: msal.ClientAssertionConfig +): Promise => { + // network request that uses config.clientId and (optionally) config.tokenEndpoint + const result: Promise = await Promise.resolve( + "network request which gets assertion" + ); + return result; +}; + const clientConfig = { auth: { clientId: "your_client_id", @@ -38,7 +48,7 @@ const clientConfig = { thumbprint: process.env.thumbprint, privateKey: process.env.privateKey, }, // OR - clientAssertion: "assertion", + clientAssertion: clientAssertionCallback, // or a predetermined clientAssertion string }, }; const pca = new msal.ConfidentialClientApplication(clientConfig); @@ -53,7 +63,7 @@ const pca = new msal.ConfidentialClientApplication(clientConfig); - A Client credential is mandatory for confidential clients. Client credential can be a: - `clientSecret` is secret string generated set on the app registration. - `clientCertificate` is a certificate set on the app registration. The `thumbprint` is a X.509 SHA-1 thumbprint of the certificate, and the `privateKey` is the PEM encoded private key. `x5c` is the optional X.509 certificate chain used in [subject name/issuer auth scenarios](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/sni.md). - - `clientAssertion` is string that the application uses when requesting a token. The certificate used to sign the assertion should be set on the app registration. Assertion should be of type urn:ietf:params:oauth:client-assertion-type:jwt-bearer. + - `clientAssertion` is a ClientAssertion object containing an assertion string or a callback function that returns an assertion string that the application uses when requesting a token, as well as the assertion's type (urn:ietf:params:oauth:client-assertion-type:jwt-bearer). The callback is invoked every time MSAL needs to acquire a token from the token issuer. App developers should generally use the callback because assertions expire and new assertions need to be created. App developers are responsible for the assertion lifetime. Use [this mechanism](https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust) to get tokens for a downstream API using a Federated Identity Credential. ## Configure Authority diff --git a/lib/msal-node/package.json b/lib/msal-node/package.json index 30e8ff5185..cc317567e0 100644 --- a/lib/msal-node/package.json +++ b/lib/msal-node/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@azure/msal-node", - "version": "2.7.0", + "version": "2.8.0", "author": { "name": "Microsoft", "email": "nugetaad@microsoft.com", @@ -77,7 +77,7 @@ "yargs": "^17.3.1" }, "dependencies": { - "@azure/msal-common": "14.9.0", + "@azure/msal-common": "14.10.0", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" }, diff --git a/lib/msal-node/src/client/ClientApplication.ts b/lib/msal-node/src/client/ClientApplication.ts index 6015ef446d..67c045e355 100644 --- a/lib/msal-node/src/client/ClientApplication.ts +++ b/lib/msal-node/src/client/ClientApplication.ts @@ -33,6 +33,9 @@ import { createClientAuthError, ClientAuthErrorCodes, buildStaticAuthorityOptions, + ClientAssertion as ClientAssertionType, + getClientAssertion, + ClientAssertionCallback, } from "@azure/msal-common"; import { Configuration, @@ -77,6 +80,9 @@ export abstract class ClientApplication { * Client assertion passed by the user for confidential client flows */ protected clientAssertion: ClientAssertion; + protected developerProvidedClientAssertion: + | string + | ClientAssertionCallback; /** * Client secret passed by the user for confidential client flows */ @@ -451,8 +457,8 @@ export abstract class ClientApplication { serverTelemetryManager: serverTelemetryManager, clientCredentials: { clientSecret: this.clientSecret, - clientAssertion: this.clientAssertion - ? this.getClientAssertion(discoveredAuthority) + clientAssertion: this.developerProvidedClientAssertion + ? await this.getClientAssertion(discoveredAuthority) : undefined, }, libraryInfo: { @@ -469,10 +475,17 @@ export abstract class ClientApplication { return clientConfiguration; } - private getClientAssertion(authority: Authority): { - assertion: string; - assertionType: string; - } { + private async getClientAssertion( + authority: Authority + ): Promise { + this.clientAssertion = ClientAssertion.fromAssertion( + await getClientAssertion( + this.developerProvidedClientAssertion, + this.config.auth.clientId, + authority.tokenEndpoint + ) + ); + return { assertion: this.clientAssertion.getJwt( this.cryptoProvider, diff --git a/lib/msal-node/src/client/ClientCredentialClient.ts b/lib/msal-node/src/client/ClientCredentialClient.ts index bd750e5abc..2198db3b79 100644 --- a/lib/msal-node/src/client/ClientCredentialClient.ts +++ b/lib/msal-node/src/client/ClientCredentialClient.ts @@ -32,6 +32,8 @@ import { TokenCacheContext, UrlString, createClientAuthError, + ClientAssertion, + getClientAssertion, } from "@azure/msal-common"; import { ManagedIdentityConfiguration, @@ -59,7 +61,7 @@ export class ClientCredentialClient extends BaseClient { public async acquireToken( request: CommonClientCredentialRequest ): Promise { - if (request.skipCache) { + if (request.skipCache || request.claims) { return this.executeTokenRequest(request, this.authority); } @@ -270,7 +272,7 @@ export class ClientCredentialClient extends BaseClient { queryParametersString ); - const requestBody = this.createTokenRequestBody(request); + const requestBody = await this.createTokenRequestBody(request); const headers: Record = this.createTokenRequestHeaders(); const thumbprint: RequestThumbprint = { @@ -330,9 +332,9 @@ export class ClientCredentialClient extends BaseClient { * generate the request to the server in the acceptable format * @param request */ - private createTokenRequestBody( + private async createTokenRequestBody( request: CommonClientCredentialRequest - ): string { + ): Promise { const parameterBuilder = new RequestParameterBuilder(); parameterBuilder.addClientId(this.config.authOptions.clientId); @@ -364,12 +366,18 @@ export class ClientCredentialClient extends BaseClient { } // Use clientAssertion from request, fallback to client assertion in base configuration - const clientAssertion = + const clientAssertion: ClientAssertion | undefined = request.clientAssertion || this.config.clientCredentials.clientAssertion; if (clientAssertion) { - parameterBuilder.addClientAssertion(clientAssertion.assertion); + parameterBuilder.addClientAssertion( + await getClientAssertion( + clientAssertion.assertion, + this.config.authOptions.clientId, + request.resourceRequestUri + ) + ); parameterBuilder.addClientAssertionType( clientAssertion.assertionType ); diff --git a/lib/msal-node/src/client/ConfidentialClientApplication.ts b/lib/msal-node/src/client/ConfidentialClientApplication.ts index 00fda6c110..26e3c7a197 100644 --- a/lib/msal-node/src/client/ConfidentialClientApplication.ts +++ b/lib/msal-node/src/client/ConfidentialClientApplication.ts @@ -26,6 +26,8 @@ import { AADAuthorityConstants, createClientAuthError, ClientAuthErrorCodes, + ClientAssertion as ClientAssertionType, + getClientAssertion, } from "@azure/msal-common"; import { IConfidentialClientApplication } from "./IConfidentialClientApplication.js"; import { OnBehalfOfRequest } from "../request/OnBehalfOfRequest.js"; @@ -91,10 +93,14 @@ export class ConfidentialClientApplication ); // If there is a client assertion present in the request, it overrides the one present in the client configuration - let clientAssertion; + let clientAssertion: ClientAssertionType | undefined; if (request.clientAssertion) { clientAssertion = { - assertion: request.clientAssertion, + assertion: await getClientAssertion( + request.clientAssertion, + this.config.auth.clientId + // tokenEndpoint will be undefined. resourceRequestUri is omitted in ClientCredentialRequest + ), assertionType: NodeConstants.JWT_BEARER_ASSERTION_TYPE, }; } @@ -247,9 +253,8 @@ export class ConfidentialClientApplication } if (configuration.auth.clientAssertion) { - this.clientAssertion = ClientAssertion.fromAssertion( - configuration.auth.clientAssertion - ); + this.developerProvidedClientAssertion = + configuration.auth.clientAssertion; return; } diff --git a/lib/msal-node/src/client/ManagedIdentityApplication.ts b/lib/msal-node/src/client/ManagedIdentityApplication.ts index dd4db52c67..6208dfb002 100644 --- a/lib/msal-node/src/client/ManagedIdentityApplication.ts +++ b/lib/msal-node/src/client/ManagedIdentityApplication.ts @@ -16,7 +16,8 @@ import { ProtocolMode, StaticAuthorityOptions, AuthenticationResult, - UrlString, + createClientConfigurationError, + ClientConfigurationErrorCodes, } from "@azure/msal-common"; import { ManagedIdentityConfiguration, @@ -31,10 +32,6 @@ import { ManagedIdentityClient } from "./ManagedIdentityClient"; import { ManagedIdentityRequestParams } from "../request/ManagedIdentityRequestParams"; import { NodeStorage } from "../cache/NodeStorage"; import { DEFAULT_AUTHORITY_FOR_MANAGED_IDENTITY } from "../utils/Constants"; -import { - ManagedIdentityErrorCodes, - createManagedIdentityError, -} from "../error/ManagedIdentityError"; /** * Class to initialize a managed identity and identify the service @@ -122,14 +119,9 @@ export class ManagedIdentityApplication { public async acquireToken( managedIdentityRequestParams: ManagedIdentityRequestParams ): Promise { - const resourceUrlString = new UrlString( - managedIdentityRequestParams.resource.replace("/.default", "") - ); - try { - resourceUrlString.validateAsUri(); - } catch (e) { - throw createManagedIdentityError( - ManagedIdentityErrorCodes.invalidResource + if (!managedIdentityRequestParams.resource) { + throw createClientConfigurationError( + ClientConfigurationErrorCodes.urlEmptyError ); } diff --git a/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts b/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts index 87a69a9d54..eb88b23a7e 100644 --- a/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts +++ b/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts @@ -81,28 +81,31 @@ export abstract class BaseManagedIdentitySource { public getServerTokenResponse( response: NetworkResponse ): ServerAuthorizationTokenResponse { + let refreshIn, expiresIn: number | undefined; + if (response.body.expires_on) { + expiresIn = response.body.expires_on - TimeUtils.nowSeconds(); + + // compute refresh_in as 1/2 of expires_in, but only if expires_in > 2h + if (expiresIn > 2 * 3600) { + refreshIn = expiresIn / 2; + } + } + const serverTokenResponse: ServerAuthorizationTokenResponse = { status: response.status, // success access_token: response.body.access_token, - expires_in: response.body.expires_on, + expires_in: expiresIn, scope: response.body.resource, token_type: response.body.token_type, + refresh_in: refreshIn, // error error: response.body.message, correlation_id: response.body.correlationId, }; - // compute refresh_in as 1/2 of expires_in, but only if expires_in > 2h - if ( - serverTokenResponse.expires_in && - serverTokenResponse.expires_in > 2 * 3600 - ) { - serverTokenResponse.refresh_in = serverTokenResponse.expires_in / 2; - } - return serverTokenResponse; } diff --git a/lib/msal-node/src/client/OnBehalfOfClient.ts b/lib/msal-node/src/client/OnBehalfOfClient.ts index 3fcc427955..02f18b0fed 100644 --- a/lib/msal-node/src/client/OnBehalfOfClient.ts +++ b/lib/msal-node/src/client/OnBehalfOfClient.ts @@ -30,6 +30,8 @@ import { TimeUtils, TokenClaims, UrlString, + ClientAssertion, + getClientAssertion, } from "@azure/msal-common"; import { EncodingUtils } from "../utils/EncodingUtils"; @@ -58,7 +60,7 @@ export class OnBehalfOfClient extends BaseClient { request.oboAssertion ); - if (request.skipCache) { + if (request.skipCache || request.claims) { return this.executeTokenRequest( request, this.authority, @@ -257,7 +259,7 @@ export class OnBehalfOfClient extends BaseClient { authority.tokenEndpoint, queryParametersString ); - const requestBody = this.createTokenRequestBody(request); + const requestBody = await this.createTokenRequestBody(request); const headers: Record = this.createTokenRequestHeaders(); const thumbprint: RequestThumbprint = { @@ -307,7 +309,9 @@ export class OnBehalfOfClient extends BaseClient { * generate a server request in accepable format * @param request */ - private createTokenRequestBody(request: CommonOnBehalfOfRequest): string { + private async createTokenRequestBody( + request: CommonOnBehalfOfRequest + ): Promise { const parameterBuilder = new RequestParameterBuilder(); parameterBuilder.addClientId(this.config.authOptions.clientId); @@ -343,10 +347,17 @@ export class OnBehalfOfClient extends BaseClient { ); } - if (this.config.clientCredentials.clientAssertion) { - const clientAssertion = - this.config.clientCredentials.clientAssertion; - parameterBuilder.addClientAssertion(clientAssertion.assertion); + const clientAssertion: ClientAssertion | undefined = + this.config.clientCredentials.clientAssertion; + + if (clientAssertion) { + parameterBuilder.addClientAssertion( + await getClientAssertion( + clientAssertion.assertion, + this.config.authOptions.clientId, + request.resourceRequestUri + ) + ); parameterBuilder.addClientAssertionType( clientAssertion.assertionType ); diff --git a/lib/msal-node/src/client/UsernamePasswordClient.ts b/lib/msal-node/src/client/UsernamePasswordClient.ts index 91f3f1a0bc..3a452aa3ef 100644 --- a/lib/msal-node/src/client/UsernamePasswordClient.ts +++ b/lib/msal-node/src/client/UsernamePasswordClient.ts @@ -8,6 +8,7 @@ import { Authority, BaseClient, CcsCredentialType, + ClientAssertion, ClientConfiguration, CommonUsernamePasswordRequest, GrantType, @@ -19,6 +20,7 @@ import { StringUtils, TimeUtils, UrlString, + getClientAssertion, } from "@azure/msal-common"; /** @@ -81,7 +83,7 @@ export class UsernamePasswordClient extends BaseClient { authority.tokenEndpoint, queryParametersString ); - const requestBody = this.createTokenRequestBody(request); + const requestBody = await this.createTokenRequestBody(request); const headers: Record = this.createTokenRequestHeaders({ credential: request.username, type: CcsCredentialType.UPN, @@ -111,9 +113,9 @@ export class UsernamePasswordClient extends BaseClient { * Generates a map for all the params to be sent to the service * @param request */ - private createTokenRequestBody( + private async createTokenRequestBody( request: CommonUsernamePasswordRequest - ): string { + ): Promise { const parameterBuilder = new RequestParameterBuilder(); parameterBuilder.addClientId(this.config.authOptions.clientId); @@ -148,10 +150,17 @@ export class UsernamePasswordClient extends BaseClient { ); } - if (this.config.clientCredentials.clientAssertion) { - const clientAssertion = - this.config.clientCredentials.clientAssertion; - parameterBuilder.addClientAssertion(clientAssertion.assertion); + const clientAssertion: ClientAssertion | undefined = + this.config.clientCredentials.clientAssertion; + + if (clientAssertion) { + parameterBuilder.addClientAssertion( + await getClientAssertion( + clientAssertion.assertion, + this.config.authOptions.clientId, + request.resourceRequestUri + ) + ); parameterBuilder.addClientAssertionType( clientAssertion.assertionType ); diff --git a/lib/msal-node/src/config/Configuration.ts b/lib/msal-node/src/config/Configuration.ts index 9784325450..2751b1f157 100644 --- a/lib/msal-node/src/config/Configuration.ts +++ b/lib/msal-node/src/config/Configuration.ts @@ -14,6 +14,7 @@ import { AzureCloudOptions, ApplicationTelemetry, INativeBrokerPlugin, + ClientAssertionCallback, } from "@azure/msal-common"; import { HttpClient } from "../network/HttpClient.js"; import http from "http"; @@ -32,7 +33,7 @@ import { HttpClientWithRetries } from "../network/HttpClientWithRetries.js"; * - authority - Url of the authority. If no value is set, defaults to https://login.microsoftonline.com/common. * - knownAuthorities - Needed for Azure B2C and ADFS. All authorities that will be used in the client application. Only the host of the authority should be passed in. * - clientSecret - Secret string that the application uses when requesting a token. Only used in confidential client applications. Can be created in the Azure app registration portal. - * - clientAssertion - Assertion string that the application uses when requesting a token. Only used in confidential client applications. Assertion should be of type urn:ietf:params:oauth:client-assertion-type:jwt-bearer. + * - clientAssertion - A ClientAssertion object containing an assertion string or a callback function that returns an assertion string that the application uses when requesting a token, as well as the assertion's type (urn:ietf:params:oauth:client-assertion-type:jwt-bearer). Only used in confidential client applications. * - clientCertificate - Certificate that the application uses when requesting a token. Only used in confidential client applications. Requires hex encoded X.509 SHA-1 thumbprint of the certificiate, and the PEM encoded private key (string should contain -----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY----- ) * - protocolMode - Enum that represents the protocol that msal follows. Used for configuring proper endpoints. * - skipAuthorityMetadataCache - A flag to choose whether to use or not use the local metadata cache during authority initialization. Defaults to false. @@ -42,7 +43,7 @@ export type NodeAuthOptions = { clientId: string; authority?: string; clientSecret?: string; - clientAssertion?: string; + clientAssertion?: string | ClientAssertionCallback; clientCertificate?: { thumbprint: string; privateKey: string; @@ -65,6 +66,9 @@ export type NodeAuthOptions = { */ export type CacheOptions = { cachePlugin?: ICachePlugin; + /** + * @deprecated claims-based-caching functionality will be removed in the next version of MSALJS + */ claimsBasedCachingEnabled?: boolean; }; diff --git a/lib/msal-node/src/error/ManagedIdentityError.ts b/lib/msal-node/src/error/ManagedIdentityError.ts index 9a67dbea09..2bac44c366 100644 --- a/lib/msal-node/src/error/ManagedIdentityError.ts +++ b/lib/msal-node/src/error/ManagedIdentityError.ts @@ -14,8 +14,6 @@ export { ManagedIdentityErrorCodes }; export const ManagedIdentityErrorMessages = { [ManagedIdentityErrorCodes.invalidManagedIdentityIdType]: "More than one ManagedIdentityIdType was provided.", - [ManagedIdentityErrorCodes.invalidResource]: - "The supplied resource is an invalid URL.", [ManagedIdentityErrorCodes.missingId]: "A ManagedIdentityId id was not provided.", [ManagedIdentityErrorCodes.MsiEnvironmentVariableUrlMalformedErrorCodes diff --git a/lib/msal-node/src/error/ManagedIdentityErrorCodes.ts b/lib/msal-node/src/error/ManagedIdentityErrorCodes.ts index f60df42b21..67a6129def 100644 --- a/lib/msal-node/src/error/ManagedIdentityErrorCodes.ts +++ b/lib/msal-node/src/error/ManagedIdentityErrorCodes.ts @@ -6,7 +6,6 @@ import { ManagedIdentityEnvironmentVariableNames } from "../utils/Constants"; export const invalidManagedIdentityIdType = "invalid_managed_identity_id_type"; -export const invalidResource = "invalid_resource"; export const missingId = "missing_client_id"; export const networkUnavailable = "network_unavailable"; export const unableToCreateAzureArc = "unable_to_create_azure_arc"; diff --git a/lib/msal-node/src/index.ts b/lib/msal-node/src/index.ts index 441c0c48ec..b2ba3251c6 100644 --- a/lib/msal-node/src/index.ts +++ b/lib/msal-node/src/index.ts @@ -125,6 +125,7 @@ export { AppTokenProviderParameters, AppTokenProviderResult, INativeBrokerPlugin, + ClientAssertionCallback, } from "@azure/msal-common"; export { version } from "./packageMetadata.js"; diff --git a/lib/msal-node/src/network/HttpClient.ts b/lib/msal-node/src/network/HttpClient.ts index f383663299..3da5a15895 100644 --- a/lib/msal-node/src/network/HttpClient.ts +++ b/lib/msal-node/src/network/HttpClient.ts @@ -36,7 +36,8 @@ export class HttpClient implements INetworkModule { */ async sendGetRequestAsync( url: string, - options?: NetworkRequestOptions + options?: NetworkRequestOptions, + timeout?: number ): Promise> { if (this.proxyUrl) { return networkRequestViaProxy( @@ -44,7 +45,8 @@ export class HttpClient implements INetworkModule { this.proxyUrl, HttpMethod.GET, options, - this.customAgentOptions as http.AgentOptions + this.customAgentOptions as http.AgentOptions, + timeout ); } else { return networkRequestViaHttps( @@ -52,7 +54,7 @@ export class HttpClient implements INetworkModule { HttpMethod.GET, options, this.customAgentOptions as https.AgentOptions, - undefined + timeout ); } } @@ -64,8 +66,7 @@ export class HttpClient implements INetworkModule { */ async sendPostRequestAsync( url: string, - options?: NetworkRequestOptions, - cancellationToken?: number + options?: NetworkRequestOptions ): Promise> { if (this.proxyUrl) { return networkRequestViaProxy( @@ -73,16 +74,14 @@ export class HttpClient implements INetworkModule { this.proxyUrl, HttpMethod.POST, options, - this.customAgentOptions as http.AgentOptions, - cancellationToken + this.customAgentOptions as http.AgentOptions ); } else { return networkRequestViaHttps( url, HttpMethod.POST, options, - this.customAgentOptions as https.AgentOptions, - cancellationToken + this.customAgentOptions as https.AgentOptions ); } } @@ -109,10 +108,6 @@ const networkRequestViaProxy = ( headers: headers, }; - if (timeout) { - tunnelRequestOptions.timeout = timeout; - } - if (agentOptions && Object.keys(agentOptions).length) { tunnelRequestOptions.agent = new http.Agent(agentOptions); } @@ -125,6 +120,11 @@ const networkRequestViaProxy = ( "Content-Type: application/x-www-form-urlencoded\r\n" + `Content-Length: ${body.length}\r\n` + `\r\n${body}`; + } else { + // optional timeout is only for get requests (regionDiscovery, for example) + if (timeout) { + tunnelRequestOptions.timeout = timeout; + } } const outgoingRequestString = `${httpMethod.toUpperCase()} ${destinationUrl.href} HTTP/1.1\r\n` + @@ -136,7 +136,7 @@ const networkRequestViaProxy = ( return new Promise>((resolve, reject) => { const request = http.request(tunnelRequestOptions); - if (tunnelRequestOptions.timeout) { + if (timeout) { request.on("timeout", () => { request.destroy(); reject(new Error("Request time out")); @@ -165,14 +165,6 @@ const networkRequestViaProxy = ( ) ); } - if (tunnelRequestOptions.timeout) { - socket.setTimeout(tunnelRequestOptions.timeout); - socket.on("timeout", () => { - request.destroy(); - socket.destroy(); - reject(new Error("Request time out")); - }); - } // make a request over an HTTP tunnel socket.write(outgoingRequestString); @@ -291,10 +283,6 @@ const networkRequestViaHttps = ( ...NetworkUtils.urlToHttpOptions(url), }; - if (timeout) { - customOptions.timeout = timeout; - } - if (agentOptions && Object.keys(agentOptions).length) { customOptions.agent = new https.Agent(agentOptions); } @@ -305,6 +293,11 @@ const networkRequestViaHttps = ( ...customOptions.headers, "Content-Length": body.length, }; + } else { + // optional timeout is only for get requests (regionDiscovery, for example) + if (timeout) { + customOptions.timeout = timeout; + } } return new Promise>((resolve, reject) => { @@ -316,6 +309,10 @@ const networkRequestViaHttps = ( request = https.request(customOptions); } + if (isPostRequest) { + request.write(body); + } + if (timeout) { request.on("timeout", () => { request.destroy(); @@ -323,10 +320,6 @@ const networkRequestViaHttps = ( }); } - if (isPostRequest) { - request.write(body); - } - request.end(); request.on("response", (response) => { diff --git a/lib/msal-node/src/packageMetadata.ts b/lib/msal-node/src/packageMetadata.ts index 84b1fc9807..84e4867a04 100644 --- a/lib/msal-node/src/packageMetadata.ts +++ b/lib/msal-node/src/packageMetadata.ts @@ -1,3 +1,3 @@ /* eslint-disable header/header */ export const name = "@azure/msal-node"; -export const version = "2.7.0"; +export const version = "2.8.0"; diff --git a/lib/msal-node/src/request/ClientCredentialRequest.ts b/lib/msal-node/src/request/ClientCredentialRequest.ts index eabd045c8c..d50437789a 100644 --- a/lib/msal-node/src/request/ClientCredentialRequest.ts +++ b/lib/msal-node/src/request/ClientCredentialRequest.ts @@ -3,7 +3,10 @@ * Licensed under the MIT License. */ -import { CommonClientCredentialRequest } from "@azure/msal-common"; +import { + ClientAssertionCallback, + CommonClientCredentialRequest, +} from "@azure/msal-common"; /** * CommonClientCredentialRequest @@ -11,7 +14,7 @@ import { CommonClientCredentialRequest } from "@azure/msal-common"; * - authority - URL of the authority, the security token service (STS) from which MSAL will acquire tokens. * - correlationId - Unique GUID set per request to trace a request end-to-end for telemetry purposes. * - skipCache - Skip token cache lookup and force request to authority to get a a new token. Defaults to false. - * - clientAssertion - A Base64Url-encoded signed JWT assertion string used in the Client Credential flow + * - clientAssertion - An assertion string or a callback function that returns an assertion string (both are Base64Url-encoded signed JWTs) used in the Client Credential flow * - tokenQueryParameters - String to string map of custom query parameters added to the /token call * @public */ @@ -25,5 +28,5 @@ export type ClientCredentialRequest = Partial< | "storeInCache" > > & { - clientAssertion?: string; + clientAssertion?: string | ClientAssertionCallback; }; diff --git a/lib/msal-node/src/utils/Constants.ts b/lib/msal-node/src/utils/Constants.ts index d840cc2208..ebb9328c77 100644 --- a/lib/msal-node/src/utils/Constants.ts +++ b/lib/msal-node/src/utils/Constants.ts @@ -65,9 +65,10 @@ export const HttpMethod = { export type HttpMethod = (typeof HttpMethod)[keyof typeof HttpMethod]; export const ProxyStatus = { - SUCCESS_RANGE_START: 200, - SUCCESS_RANGE_END: 299, - SERVER_ERROR: 500, + SUCCESS: HttpStatus.SUCCESS, + SUCCESS_RANGE_START: HttpStatus.SUCCESS_RANGE_START, + SUCCESS_RANGE_END: HttpStatus.SUCCESS_RANGE_END, + SERVER_ERROR: HttpStatus.SERVER_ERROR, } as const; export type ProxyStatus = (typeof ProxyStatus)[keyof typeof ProxyStatus]; @@ -160,7 +161,7 @@ export const MANAGED_IDENTITY_HTTP_STATUS_CODES_TO_RETRY_ON = [ HttpStatus.NOT_FOUND, HttpStatus.REQUEST_TIMEOUT, HttpStatus.TOO_MANY_REQUESTS, - HttpStatus.INTERNAL_SERVER_ERROR, + HttpStatus.SERVER_ERROR, HttpStatus.SERVICE_UNAVAILABLE, HttpStatus.GATEWAY_TIMEOUT, ]; diff --git a/lib/msal-node/test/client/ClientAssertion.spec.ts b/lib/msal-node/test/client/ClientAssertion.spec.ts index f5b51af8ad..6e0fabea4f 100644 --- a/lib/msal-node/test/client/ClientAssertion.spec.ts +++ b/lib/msal-node/test/client/ClientAssertion.spec.ts @@ -1,8 +1,13 @@ import { ClientAssertion } from "../../src/client/ClientAssertion"; -import { TEST_CONSTANTS } from "../utils/TestConstants"; +import { + DEFAULT_OPENID_CONFIG_RESPONSE, + TEST_CONSTANTS, +} from "../utils/TestConstants"; import { CryptoProvider } from "../../src/crypto/CryptoProvider"; import { EncodingUtils } from "../../src/utils/EncodingUtils"; import { JwtConstants } from "../../src/utils/Constants"; +import { getClientAssertionCallback } from "./ClientTestUtils"; +import { getClientAssertion } from "@azure/msal-common"; const jsonwebtoken = require("jsonwebtoken"); @@ -13,7 +18,7 @@ describe("Client assertion test", () => { const issuer = "client_id"; const audience = "audience"; - test("creates ClientAssertion From assertion", () => { + test("creates ClientAssertion from assertion string", () => { const assertion = ClientAssertion.fromAssertion( TEST_CONSTANTS.CLIENT_ASSERTION ); @@ -22,6 +27,23 @@ describe("Client assertion test", () => { ); }); + test("creates ClientAssertion from assertion callback (which returns a string)", async () => { + const clientAssertionCallback = getClientAssertionCallback( + TEST_CONSTANTS.CLIENT_ASSERTION + ); + + const assertionFromCallback: string = await getClientAssertion( + clientAssertionCallback, + TEST_CONSTANTS.CLIENT_ID, // value doesn't matter, will be ignored in mock callback + DEFAULT_OPENID_CONFIG_RESPONSE.body.token_endpoint // value doesn't matter, will be ignored in mock callback + ); + + const assertion = ClientAssertion.fromAssertion(assertionFromCallback); + expect(assertion.getJwt(cryptoProvider, issuer, audience)).toEqual( + TEST_CONSTANTS.CLIENT_ASSERTION + ); + }); + test("creates ClientAssertion from certificate", () => { const expectedPayload = { [JwtConstants.AUDIENCE]: audience, diff --git a/lib/msal-node/test/client/ClientCredentialClient.spec.ts b/lib/msal-node/test/client/ClientCredentialClient.spec.ts index 6efc65f76c..ef770409fa 100644 --- a/lib/msal-node/test/client/ClientCredentialClient.spec.ts +++ b/lib/msal-node/test/client/ClientCredentialClient.spec.ts @@ -20,6 +20,7 @@ import { createClientAuthError, ClientAuthErrorCodes, CacheHelpers, + GrantType, } from "@azure/msal-common"; import { ClientCredentialClient, UsernamePasswordClient } from "../../src"; import { @@ -36,6 +37,7 @@ import { import { checkMockedNetworkRequest, ClientTestUtils, + getClientAssertionCallback, mockCrypto, } from "./ClientTestUtils"; import { mockNetworkClient } from "../utils/MockNetworkClient"; @@ -98,12 +100,12 @@ describe("ClientCredentialClient unit tests", () => { clientCredentialRequest ); - const returnVal: string = - createTokenRequestBodySpy.mock.results[0].value; + const returnVal: string = await createTokenRequestBodySpy.mock + .results[0].value; const checks = { graphScope: true, clientId: true, - grantType: true, + grantType: GrantType.CLIENT_CREDENTIALS_GRANT, clientSecret: true, clientSku: true, clientVersion: true, @@ -281,12 +283,12 @@ describe("ClientCredentialClient unit tests", () => { clientCredentialRequest ); - const returnVal: string = - createTokenRequestBodySpy.mock.results[0].value; + const returnVal: string = await createTokenRequestBodySpy.mock + .results[0].value; const checks = { dstsScope: true, clientId: true, - grantType: true, + grantType: GrantType.CLIENT_CREDENTIALS_GRANT, clientSecret: true, clientSku: true, clientVersion: true, @@ -363,12 +365,12 @@ describe("ClientCredentialClient unit tests", () => { clientCredentialRequest ); - const returnVal: string = - createTokenRequestBodySpy.mock.results[0].value; + const returnVal: string = await createTokenRequestBodySpy.mock + .results[0].value; const checks = { graphScope: true, clientId: true, - grantType: true, + grantType: GrantType.CLIENT_CREDENTIALS_GRANT, clientSecret: true, clientSku: true, clientVersion: true, @@ -420,16 +422,19 @@ describe("ClientCredentialClient unit tests", () => { ])( "Validates that claims and client capabilities are correctly merged", async (claims, mergedClaims) => { - clientCredentialRequest.claims = claims; + // acquire a token with a client that has client capabilities, but no claims in the request + // verify that it comes from the IDP const authResult = (await client.acquireToken( clientCredentialRequest )) as AuthenticationResult; expect(authResult.accessToken).toEqual( CONFIDENTIAL_CLIENT_AUTHENTICATION_RESULT.body.access_token ); + expect(authResult.fromCache).toBe(false); - const returnVal: string = createTokenRequestBodySpy.mock - .results[0].value as string; + // verify that the client capabilities have been merged with the (empty) claims + const returnVal: string = await createTokenRequestBodySpy.mock + .results[0].value; expect( decodeURIComponent( returnVal @@ -437,8 +442,10 @@ describe("ClientCredentialClient unit tests", () => { .filter((key: string) => key.includes("claims="))[0] .split("claims=")[1] ) - ).toEqual(mergedClaims); + ).toEqual(CAE_CONSTANTS.MERGED_EMPTY_CLAIMS); + // acquire a token (without changing anything) and verify that it comes from the cache + // verify that it comes from the cache const cachedAuthResult = (await client.acquireToken( clientCredentialRequest )) as AuthenticationResult; @@ -446,6 +453,51 @@ describe("ClientCredentialClient unit tests", () => { CONFIDENTIAL_CLIENT_AUTHENTICATION_RESULT.body.access_token ); expect(cachedAuthResult.fromCache).toBe(true); + + // acquire a token with a client that has client capabilities, and has claims in the request + // verify that it comes from the IDP + clientCredentialRequest.claims = claims; + const authResult2 = (await client.acquireToken( + clientCredentialRequest + )) as AuthenticationResult; + expect(authResult2.accessToken).toEqual( + CONFIDENTIAL_CLIENT_AUTHENTICATION_RESULT.body.access_token + ); + expect(authResult2.fromCache).toBe(false); + + // verify that the client capabilities have been merged with the claims + const returnVal2: string = await createTokenRequestBodySpy.mock + .results[1].value; + expect( + decodeURIComponent( + returnVal2 + .split("&") + .filter((key: string) => key.includes("claims="))[0] + .split("claims=")[1] + ) + ).toEqual(mergedClaims); + + // acquire a token with a client that has client capabilities, but no claims in the request + // verify that it comes from the cache + delete clientCredentialRequest.claims; + const authResult3 = (await client.acquireToken( + clientCredentialRequest + )) as AuthenticationResult; + expect(authResult3.accessToken).toEqual( + CONFIDENTIAL_CLIENT_AUTHENTICATION_RESULT.body.access_token + ); + expect(authResult3.fromCache).toBe(true); + + // acquire a token with a client that has client capabilities, and has claims in the request + // verify that it comes from the IDP + clientCredentialRequest.claims = claims; + const authResult4 = (await client.acquireToken( + clientCredentialRequest + )) as AuthenticationResult; + expect(authResult4.accessToken).toEqual( + CONFIDENTIAL_CLIENT_AUTHENTICATION_RESULT.body.access_token + ); + expect(authResult4.fromCache).toBe(false); } ); }); @@ -476,12 +528,12 @@ describe("ClientCredentialClient unit tests", () => { clientCredentialRequest ); - const returnVal: string = createTokenRequestBodySpy.mock.results[0] - .value as string; + const returnVal: string = await createTokenRequestBodySpy.mock + .results[0].value; const checks = { graphScope: true, clientId: true, - grantType: true, + grantType: GrantType.CLIENT_CREDENTIALS_GRANT, clientSecret: true, clientSku: true, clientVersion: true, @@ -495,118 +547,127 @@ describe("ClientCredentialClient unit tests", () => { checkMockedNetworkRequest(returnVal, checks); }); - it("Uses clientAssertion from ClientConfiguration when no client assertion is added to request", async () => { - config.clientCredentials = { - ...config.clientCredentials, - clientAssertion: { - assertion: TEST_CONFIG.TEST_CONFIG_ASSERTION, - assertionType: TEST_CONFIG.TEST_ASSERTION_TYPE, - }, - }; - const client: ClientCredentialClient = new ClientCredentialClient( - config - ); - - const clientCredentialRequest: CommonClientCredentialRequest = { - authority: TEST_CONFIG.validAuthority, - correlationId: TEST_CONFIG.CORRELATION_ID, - scopes: TEST_CONFIG.DEFAULT_GRAPH_SCOPE, - claims: "{}", - }; + it.each([ + TEST_CONFIG.TEST_CONFIG_ASSERTION, + getClientAssertionCallback(TEST_CONFIG.TEST_CONFIG_ASSERTION), + ])( + "Uses clientAssertion from ClientConfiguration when no client assertion is added to request", + async (clientAssertion) => { + config.clientCredentials = { + ...config.clientCredentials, + clientAssertion: { + assertion: clientAssertion, + assertionType: TEST_CONFIG.TEST_ASSERTION_TYPE, + }, + }; + const client: ClientCredentialClient = new ClientCredentialClient( + config + ); - const authResult = (await client.acquireToken( - clientCredentialRequest - )) as AuthenticationResult; - const expectedScopes = [TEST_CONFIG.DEFAULT_GRAPH_SCOPE[0]]; - expect(authResult.scopes).toEqual(expectedScopes); - expect(authResult.accessToken).toEqual( - CONFIDENTIAL_CLIENT_AUTHENTICATION_RESULT.body.access_token - ); - expect(authResult.state).toBe(""); + const clientCredentialRequest: CommonClientCredentialRequest = { + authority: TEST_CONFIG.validAuthority, + correlationId: TEST_CONFIG.CORRELATION_ID, + scopes: TEST_CONFIG.DEFAULT_GRAPH_SCOPE, + }; - expect(createTokenRequestBodySpy.mock.lastCall[0]).toEqual( - clientCredentialRequest - ); + const authResult = (await client.acquireToken( + clientCredentialRequest + )) as AuthenticationResult; + const expectedScopes = [TEST_CONFIG.DEFAULT_GRAPH_SCOPE[0]]; + expect(authResult.scopes).toEqual(expectedScopes); + expect(authResult.accessToken).toEqual( + CONFIDENTIAL_CLIENT_AUTHENTICATION_RESULT.body.access_token + ); + expect(authResult.state).toBe(""); - const returnVal: string = createTokenRequestBodySpy.mock.results[0] - .value as string; - const checks = { - graphScope: true, - clientId: true, - grantType: true, - clientSecret: true, - clientSku: true, - clientVersion: true, - clientOs: true, - clientCpu: true, - appName: true, - appVersion: true, - msLibraryCapability: true, - claims: false, - testConfigAssertion: true, - testAssertionType: true, - }; - checkMockedNetworkRequest(returnVal, checks); - }); + expect(createTokenRequestBodySpy.mock.lastCall[0]).toEqual( + clientCredentialRequest + ); - it("Uses the clientAssertion included in the request instead of the one in ClientConfiguration", async () => { - config.clientCredentials = { - ...config.clientCredentials, - clientAssertion: { - assertion: TEST_CONFIG.TEST_CONFIG_ASSERTION, - assertionType: TEST_CONFIG.TEST_ASSERTION_TYPE, - }, - }; - const client: ClientCredentialClient = new ClientCredentialClient( - config - ); + const returnVal: string = await createTokenRequestBodySpy.mock + .results[0].value; + const checks = { + graphScope: true, + clientId: true, + grantType: GrantType.CLIENT_CREDENTIALS_GRANT, + clientSecret: true, + clientSku: true, + clientVersion: true, + clientOs: true, + clientCpu: true, + appName: true, + appVersion: true, + msLibraryCapability: true, + testConfigAssertion: true, + testAssertionType: true, + }; + checkMockedNetworkRequest(returnVal, checks); + } + ); + + it.each([ + TEST_CONFIG.TEST_REQUEST_ASSERTION, + getClientAssertionCallback(TEST_CONFIG.TEST_REQUEST_ASSERTION), + ])( + "Uses the clientAssertion included in the request instead of the one in ClientConfiguration", + async (clientAssertion) => { + config.clientCredentials = { + ...config.clientCredentials, + clientAssertion: { + assertion: + "config-assertion that will be overridden by request-assertion", + assertionType: TEST_CONFIG.TEST_ASSERTION_TYPE, + }, + }; + const client: ClientCredentialClient = new ClientCredentialClient( + config + ); - const clientCredentialRequest: CommonClientCredentialRequest = { - authority: TEST_CONFIG.validAuthority, - correlationId: TEST_CONFIG.CORRELATION_ID, - scopes: TEST_CONFIG.DEFAULT_GRAPH_SCOPE, - claims: "{}", - clientAssertion: { - assertion: TEST_CONFIG.TEST_REQUEST_ASSERTION, - assertionType: TEST_CONFIG.TEST_ASSERTION_TYPE, - }, - }; + const clientCredentialRequest: CommonClientCredentialRequest = { + authority: TEST_CONFIG.validAuthority, + correlationId: TEST_CONFIG.CORRELATION_ID, + scopes: TEST_CONFIG.DEFAULT_GRAPH_SCOPE, + clientAssertion: { + assertion: clientAssertion, + assertionType: TEST_CONFIG.TEST_ASSERTION_TYPE, + }, + }; - const authResult = (await client.acquireToken( - clientCredentialRequest - )) as AuthenticationResult; - const expectedScopes = [TEST_CONFIG.DEFAULT_GRAPH_SCOPE[0]]; - expect(authResult.scopes).toEqual(expectedScopes); - expect(authResult.accessToken).toEqual( - CONFIDENTIAL_CLIENT_AUTHENTICATION_RESULT.body.access_token - ); - expect(authResult.state).toBe(""); + const authResult = (await client.acquireToken( + clientCredentialRequest + )) as AuthenticationResult; + const expectedScopes = [TEST_CONFIG.DEFAULT_GRAPH_SCOPE[0]]; + expect(authResult.scopes).toEqual(expectedScopes); + expect(authResult.accessToken).toEqual( + CONFIDENTIAL_CLIENT_AUTHENTICATION_RESULT.body.access_token + ); + expect(authResult.state).toBe(""); - expect(createTokenRequestBodySpy.mock.lastCall[0]).toEqual( - clientCredentialRequest - ); + expect(createTokenRequestBodySpy.mock.lastCall[0]).toEqual( + clientCredentialRequest + ); - const returnVal: string = createTokenRequestBodySpy.mock.results[0] - .value as string; - const checks = { - graphScope: true, - clientId: true, - grantType: true, - clientSecret: true, - clientSku: true, - clientVersion: true, - clientOs: true, - clientCpu: true, - appName: true, - appVersion: true, - msLibraryCapability: true, - claims: false, - testConfigAssertion: false, - testRequestAssertion: true, - testAssertionType: true, - }; - checkMockedNetworkRequest(returnVal, checks); - }); + const returnVal: string = await createTokenRequestBodySpy.mock + .results[0].value; + const checks = { + graphScope: true, + clientId: true, + grantType: GrantType.CLIENT_CREDENTIALS_GRANT, + clientSecret: true, + clientSku: true, + clientVersion: true, + clientOs: true, + clientCpu: true, + appName: true, + appVersion: true, + msLibraryCapability: true, + testConfigAssertion: false, + testRequestAssertion: true, + testAssertionType: true, + }; + checkMockedNetworkRequest(returnVal, checks); + } + ); // For more information about this test see: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS it("Does not add headers that do not qualify for a simple request", async () => { @@ -790,12 +851,12 @@ describe("ClientCredentialClient unit tests", () => { clientCredentialRequest ); - const returnVal: string = createTokenRequestBodySpy.mock.results[0] - .value as string; + const returnVal: string = await createTokenRequestBodySpy.mock + .results[0].value; const checks = { graphScope: true, clientId: true, - grantType: true, + grantType: GrantType.CLIENT_CREDENTIALS_GRANT, clientSecret: true, }; checkMockedNetworkRequest(returnVal, checks); @@ -827,12 +888,12 @@ describe("ClientCredentialClient unit tests", () => { clientCredentialRequest ); - const returnVal: string = createTokenRequestBodySpy.mock.results[0] - .value as string; + const returnVal: string = await createTokenRequestBodySpy.mock + .results[0].value; const checks = { graphScope: true, clientId: true, - grantType: true, + grantType: GrantType.CLIENT_CREDENTIALS_GRANT, clientSecret: true, }; checkMockedNetworkRequest(returnVal, checks); diff --git a/lib/msal-node/test/client/ClientTestUtils.ts b/lib/msal-node/test/client/ClientTestUtils.ts index fb0fd4b4ef..85623f9db7 100644 --- a/lib/msal-node/test/client/ClientTestUtils.ts +++ b/lib/msal-node/test/client/ClientTestUtils.ts @@ -5,7 +5,6 @@ import { AADServerParamKeys, - GrantType, ThrottlingConstants, ServerTelemetryEntity, CacheManager, @@ -30,6 +29,9 @@ import { CacheHelpers, Authority, INetworkModule, + ClientAssertionCallback, + ClientAssertionConfig, + PasswordGrantConstants, } from "@azure/msal-common"; import { AUTHENTICATION_RESULT, @@ -361,7 +363,7 @@ interface checks { dstsScope?: boolean | undefined; graphScope?: boolean | undefined; clientId?: boolean | undefined; - grantType?: boolean | undefined; + grantType?: string | undefined; clientSecret?: boolean | undefined; clientSku?: boolean | undefined; clientVersion?: boolean | undefined; @@ -374,6 +376,9 @@ interface checks { testConfigAssertion?: boolean | undefined; testRequestAssertion?: boolean | undefined; testAssertionType?: boolean | undefined; + responseType?: boolean | undefined; + username?: string | undefined; + password?: string | undefined; } export const checkMockedNetworkRequest = ( @@ -405,9 +410,9 @@ export const checkMockedNetworkRequest = ( if (checks.grantType !== undefined) { expect( returnVal.includes( - `${AADServerParamKeys.GRANT_TYPE}=${GrantType.CLIENT_CREDENTIALS_GRANT}` + `${AADServerParamKeys.GRANT_TYPE}=${checks.grantType}` ) - ).toBe(checks.grantType); + ).toBe(true); } if (checks.clientSecret !== undefined) { @@ -513,4 +518,40 @@ export const checkMockedNetworkRequest = ( ) ).toBe(checks.testAssertionType); } + + if (checks.responseType !== undefined) { + expect( + returnVal.includes( + `${AADServerParamKeys.RESPONSE_TYPE}=${Constants.TOKEN_RESPONSE_TYPE}%20${Constants.ID_TOKEN_RESPONSE_TYPE}` + ) + ).toBe(checks.responseType); + } + + if (checks.username !== undefined) { + expect( + returnVal.includes( + `${PasswordGrantConstants.username}=${checks.username}` + ) + ).toBe(true); + } + + if (checks.password !== undefined) { + expect( + returnVal.includes( + `${PasswordGrantConstants.password}=${checks.password}` + ) + ).toBe(true); + } +}; + +export const getClientAssertionCallback = ( + clientAssertion: string +): ClientAssertionCallback => { + const clientAssertionCallback: ClientAssertionCallback = async ( + _config: ClientAssertionConfig + ): Promise => { + return await Promise.resolve(clientAssertion); + }; + + return clientAssertionCallback; }; diff --git a/lib/msal-node/test/client/ConfidentialClientApplication.spec.ts b/lib/msal-node/test/client/ConfidentialClientApplication.spec.ts index 583f0ce76d..5ed62f40f0 100644 --- a/lib/msal-node/test/client/ConfidentialClientApplication.spec.ts +++ b/lib/msal-node/test/client/ConfidentialClientApplication.spec.ts @@ -5,14 +5,17 @@ import { AuthorizationCodeClient, + SilentFlowClient, RefreshTokenClient, AuthenticationResult, OIDC_DEFAULT_SCOPES, CommonClientCredentialRequest, createClientAuthError, ClientAuthErrorCodes, + AccountEntity, + AccountInfo, } from "@azure/msal-common"; -import { TEST_CONSTANTS } from "../utils/TestConstants"; +import { ID_TOKEN_CLAIMS, TEST_CONSTANTS } from "../utils/TestConstants"; import { AuthError, ConfidentialClientApplication, @@ -23,6 +26,7 @@ import { AuthorizationCodeRequest, ClientCredentialClient, RefreshTokenRequest, + SilentFlowRequest, } from "../../src"; import * as msalNode from "../../src"; @@ -30,8 +34,12 @@ import { getMsalCommonAutoMock, MSALCommonModule } from "../utils/MockUtils"; import { CAE_CONSTANTS, CONFIDENTIAL_CLIENT_AUTHENTICATION_RESULT, + TEST_CONFIG, + TEST_TOKENS, } from "../test_kit/StringConstants"; import { mockNetworkClient } from "../utils/MockNetworkClient"; +import { getClientAssertionCallback } from "./ClientTestUtils"; +import { buildAccountFromIdTokenClaims } from "msal-test-utils"; const msalCommon: MSALCommonModule = jest.requireActual("@azure/msal-common"); @@ -82,6 +90,38 @@ describe("ConfidentialClientApplication", () => { expect(AuthorizationCodeClient).toHaveBeenCalledTimes(1); }); + test("acquireTokenBySilentFlow", async () => { + const testAccountEntity: AccountEntity = + buildAccountFromIdTokenClaims(ID_TOKEN_CLAIMS); + + const testAccount: AccountInfo = { + ...testAccountEntity.getAccountInfo(), + idTokenClaims: ID_TOKEN_CLAIMS, + idToken: TEST_TOKENS.IDTOKEN_V2, + }; + + const request: SilentFlowRequest = { + scopes: TEST_CONFIG.DEFAULT_GRAPH_SCOPE, + account: testAccount, + authority: TEST_CONFIG.validAuthority, + correlationId: TEST_CONFIG.CORRELATION_ID, + forceRefresh: false, + }; + + const mockSilentFlowClientInstance = { + includeRedirectUri: false, + acquireToken: jest.fn(), + }; + jest.spyOn(msalCommon, "SilentFlowClient").mockImplementation( + () => + mockSilentFlowClientInstance as unknown as SilentFlowClient + ); + + const authApp = new ConfidentialClientApplication(appConfig); + await authApp.acquireTokenSilent(request); + expect(SilentFlowClient).toHaveBeenCalledTimes(1); + }); + describe("CAE, claims and client capabilities", () => { let createTokenRequestBodySpy: jest.SpyInstance; let client: ConfidentialClientApplication; @@ -132,7 +172,8 @@ describe("ConfidentialClientApplication", () => { ])( "Validates that claims and client capabilities are correctly merged", async (claims, mergedClaims) => { - authorizationCodeRequest.claims = claims; + // acquire a token with a client that has client capabilities, but no claims in the request + // verify that it comes from the IDP const authResult = (await client.acquireTokenByCode( authorizationCodeRequest )) as AuthenticationResult; @@ -140,7 +181,9 @@ describe("ConfidentialClientApplication", () => { CONFIDENTIAL_CLIENT_AUTHENTICATION_RESULT.body .access_token ); + expect(authResult.fromCache).toBe(false); + // verify that the client capabilities have been merged with the (empty) claims const returnVal: string = (await createTokenRequestBodySpy .mock.results[0].value) as string; expect( @@ -152,9 +195,35 @@ describe("ConfidentialClientApplication", () => { )[0] .split("claims=")[1] ) - ).toEqual(mergedClaims); + ).toEqual(CAE_CONSTANTS.MERGED_EMPTY_CLAIMS); // skip cache lookup verification because acquireTokenByCode does not pull elements from the cache + + // acquire a token with a client that has client capabilities, and has claims in the request + // verify that it comes from the IDP + authorizationCodeRequest.claims = claims; + const authResult2 = (await client.acquireTokenByCode( + authorizationCodeRequest + )) as AuthenticationResult; + expect(authResult2.accessToken).toEqual( + CONFIDENTIAL_CLIENT_AUTHENTICATION_RESULT.body + .access_token + ); + expect(authResult2.fromCache).toBe(false); + + // verify that the client capabilities have been merged with the claims + const returnVal2: string = (await createTokenRequestBodySpy + .mock.results[1].value) as string; + expect( + decodeURIComponent( + returnVal2 + .split("&") + .filter((key: string) => + key.includes("claims=") + )[0] + .split("claims=")[1] + ) + ).toEqual(mergedClaims); } ); }); @@ -221,29 +290,35 @@ describe("ConfidentialClientApplication", () => { expect(ClientCredentialClient).toHaveBeenCalledTimes(1); }); - test("acquireTokenByClientCredential with client assertion", async () => { - const request: ClientCredentialRequest = { - scopes: TEST_CONSTANTS.DEFAULT_GRAPH_SCOPE, - skipCache: false, - clientAssertion: "testAssertion", - }; + it.each([ + TEST_CONFIG.TEST_REQUEST_ASSERTION, + getClientAssertionCallback(TEST_CONFIG.TEST_REQUEST_ASSERTION), + ])( + "acquireTokenByClientCredential with client assertion", + async (clientAssertion) => { + const request: ClientCredentialRequest = { + scopes: TEST_CONSTANTS.DEFAULT_GRAPH_SCOPE, + skipCache: false, + clientAssertion: clientAssertion, + }; - ClientCredentialClient.prototype.acquireToken = jest.fn( - (request: CommonClientCredentialRequest) => { - expect(request.clientAssertion).not.toBe(undefined); - expect(request.clientAssertion?.assertion).toBe( - "testAssertion" - ); - expect(request.clientAssertion?.assertionType).toBe( - "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" - ); - return Promise.resolve(null); - } - ); + ClientCredentialClient.prototype.acquireToken = jest.fn( + (request: CommonClientCredentialRequest) => { + expect(request.clientAssertion).not.toBe(undefined); + expect(request.clientAssertion?.assertion).toBe( + TEST_CONFIG.TEST_REQUEST_ASSERTION + ); + expect(request.clientAssertion?.assertionType).toBe( + "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" + ); + return Promise.resolve(null); + } + ); - const authApp = new ConfidentialClientApplication(appConfig); - await authApp.acquireTokenByClientCredential(request); - }); + const authApp = new ConfidentialClientApplication(appConfig); + await authApp.acquireTokenByClientCredential(request); + } + ); test("acquireTokenOnBehalfOf", async () => { const request: OnBehalfOfRequest = { diff --git a/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts b/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts index ef2f149f58..e05a62a5ec 100644 --- a/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts +++ b/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts @@ -785,16 +785,6 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { const systemAssignedManagedIdentityApplication: ManagedIdentityApplication = new ManagedIdentityApplication(systemAssignedConfig); - await expect( - systemAssignedManagedIdentityApplication.acquireToken({ - resource: "invalid_resource", - }) - ).rejects.toMatchObject( - createManagedIdentityError( - ManagedIdentityErrorCodes.invalidResource - ) - ); - await expect( systemAssignedManagedIdentityApplication.acquireToken({ resource: "", @@ -822,7 +812,7 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { expect(() => { new ManagedIdentityApplication(badUserAssignedClientIdConfig); - }).toThrowError( + }).toThrow( createManagedIdentityError( ManagedIdentityErrorCodes.invalidManagedIdentityIdType ) diff --git a/lib/msal-node/test/client/OnBehalfOfClient.spec.ts b/lib/msal-node/test/client/OnBehalfOfClient.spec.ts index 3ec594205c..33f50a3cdb 100644 --- a/lib/msal-node/test/client/OnBehalfOfClient.spec.ts +++ b/lib/msal-node/test/client/OnBehalfOfClient.spec.ts @@ -25,7 +25,11 @@ import { TEST_CONFIG, TEST_TOKENS, } from "../test_kit/StringConstants"; -import { checkMockedNetworkRequest, ClientTestUtils } from "./ClientTestUtils"; +import { + checkMockedNetworkRequest, + ClientTestUtils, + getClientAssertionCallback, +} from "./ClientTestUtils"; import { EncodingUtils } from "../../src/utils/EncodingUtils"; import { mockNetworkClient } from "../utils/MockNetworkClient"; @@ -86,8 +90,8 @@ describe("OnBehalfOf unit tests", () => { oboRequest ); - const returnVal: string = - createTokenRequestBodySpy.mock.results[0].value; + const returnVal: string = await createTokenRequestBodySpy.mock + .results[0].value; const checks = { graphScope: true, clientId: true, @@ -142,16 +146,19 @@ describe("OnBehalfOf unit tests", () => { ])( "Validates that claims and client capabilities are correctly merged", async (claims, mergedClaims) => { - oboRequest.claims = claims; + // acquire a token with a client that has client capabilities, but no claims in the request + // verify that it comes from the IDP const authResult = (await client.acquireToken( oboRequest )) as AuthenticationResult; expect(authResult.accessToken).toEqual( AUTHENTICATION_RESULT.body.access_token ); + expect(authResult.fromCache).toBe(false); - const returnVal: string = createTokenRequestBodySpy.mock - .results[0].value as string; + // verify that the client capabilities have been merged with the (empty) claims + const returnVal: string = await createTokenRequestBodySpy + .mock.results[0].value; expect( decodeURIComponent( returnVal @@ -161,8 +168,10 @@ describe("OnBehalfOf unit tests", () => { )[0] .split("claims=")[1] ) - ).toEqual(mergedClaims); + ).toEqual(CAE_CONSTANTS.MERGED_EMPTY_CLAIMS); + // acquire a token (without changing anything) and verify that it comes from the cache + // verify that it comes from the cache const cachedAuthResult = (await client.acquireToken( oboRequest )) as AuthenticationResult; @@ -170,6 +179,53 @@ describe("OnBehalfOf unit tests", () => { AUTHENTICATION_RESULT.body.access_token ); expect(cachedAuthResult.fromCache).toBe(true); + + // acquire a token with a client that has client capabilities, and has claims in the request + // verify that it comes from the IDP + oboRequest.claims = claims; + const authResult2 = (await client.acquireToken( + oboRequest + )) as AuthenticationResult; + expect(authResult2.accessToken).toEqual( + AUTHENTICATION_RESULT.body.access_token + ); + expect(authResult2.fromCache).toBe(false); + + // verify that the client capabilities have been merged with the claims + const returnVal2: string = await createTokenRequestBodySpy + .mock.results[1].value; + expect( + decodeURIComponent( + returnVal2 + .split("&") + .filter((key: string) => + key.includes("claims=") + )[0] + .split("claims=")[1] + ) + ).toEqual(mergedClaims); + + // acquire a token with a client that has client capabilities, but no claims in the request + // verify that it comes from the cache + delete oboRequest.claims; + const authResult3 = (await client.acquireToken( + oboRequest + )) as AuthenticationResult; + expect(authResult3.accessToken).toEqual( + AUTHENTICATION_RESULT.body.access_token + ); + expect(authResult3.fromCache).toBe(true); + + // acquire a token with a client that has client capabilities, and has claims in the request + // verify that it comes from the IDP + oboRequest.claims = claims; + const authResult4 = (await client.acquireToken( + oboRequest + )) as AuthenticationResult; + expect(authResult4.accessToken).toEqual( + AUTHENTICATION_RESULT.body.access_token + ); + expect(authResult4.fromCache).toBe(false); } ); }); @@ -239,8 +295,8 @@ describe("OnBehalfOf unit tests", () => { oboRequest ); - const returnVal: string = - createTokenRequestBodySpy.mock.results[0].value; + const returnVal: string = await createTokenRequestBodySpy.mock + .results[0].value; const checks = { graphScope: true, clientId: true, @@ -350,5 +406,60 @@ describe("OnBehalfOf unit tests", () => { testAccessTokenEntity.homeAccountId ); }); + + it.each([ + TEST_CONFIG.TEST_CONFIG_ASSERTION, + getClientAssertionCallback(TEST_CONFIG.TEST_CONFIG_ASSERTION), + ])( + "Uses clientAssertion from ClientConfiguration when no client assertion is added to request", + async (clientAssertion) => { + config.clientCredentials = { + ...config.clientCredentials, + clientAssertion: { + assertion: clientAssertion, + assertionType: TEST_CONFIG.TEST_ASSERTION_TYPE, + }, + }; + const client: OnBehalfOfClient = new OnBehalfOfClient(config); + + const oboRequest: CommonOnBehalfOfRequest = { + scopes: [...TEST_CONFIG.DEFAULT_GRAPH_SCOPE], + authority: TEST_CONFIG.validAuthority, + correlationId: TEST_CONFIG.CORRELATION_ID, + oboAssertion: "user_assertion_hash", + skipCache: true, + }; + + const authResult = (await client.acquireToken( + oboRequest + )) as AuthenticationResult; + expect(authResult.accessToken).toEqual( + AUTHENTICATION_RESULT.body.access_token + ); + expect(authResult.state).toBe(""); + expect(authResult.fromCache).toBe(false); + + expect(createTokenRequestBodySpy.mock.lastCall[0]).toEqual( + oboRequest + ); + + const returnVal: string = await createTokenRequestBodySpy.mock + .results[0].value; + const checks = { + graphScope: true, + clientId: true, + clientSecret: true, + clientSku: true, + clientVersion: true, + clientOs: true, + appName: true, + appVersion: true, + msLibraryCapability: true, + testConfigAssertion: true, + testAssertionType: true, + }; + checkMockedNetworkRequest(returnVal, checks); + } + ); }); }); diff --git a/lib/msal-node/test/client/UsernamePasswordClient.spec.ts b/lib/msal-node/test/client/UsernamePasswordClient.spec.ts index 8c07a523d6..d4ad46ebef 100644 --- a/lib/msal-node/test/client/UsernamePasswordClient.spec.ts +++ b/lib/msal-node/test/client/UsernamePasswordClient.spec.ts @@ -3,43 +3,53 @@ * Licensed under the MIT License. */ -import sinon from "sinon"; import { - AADServerParamKeys, AuthenticationResult, - Authority, BaseClient, ClientConfiguration, CommonUsernamePasswordRequest, Constants, GrantType, - PasswordGrantConstants, - ThrottlingConstants, } from "@azure/msal-common"; import { AUTHENTICATION_RESULT_DEFAULT_SCOPES, DEFAULT_OPENID_CONFIG_RESPONSE, + MOCK_PASSWORD, + MOCK_USERNAME, RANDOM_TEST_GUID, TEST_CONFIG, } from "../test_kit/StringConstants"; import { UsernamePasswordClient } from "../../src"; -import { ClientTestUtils } from "./ClientTestUtils"; +import { + ClientTestUtils, + checkMockedNetworkRequest, + getClientAssertionCallback, +} from "./ClientTestUtils"; +import { mockNetworkClient } from "../utils/MockNetworkClient"; describe("Username Password unit tests", () => { + let createTokenRequestBodySpy: jest.SpyInstance; let config: ClientConfiguration; - beforeEach(async () => { - sinon - .stub(Authority.prototype, "getEndpointMetadataFromNetwork") - .resolves(DEFAULT_OPENID_CONFIG_RESPONSE.body); - config = await ClientTestUtils.createTestClientConfiguration(); + createTokenRequestBodySpy = jest.spyOn( + UsernamePasswordClient.prototype, + "createTokenRequestBody" + ); + + config = await ClientTestUtils.createTestClientConfiguration( + undefined, + mockNetworkClient( + DEFAULT_OPENID_CONFIG_RESPONSE.body, + AUTHENTICATION_RESULT_DEFAULT_SCOPES + ) + ); if (config.systemOptions) { config.systemOptions.preventCorsPreflight = true; } }); afterEach(() => { - sinon.restore(); + jest.restoreAllMocks(); }); describe("Constructor", () => { @@ -52,24 +62,13 @@ describe("Username Password unit tests", () => { }); it("acquires a token", async () => { - sinon - .stub( - UsernamePasswordClient.prototype, - "executePostToTokenEndpoint" - ) - .resolves(AUTHENTICATION_RESULT_DEFAULT_SCOPES); - - const createTokenRequestBodySpy = sinon.spy( - UsernamePasswordClient.prototype, - "createTokenRequestBody" - ); - const client = new UsernamePasswordClient(config); + const usernamePasswordRequest: CommonUsernamePasswordRequest = { authority: Constants.DEFAULT_AUTHORITY, scopes: TEST_CONFIG.DEFAULT_GRAPH_SCOPE, - username: "mock_name", - password: "mock_password", + username: MOCK_USERNAME, + password: MOCK_PASSWORD, claims: TEST_CONFIG.CLAIMS, correlationId: RANDOM_TEST_GUID, }; @@ -92,107 +91,50 @@ describe("Username Password unit tests", () => { ); expect(authResult.state).toHaveLength(0); - expect( - createTokenRequestBodySpy.calledWith(usernamePasswordRequest) - ).toBe(true); + expect(createTokenRequestBodySpy.mock.lastCall[0]).toEqual( + usernamePasswordRequest + ); - expect( - createTokenRequestBodySpy.returnValues[0].includes( - `${TEST_CONFIG.DEFAULT_GRAPH_SCOPE[0]}` - ) - ).toBe(true); - expect( - createTokenRequestBodySpy.returnValues[0].includes( - `${AADServerParamKeys.CLIENT_ID}=${encodeURIComponent( - TEST_CONFIG.MSAL_CLIENT_ID - )}` - ) - ).toBe(true); - expect( - createTokenRequestBodySpy.returnValues[0].includes( - `${AADServerParamKeys.GRANT_TYPE}=${encodeURIComponent( - GrantType.RESOURCE_OWNER_PASSWORD_GRANT - )}` - ) - ).toBe(true); - expect( - createTokenRequestBodySpy.returnValues[0].includes( - `${PasswordGrantConstants.username}=mock_name` - ) - ).toBe(true); - expect( - createTokenRequestBodySpy.returnValues[0].includes( - `${PasswordGrantConstants.password}=mock_password` - ) - ).toBe(true); - expect( - createTokenRequestBodySpy.returnValues[0].includes( - `${AADServerParamKeys.X_CLIENT_SKU}=${Constants.SKU}` - ) - ).toBe(true); - expect( - createTokenRequestBodySpy.returnValues[0].includes( - `${AADServerParamKeys.X_CLIENT_VER}=${TEST_CONFIG.TEST_VERSION}` - ) - ).toBe(true); - expect( - createTokenRequestBodySpy.returnValues[0].includes( - `${AADServerParamKeys.X_CLIENT_OS}=${TEST_CONFIG.TEST_OS}` - ) - ).toBe(true); - expect( - createTokenRequestBodySpy.returnValues[0].includes( - `${AADServerParamKeys.X_CLIENT_CPU}=${TEST_CONFIG.TEST_CPU}` - ) - ).toBe(true); - expect( - createTokenRequestBodySpy.returnValues[0].includes( - `${AADServerParamKeys.X_APP_NAME}=${TEST_CONFIG.applicationName}` - ) - ).toBe(true); - expect( - createTokenRequestBodySpy.returnValues[0].includes( - `${AADServerParamKeys.X_APP_VER}=${TEST_CONFIG.applicationVersion}` - ) - ).toBe(true); - expect( - createTokenRequestBodySpy.returnValues[0].includes( - `${AADServerParamKeys.X_MS_LIB_CAPABILITY}=${ThrottlingConstants.X_MS_LIB_CAPABILITY_VALUE}` - ) - ).toBe(true); - expect( - createTokenRequestBodySpy.returnValues[0].includes( - `${AADServerParamKeys.RESPONSE_TYPE}=${Constants.TOKEN_RESPONSE_TYPE}%20${Constants.ID_TOKEN_RESPONSE_TYPE}` - ) - ).toBe(true); + const returnVal: string = await createTokenRequestBodySpy.mock + .results[0].value; + const checks = { + graphScope: true, + clientId: true, + grantType: GrantType.RESOURCE_OWNER_PASSWORD_GRANT, + clientSecret: true, + clientSku: true, + clientVersion: true, + clientOs: true, + clientCpu: true, + appName: true, + appVersion: true, + msLibraryCapability: true, + claims: true, + responseType: true, + username: MOCK_USERNAME, + password: MOCK_PASSWORD, + }; + checkMockedNetworkRequest(returnVal, checks); }); - it("Adds tokenQueryParameters to the /token request", (done) => { - sinon - .stub( - UsernamePasswordClient.prototype, - "executePostToTokenEndpoint" - ) - .callsFake((url: string) => { - try { - expect( - url.includes( - "/token?testParam1=testValue1&testParam3=testValue3" - ) - ).toBeTruthy(); - expect(!url.includes("/token?testParam2=")).toBeTruthy(); - done(); - } catch (error) { - done(error); - } - }); + it("Adds tokenQueryParameters to the /token request", async () => { + const badExecutePostToTokenEndpointMock = jest.spyOn( + UsernamePasswordClient.prototype, + "executePostToTokenEndpoint" + ); + // no implementation has been mocked, the acquireToken call will fail + + const fakeConfig: ClientConfiguration = + await ClientTestUtils.createTestClientConfiguration(); + const client: UsernamePasswordClient = new UsernamePasswordClient( + fakeConfig + ); - const client = new UsernamePasswordClient(config); const usernamePasswordRequest: CommonUsernamePasswordRequest = { authority: Constants.DEFAULT_AUTHORITY, scopes: TEST_CONFIG.DEFAULT_GRAPH_SCOPE, - username: "mock_name", - password: "mock_password", + username: MOCK_USERNAME, + password: MOCK_PASSWORD, claims: TEST_CONFIG.CLAIMS, correlationId: RANDOM_TEST_GUID, tokenQueryParameters: { @@ -202,30 +144,29 @@ describe("Username Password unit tests", () => { }, }; - client.acquireToken(usernamePasswordRequest).catch(() => { - // Catch errors thrown after the function call this test is testing - }); + await expect( + client.acquireToken(usernamePasswordRequest) + ).rejects.toThrow(); + + if (!badExecutePostToTokenEndpointMock.mock.lastCall) { + fail("executePostToTokenEndpointMock was not called"); + } + const url: string = badExecutePostToTokenEndpointMock.mock + .lastCall[0] as string; + expect( + url.includes("/token?testParam1=testValue1&testParam3=testValue3") + ).toBeTruthy(); + expect(!url.includes("/token?testParam2=")).toBeTruthy(); }); it("properly encodes special characters in emails (usernames)", async () => { - sinon - .stub( - UsernamePasswordClient.prototype, - "executePostToTokenEndpoint" - ) - .resolves(AUTHENTICATION_RESULT_DEFAULT_SCOPES); - - const createTokenRequestBodySpy = sinon.spy( - UsernamePasswordClient.prototype, - "createTokenRequestBody" - ); - const client = new UsernamePasswordClient(config); + const usernamePasswordRequest: CommonUsernamePasswordRequest = { authority: Constants.DEFAULT_AUTHORITY, scopes: TEST_CONFIG.DEFAULT_GRAPH_SCOPE, - username: "mock+name", - password: "mock_password", + username: `${MOCK_USERNAME}&+`, + password: MOCK_PASSWORD, claims: TEST_CONFIG.CLAIMS, correlationId: RANDOM_TEST_GUID, }; @@ -248,36 +189,40 @@ describe("Username Password unit tests", () => { ); expect(authResult.state).toHaveLength(0); - expect( - createTokenRequestBodySpy.calledWith(usernamePasswordRequest) - ).toBe(true); + expect(createTokenRequestBodySpy.mock.lastCall[0]).toEqual( + usernamePasswordRequest + ); - expect( - createTokenRequestBodySpy.returnValues[0].includes( - `${PasswordGrantConstants.username}=mock%2Bname` - ) - ).toBe(true); + const returnVal: string = await createTokenRequestBodySpy.mock + .results[0].value; + const checks = { + graphScope: true, + clientId: true, + grantType: GrantType.RESOURCE_OWNER_PASSWORD_GRANT, + clientSecret: true, + clientSku: true, + clientVersion: true, + clientOs: true, + clientCpu: true, + appName: true, + appVersion: true, + msLibraryCapability: true, + claims: true, + responseType: true, + username: `${MOCK_USERNAME}%26%2B`, + password: MOCK_PASSWORD, + }; + checkMockedNetworkRequest(returnVal, checks); }); it("properly encodes special characters in passwords", async () => { - sinon - .stub( - UsernamePasswordClient.prototype, - "executePostToTokenEndpoint" - ) - .resolves(AUTHENTICATION_RESULT_DEFAULT_SCOPES); - - const createTokenRequestBodySpy = sinon.spy( - UsernamePasswordClient.prototype, - "createTokenRequestBody" - ); - const client = new UsernamePasswordClient(config); + const usernamePasswordRequest: CommonUsernamePasswordRequest = { authority: Constants.DEFAULT_AUTHORITY, scopes: TEST_CONFIG.DEFAULT_GRAPH_SCOPE, - username: "mock_name", - password: "mock_password&+", + username: MOCK_USERNAME, + password: `${MOCK_PASSWORD}&+`, claims: TEST_CONFIG.CLAIMS, correlationId: RANDOM_TEST_GUID, }; @@ -300,36 +245,40 @@ describe("Username Password unit tests", () => { ); expect(authResult.state).toHaveLength(0); - expect( - createTokenRequestBodySpy.calledWith(usernamePasswordRequest) - ).toBe(true); + expect(createTokenRequestBodySpy.mock.lastCall[0]).toEqual( + usernamePasswordRequest + ); - expect( - createTokenRequestBodySpy.returnValues[0].includes( - `${PasswordGrantConstants.password}=mock_password%26%2B` - ) - ).toBe(true); + const returnVal: string = await createTokenRequestBodySpy.mock + .results[0].value; + const checks = { + graphScope: true, + clientId: true, + grantType: GrantType.RESOURCE_OWNER_PASSWORD_GRANT, + clientSecret: true, + clientSku: true, + clientVersion: true, + clientOs: true, + clientCpu: true, + appName: true, + appVersion: true, + msLibraryCapability: true, + claims: true, + responseType: true, + username: MOCK_USERNAME, + password: `${MOCK_PASSWORD}%26%2B`, + }; + checkMockedNetworkRequest(returnVal, checks); }); it("Does not include claims if empty object is passed", async () => { - sinon - .stub( - UsernamePasswordClient.prototype, - "executePostToTokenEndpoint" - ) - .resolves(AUTHENTICATION_RESULT_DEFAULT_SCOPES); - - const createTokenRequestBodySpy = sinon.spy( - UsernamePasswordClient.prototype, - "createTokenRequestBody" - ); - const client = new UsernamePasswordClient(config); + const usernamePasswordRequest: CommonUsernamePasswordRequest = { authority: Constants.DEFAULT_AUTHORITY, scopes: TEST_CONFIG.DEFAULT_GRAPH_SCOPE, - username: "mock_name", - password: "mock_password", + username: MOCK_USERNAME, + password: MOCK_PASSWORD, correlationId: RANDOM_TEST_GUID, claims: "{}", }; @@ -352,80 +301,100 @@ describe("Username Password unit tests", () => { ); expect(authResult.state).toBe(""); - expect( - createTokenRequestBodySpy.calledWith(usernamePasswordRequest) - ).toBe(true); + expect(createTokenRequestBodySpy.mock.lastCall[0]).toEqual( + usernamePasswordRequest + ); - expect( - createTokenRequestBodySpy.returnValues[0].includes( - `${TEST_CONFIG.DEFAULT_GRAPH_SCOPE[0]}` - ) - ).toBe(true); - expect( - createTokenRequestBodySpy.returnValues[0].includes( - `${AADServerParamKeys.CLIENT_ID}=${encodeURIComponent( - TEST_CONFIG.MSAL_CLIENT_ID - )}` - ) - ).toBe(true); - expect( - createTokenRequestBodySpy.returnValues[0].includes( - `${AADServerParamKeys.GRANT_TYPE}=${encodeURIComponent( - GrantType.RESOURCE_OWNER_PASSWORD_GRANT - )}` - ) - ).toBe(true); - expect( - createTokenRequestBodySpy.returnValues[0].includes( - `${PasswordGrantConstants.username}=mock_name` - ) - ).toBe(true); - expect( - createTokenRequestBodySpy.returnValues[0].includes( - `${PasswordGrantConstants.password}=mock_password` - ) - ).toBe(true); - expect( - createTokenRequestBodySpy.returnValues[0].includes( - `${AADServerParamKeys.CLAIMS}=${encodeURIComponent( - TEST_CONFIG.CLAIMS - )}` - ) - ).toBe(false); - expect( - createTokenRequestBodySpy.returnValues[0].includes( - `${AADServerParamKeys.X_CLIENT_SKU}=${Constants.SKU}` - ) - ).toBe(true); - expect( - createTokenRequestBodySpy.returnValues[0].includes( - `${AADServerParamKeys.X_CLIENT_VER}=${TEST_CONFIG.TEST_VERSION}` - ) - ).toBe(true); - expect( - createTokenRequestBodySpy.returnValues[0].includes( - `${AADServerParamKeys.X_CLIENT_OS}=${TEST_CONFIG.TEST_OS}` - ) - ).toBe(true); - expect( - createTokenRequestBodySpy.returnValues[0].includes( - `${AADServerParamKeys.X_CLIENT_CPU}=${TEST_CONFIG.TEST_CPU}` - ) - ).toBe(true); - expect( - createTokenRequestBodySpy.returnValues[0].includes( - `${AADServerParamKeys.X_APP_NAME}=${TEST_CONFIG.applicationName}` - ) - ).toBe(true); - expect( - createTokenRequestBodySpy.returnValues[0].includes( - `${AADServerParamKeys.X_APP_VER}=${TEST_CONFIG.applicationVersion}` - ) - ).toBe(true); - expect( - createTokenRequestBodySpy.returnValues[0].includes( - `${AADServerParamKeys.X_MS_LIB_CAPABILITY}=${ThrottlingConstants.X_MS_LIB_CAPABILITY_VALUE}` - ) - ).toBe(true); + const returnVal: string = await createTokenRequestBodySpy.mock + .results[0].value; + const checks = { + graphScope: true, + clientId: true, + grantType: GrantType.RESOURCE_OWNER_PASSWORD_GRANT, + clientSecret: true, + clientSku: true, + clientVersion: true, + clientOs: true, + clientCpu: true, + appName: true, + appVersion: true, + msLibraryCapability: true, + claims: false, + responseType: true, + username: MOCK_USERNAME, + password: MOCK_PASSWORD, + }; + checkMockedNetworkRequest(returnVal, checks); }); + + it.each([ + TEST_CONFIG.TEST_CONFIG_ASSERTION, + getClientAssertionCallback(TEST_CONFIG.TEST_CONFIG_ASSERTION), + ])( + "Uses clientAssertion from ClientConfiguration when no client assertion is added to request", + async (clientAssertion) => { + config.clientCredentials = { + ...config.clientCredentials, + clientAssertion: { + assertion: clientAssertion, + assertionType: TEST_CONFIG.TEST_ASSERTION_TYPE, + }, + }; + const client: UsernamePasswordClient = new UsernamePasswordClient( + config + ); + + const usernamePasswordRequest: CommonUsernamePasswordRequest = { + authority: Constants.DEFAULT_AUTHORITY, + scopes: TEST_CONFIG.DEFAULT_GRAPH_SCOPE, + username: MOCK_USERNAME, + password: MOCK_PASSWORD, + correlationId: RANDOM_TEST_GUID, + }; + + const authResult = (await client.acquireToken( + usernamePasswordRequest + )) as AuthenticationResult; + const expectedScopes = [ + Constants.OPENID_SCOPE, + Constants.PROFILE_SCOPE, + Constants.OFFLINE_ACCESS_SCOPE, + TEST_CONFIG.DEFAULT_GRAPH_SCOPE[0], + ]; + expect(authResult.scopes).toEqual(expectedScopes); + expect(authResult.idToken).toEqual( + AUTHENTICATION_RESULT_DEFAULT_SCOPES.body.id_token + ); + expect(authResult.accessToken).toEqual( + AUTHENTICATION_RESULT_DEFAULT_SCOPES.body.access_token + ); + expect(authResult.state).toBe(""); + + expect(createTokenRequestBodySpy.mock.lastCall[0]).toEqual( + usernamePasswordRequest + ); + + const returnVal: string = await createTokenRequestBodySpy.mock + .results[0].value; + const checks = { + graphScope: true, + clientId: true, + grantType: GrantType.RESOURCE_OWNER_PASSWORD_GRANT, + clientSecret: true, + clientSku: true, + clientVersion: true, + clientOs: true, + clientCpu: true, + appName: true, + appVersion: true, + msLibraryCapability: true, + responseType: true, + username: MOCK_USERNAME, + password: MOCK_PASSWORD, + testConfigAssertion: true, + testAssertionType: true, + }; + checkMockedNetworkRequest(returnVal, checks); + } + ); }); diff --git a/lib/msal-node/test/network/HttpClient.spec.ts b/lib/msal-node/test/network/HttpClient.spec.ts index 48bda31fc1..87f7fff445 100644 --- a/lib/msal-node/test/network/HttpClient.spec.ts +++ b/lib/msal-node/test/network/HttpClient.spec.ts @@ -3,8 +3,10 @@ import { NetworkResponse, NetworkRequestOptions, UrlToHttpRequestOptions, + HttpStatus, } from "@azure/msal-common"; import { MockedMetadataResponse } from "../utils/TestConstants"; +import { ProxyStatus } from "../../src/utils/Constants"; import http from "http"; jest.mock("http", () => ({ @@ -18,20 +20,18 @@ jest.mock("https", () => ({ request: jest.fn(), })); -const httpsStatusCode200 = 200; -const httpsStatusCode400 = 400; -const httpsStatusCode500 = 500; -const httpsStatusCode600 = 600; -const httpsStatusMessage200 = "OK"; -const httpsStatusMessage400 = "Bad Request"; -const httpsStatusMessage500 = "Internal Server Error"; -const httpsStatusMessage600 = "Unknown Error"; -const proxyStatusCode200 = 200; -const proxyStatusCode500 = 500; -const socketStatusCode200 = 200; -const socketStatusCode400 = 400; -const socketStatusCode500 = 500; -const socketStatusCode600 = 600; +const httpsStatusSuccessMessage = "OK"; +const httpsStatusClientErrorMessage = "Bad Request"; +const httpsStatusServerErrorMessage = "Internal Server Error"; +const httpsStatusMultiSidedErrorMessage = "Unknown Error"; + +const SocketStatus = { + SUCCESS: HttpStatus.SUCCESS, + CLIENT_ERROR: HttpStatus.CLIENT_ERROR, + SERVER_ERROR: HttpStatus.SERVER_ERROR, + MULTI_SIDED_ERROR: HttpStatus.MULTI_SIDED_ERROR, +} as const; +type SocketStatus = (typeof SocketStatus)[keyof typeof SocketStatus]; const url: string = "https://www.url.com"; @@ -94,15 +94,15 @@ const mockPostResponseBodyBuffer: Buffer = Buffer.from( JSON.stringify(mockPostResponseBody) ); -const mockServer400ErrorResponseBody = httpsStatusMessage400; +const mockServer400ErrorResponseBody = httpsStatusClientErrorMessage; const mockServer400ErrorResponseBodyBuffer: Buffer = Buffer.from( mockServer400ErrorResponseBody ); -const mockServer500ErrorResponseBody = httpsStatusMessage500; +const mockServer500ErrorResponseBody = httpsStatusServerErrorMessage; const mockServer500ErrorResponseBodyBuffer: Buffer = Buffer.from( mockServer500ErrorResponseBody ); -const mockServer600ErrorResponseBody = httpsStatusMessage600; +const mockServer600ErrorResponseBody = httpsStatusMultiSidedErrorMessage; const mockServer600ErrorResponseBodyBuffer: Buffer = Buffer.from( mockServer600ErrorResponseBody ); @@ -119,15 +119,15 @@ const getMockServerErrorResponse = ( if (statusCode >= 400 && statusCode <= 499) { errorType = "client_error"; errorDescriptionHelper = "A client"; - statusMessage = httpsStatusMessage400; + statusMessage = httpsStatusClientErrorMessage; } else if (statusCode >= 500 && statusCode <= 599) { errorType = "server_error"; errorDescriptionHelper = "A server"; - statusMessage = httpsStatusMessage500; + statusMessage = httpsStatusServerErrorMessage; } else { errorType = "unknown_error"; errorDescriptionHelper = "An unknown"; - statusMessage = httpsStatusMessage600; + statusMessage = httpsStatusMultiSidedErrorMessage; } return { @@ -218,19 +218,19 @@ const mockHttpRequest = ( socketRequestStatusCode: number ) => { const bodyString = - socketRequestStatusCode !== socketStatusCode200 + socketRequestStatusCode !== SocketStatus.SUCCESS ? body : JSON.stringify(body); let statusMessage; - if (socketRequestStatusCode === socketStatusCode200) { - statusMessage = httpsStatusMessage200; - } else if (socketRequestStatusCode === socketStatusCode400) { - statusMessage = httpsStatusMessage400; - } else if (socketRequestStatusCode === socketStatusCode500) { - statusMessage = httpsStatusMessage500; + if (socketRequestStatusCode === SocketStatus.SUCCESS) { + statusMessage = httpsStatusSuccessMessage; + } else if (socketRequestStatusCode === SocketStatus.CLIENT_ERROR) { + statusMessage = httpsStatusClientErrorMessage; + } else if (socketRequestStatusCode === SocketStatus.SERVER_ERROR) { + statusMessage = httpsStatusServerErrorMessage; } else { - statusMessage = httpsStatusMessage600; + statusMessage = httpsStatusMultiSidedErrorMessage; } const mockSocketResponse = `HTTP/1.1 ${socketRequestStatusCode} ${statusMessage}${headersString}${bodyString}`; @@ -238,13 +238,11 @@ const mockHttpRequest = ( // sample socket object const mockSocket: { - setTimeout: jest.Mock; on: jest.Mock; destroy: jest.Mock; write: jest.Mock; error: jest.Mock; } = { - setTimeout: jest.fn(), on: jest.fn((_socketEvent: string, cb: any) => cb(mockSocketResponseBuffer) ), @@ -301,13 +299,13 @@ describe("HttpClient", () => { test("Via Https", async () => { const httpsNetworkResponse: NetworkResponse = getNetworkResponse( mockGetResponseBody, - httpsStatusCode200 + HttpStatus.SUCCESS ); (https.request as jest.Mock).mockImplementationOnce( mockHttpsRequest( mockGetResponseBodyBuffer, - httpsStatusCode200, - httpsStatusMessage200 + HttpStatus.SUCCESS, + httpsStatusSuccessMessage ) ); await expect( @@ -317,12 +315,12 @@ describe("HttpClient", () => { test("Via Proxy", async () => { const proxyThenSocketNetworkResponse: NetworkResponse = - getNetworkResponse(mockGetResponseBody, httpsStatusCode200); + getNetworkResponse(mockGetResponseBody, HttpStatus.SUCCESS); (http.request as jest.Mock).mockImplementationOnce( mockHttpRequest( mockGetResponseBody, - proxyStatusCode200, - socketStatusCode200 + ProxyStatus.SUCCESS, + SocketStatus.SUCCESS ) ); await expect( @@ -335,13 +333,13 @@ describe("HttpClient", () => { test("Via Https", async () => { const httpsNetworkResponse: NetworkResponse = getNetworkResponse( mockPostResponseBody, - httpsStatusCode200 + HttpStatus.SUCCESS ); (https.request as jest.Mock).mockImplementationOnce( mockHttpsRequest( mockPostResponseBodyBuffer, - httpsStatusCode200, - httpsStatusMessage200 + HttpStatus.SUCCESS, + httpsStatusSuccessMessage ) ); await expect( @@ -354,12 +352,12 @@ describe("HttpClient", () => { test("Via Proxy", async () => { const proxyThenSocketNetworkResponse: NetworkResponse = - getNetworkResponse(mockPostResponseBody, httpsStatusCode200); + getNetworkResponse(mockPostResponseBody, HttpStatus.SUCCESS); (http.request as jest.Mock).mockImplementationOnce( mockHttpRequest( mockPostResponseBody, - proxyStatusCode200, - socketStatusCode200 + ProxyStatus.SUCCESS, + SocketStatus.SUCCESS ) ); await expect( @@ -378,38 +376,15 @@ describe("HttpClient", () => { test("Via Https", async () => { (https.request as jest.Mock).mockImplementationOnce( mockHttpsRequest( - mockPostResponseBodyBuffer, - httpsStatusCode200, - httpsStatusMessage200 - ) - ); - await expect( - httpClientWithoutProxyUrl.sendPostRequestAsync( - url, - postNetworkRequestOptions, - timeoutInMilliseconds - ) - ).rejects.toEqual(error); - }); - - /** - * can only test http timeout, not socket timeout inside of the socket - * - * future work: add another timeout parameter to sendPostRequestAsync so it accepts two different timeouts: - * one for http timeout and one for the socket timeout inside of the socket - */ - test("Via Proxy", async () => { - (http.request as jest.Mock).mockImplementationOnce( - mockHttpRequest( - mockPostResponseBody, - proxyStatusCode200, - socketStatusCode200 + mockGetResponseBodyBuffer, + HttpStatus.SUCCESS, + httpsStatusSuccessMessage ) ); await expect( - httpClientWithProxyUrl.sendPostRequestAsync( + httpClientWithoutProxyUrl.sendGetRequestAsync( url, - postNetworkRequestOptions, + undefined, timeoutInMilliseconds ) ).rejects.toEqual(error); @@ -426,14 +401,14 @@ describe("HttpClient", () => { test("Client Error 400", async () => { const serverErrorNetworkResponse: NetworkResponse = getNetworkResponse( - getMockServerErrorResponse(httpsStatusCode400), - httpsStatusCode400 + getMockServerErrorResponse(HttpStatus.CLIENT_ERROR), + HttpStatus.CLIENT_ERROR ); (https.request as jest.Mock).mockImplementationOnce( mockHttpsRequest( mockServer400ErrorResponseBodyBuffer, - httpsStatusCode400, - httpsStatusMessage400 + HttpStatus.CLIENT_ERROR, + httpsStatusClientErrorMessage ) ); await expect( @@ -444,14 +419,14 @@ describe("HttpClient", () => { test("Server Error 500", async () => { const serverErrorNetworkResponse: NetworkResponse = getNetworkResponse( - getMockServerErrorResponse(httpsStatusCode500), - httpsStatusCode500 + getMockServerErrorResponse(HttpStatus.SERVER_ERROR), + HttpStatus.SERVER_ERROR ); (https.request as jest.Mock).mockImplementationOnce( mockHttpsRequest( mockServer500ErrorResponseBodyBuffer, - httpsStatusCode500, - httpsStatusMessage500 + HttpStatus.SERVER_ERROR, + httpsStatusServerErrorMessage ) ); await expect( @@ -462,14 +437,16 @@ describe("HttpClient", () => { test("Unknown Error 600", async () => { const serverErrorNetworkResponse: NetworkResponse = getNetworkResponse( - getMockServerErrorResponse(httpsStatusCode600), - httpsStatusCode600 + getMockServerErrorResponse( + HttpStatus.MULTI_SIDED_ERROR + ), + HttpStatus.MULTI_SIDED_ERROR ); (https.request as jest.Mock).mockImplementationOnce( mockHttpsRequest( mockServer600ErrorResponseBodyBuffer, - httpsStatusCode600, - httpsStatusMessage600 + HttpStatus.MULTI_SIDED_ERROR, + httpsStatusMultiSidedErrorMessage ) ); await expect( @@ -483,8 +460,8 @@ describe("HttpClient", () => { (http.request as jest.Mock).mockImplementationOnce( mockHttpRequest( mockGetResponseBody, - proxyStatusCode500, - httpsStatusCode500 + ProxyStatus.SERVER_ERROR, + HttpStatus.SERVER_ERROR ) ); await expect( @@ -496,14 +473,16 @@ describe("HttpClient", () => { test("Client Error 400", async () => { const serverErrorNetworkResponse: NetworkResponse = getNetworkResponse( - getMockServerErrorResponse(socketStatusCode400), - socketStatusCode400 + getMockServerErrorResponse( + SocketStatus.CLIENT_ERROR + ), + SocketStatus.CLIENT_ERROR ); (http.request as jest.Mock).mockImplementationOnce( mockHttpRequest( mockServer400ErrorResponseBody, - proxyStatusCode200, - socketStatusCode400 + ProxyStatus.SUCCESS, + SocketStatus.CLIENT_ERROR ) ); await expect( @@ -514,14 +493,16 @@ describe("HttpClient", () => { test("Server Error 500", async () => { const serverErrorNetworkResponse: NetworkResponse = getNetworkResponse( - getMockServerErrorResponse(socketStatusCode500), - socketStatusCode500 + getMockServerErrorResponse( + SocketStatus.SERVER_ERROR + ), + SocketStatus.SERVER_ERROR ); (http.request as jest.Mock).mockImplementationOnce( mockHttpRequest( mockServer500ErrorResponseBody, - proxyStatusCode200, - socketStatusCode500 + ProxyStatus.SUCCESS, + SocketStatus.SERVER_ERROR ) ); await expect( @@ -532,14 +513,16 @@ describe("HttpClient", () => { test("Unknown Error 600", async () => { const serverErrorNetworkResponse: NetworkResponse = getNetworkResponse( - getMockServerErrorResponse(socketStatusCode600), - socketStatusCode600 + getMockServerErrorResponse( + SocketStatus.MULTI_SIDED_ERROR + ), + SocketStatus.MULTI_SIDED_ERROR ); (http.request as jest.Mock).mockImplementationOnce( mockHttpRequest( mockServer600ErrorResponseBody, - proxyStatusCode200, - socketStatusCode600 + ProxyStatus.SUCCESS, + SocketStatus.MULTI_SIDED_ERROR ) ); await expect( @@ -555,14 +538,14 @@ describe("HttpClient", () => { test("Client Error 400", async () => { const serverErrorNetworkResponse: NetworkResponse = getNetworkResponse( - getMockServerErrorResponse(httpsStatusCode400), - httpsStatusCode400 + getMockServerErrorResponse(HttpStatus.CLIENT_ERROR), + HttpStatus.CLIENT_ERROR ); (https.request as jest.Mock).mockImplementationOnce( mockHttpsRequest( mockServer400ErrorResponseBodyBuffer, - httpsStatusCode400, - httpsStatusMessage400 + HttpStatus.CLIENT_ERROR, + httpsStatusClientErrorMessage ) ); await expect( @@ -576,14 +559,14 @@ describe("HttpClient", () => { test("Server Error 500", async () => { const serverErrorNetworkResponse: NetworkResponse = getNetworkResponse( - getMockServerErrorResponse(httpsStatusCode500), - httpsStatusCode500 + getMockServerErrorResponse(HttpStatus.SERVER_ERROR), + HttpStatus.SERVER_ERROR ); (https.request as jest.Mock).mockImplementationOnce( mockHttpsRequest( mockServer500ErrorResponseBodyBuffer, - httpsStatusCode500, - httpsStatusMessage500 + HttpStatus.SERVER_ERROR, + httpsStatusServerErrorMessage ) ); await expect( @@ -597,14 +580,16 @@ describe("HttpClient", () => { test("Unknown Error 600", async () => { const serverErrorNetworkResponse: NetworkResponse = getNetworkResponse( - getMockServerErrorResponse(httpsStatusCode600), - httpsStatusCode600 + getMockServerErrorResponse( + HttpStatus.MULTI_SIDED_ERROR + ), + HttpStatus.MULTI_SIDED_ERROR ); (https.request as jest.Mock).mockImplementationOnce( mockHttpsRequest( mockServer600ErrorResponseBodyBuffer, - httpsStatusCode600, - httpsStatusMessage600 + HttpStatus.MULTI_SIDED_ERROR, + httpsStatusMultiSidedErrorMessage ) ); await expect( @@ -621,8 +606,8 @@ describe("HttpClient", () => { (http.request as jest.Mock).mockImplementationOnce( mockHttpRequest( mockPostResponseBody, - proxyStatusCode500, - httpsStatusCode500 + ProxyStatus.SERVER_ERROR, + HttpStatus.SERVER_ERROR ) ); await expect( @@ -637,14 +622,16 @@ describe("HttpClient", () => { test("Client Error 400", async () => { const serverErrorNetworkResponse: NetworkResponse = getNetworkResponse( - getMockServerErrorResponse(socketStatusCode400), - socketStatusCode400 + getMockServerErrorResponse( + SocketStatus.CLIENT_ERROR + ), + SocketStatus.CLIENT_ERROR ); (http.request as jest.Mock).mockImplementationOnce( mockHttpRequest( mockServer400ErrorResponseBody, - proxyStatusCode200, - socketStatusCode400 + ProxyStatus.SUCCESS, + SocketStatus.CLIENT_ERROR ) ); await expect( @@ -658,14 +645,16 @@ describe("HttpClient", () => { test("Server Error 500", async () => { const serverErrorNetworkResponse: NetworkResponse = getNetworkResponse( - getMockServerErrorResponse(socketStatusCode500), - socketStatusCode500 + getMockServerErrorResponse( + SocketStatus.SERVER_ERROR + ), + SocketStatus.SERVER_ERROR ); (http.request as jest.Mock).mockImplementationOnce( mockHttpRequest( mockServer500ErrorResponseBody, - proxyStatusCode200, - socketStatusCode500 + ProxyStatus.SUCCESS, + SocketStatus.SERVER_ERROR ) ); await expect( @@ -679,14 +668,16 @@ describe("HttpClient", () => { test("Unknown Error 600", async () => { const serverErrorNetworkResponse: NetworkResponse = getNetworkResponse( - getMockServerErrorResponse(socketStatusCode600), - socketStatusCode600 + getMockServerErrorResponse( + SocketStatus.MULTI_SIDED_ERROR + ), + SocketStatus.MULTI_SIDED_ERROR ); (http.request as jest.Mock).mockImplementationOnce( mockHttpRequest( mockServer600ErrorResponseBody, - proxyStatusCode200, - socketStatusCode600 + ProxyStatus.SUCCESS, + SocketStatus.MULTI_SIDED_ERROR ) ); await expect( diff --git a/lib/msal-node/test/test_kit/ManagedIdentityTestUtils.ts b/lib/msal-node/test/test_kit/ManagedIdentityTestUtils.ts index da72c8db63..dceea72efb 100644 --- a/lib/msal-node/test/test_kit/ManagedIdentityTestUtils.ts +++ b/lib/msal-node/test/test_kit/ManagedIdentityTestUtils.ts @@ -9,6 +9,7 @@ import { INetworkModule, NetworkRequestOptions, NetworkResponse, + TimeUtils, } from "@azure/msal-common"; import { MANAGED_IDENTITY_RESOURCE, @@ -92,7 +93,7 @@ export class ManagedIdentityNetworkClient implements INetworkModule { sendGetRequestAsync( _url: string, _options?: NetworkRequestOptions, - _cancellationToken?: number + _timeout?: number ): Promise> { return new Promise>((resolve, _reject) => { resolve({ @@ -100,7 +101,9 @@ export class ManagedIdentityNetworkClient implements INetworkModule { body: { access_token: TEST_TOKENS.ACCESS_TOKEN, client_id: this.clientId, - expires_on: TEST_TOKEN_LIFETIMES.DEFAULT_EXPIRES_IN * 3, // 3 hours + expires_on: + TimeUtils.nowSeconds() + + TEST_TOKEN_LIFETIMES.DEFAULT_EXPIRES_IN * 3, // 3 hours in the future resource: MANAGED_IDENTITY_RESOURCE.replace( "/.default", "" @@ -122,7 +125,9 @@ export class ManagedIdentityNetworkClient implements INetworkModule { body: { access_token: TEST_TOKENS.ACCESS_TOKEN, client_id: this.clientId, - expires_on: TEST_TOKEN_LIFETIMES.DEFAULT_EXPIRES_IN * 3, // 3 hours + expires_on: + TimeUtils.nowSeconds() + + TEST_TOKEN_LIFETIMES.DEFAULT_EXPIRES_IN * 3, // 3 hours in the future resource: ( this.resource || MANAGED_IDENTITY_RESOURCE ).replace("/.default", ""), @@ -169,7 +174,7 @@ export class ManagedIdentityNetworkErrorClient implements INetworkModule { sendGetRequestAsync( _url: string, _options?: NetworkRequestOptions, - _cancellationToken?: number + _timeout?: number ): Promise> { return new Promise>((resolve, _reject) => { resolve( @@ -188,7 +193,7 @@ export class ManagedIdentityNetworkErrorClient implements INetworkModule { ): Promise> { return new Promise>((resolve, _reject) => { resolve({ - status: httpStatusCode || HttpStatus.INTERNAL_SERVER_ERROR, + status: httpStatusCode || HttpStatus.SERVER_ERROR, body: { message: MANAGED_IDENTITY_TOKEN_RETRIEVAL_ERROR, correlationId: mockAuthenticationResult.correlationId, diff --git a/lib/msal-node/test/test_kit/StringConstants.ts b/lib/msal-node/test/test_kit/StringConstants.ts index e63153e68e..965909e140 100644 --- a/lib/msal-node/test/test_kit/StringConstants.ts +++ b/lib/msal-node/test/test_kit/StringConstants.ts @@ -3,7 +3,10 @@ * Licensed under the MIT License. */ -import { AuthenticationResult } from "@azure/msal-common"; +import { + AuthenticationResult, + PasswordGrantConstants, +} from "@azure/msal-common"; import { AuthenticationScheme, Constants, @@ -514,3 +517,6 @@ export const CORS_SIMPLE_REQUEST_HEADERS = [ ]; export const THREE_SECONDS_IN_MILLI = 3000; + +export const MOCK_USERNAME = `mock_${PasswordGrantConstants.username}`; +export const MOCK_PASSWORD = `mock_${PasswordGrantConstants.password}`; diff --git a/lib/msal-node/test/utils/MockNetworkClient.ts b/lib/msal-node/test/utils/MockNetworkClient.ts index b523078165..9540ead950 100644 --- a/lib/msal-node/test/utils/MockNetworkClient.ts +++ b/lib/msal-node/test/utils/MockNetworkClient.ts @@ -17,7 +17,7 @@ export const mockNetworkClient = ( sendGetRequestAsync( _url: string, _options?: NetworkRequestOptions, - _cancellationToken?: number + _timeout?: number ): Promise> { return new Promise>((resolve, _reject) => { resolve(getRequestResult as NetworkResponse); diff --git a/lib/msal-react/CHANGELOG.json b/lib/msal-react/CHANGELOG.json index 4a2d0a8dbf..2216563f73 100644 --- a/lib/msal-react/CHANGELOG.json +++ b/lib/msal-react/CHANGELOG.json @@ -1,6 +1,33 @@ { "name": "@azure/msal-react", "entries": [ + { + "date": "Mon, 06 May 2024 23:48:17 GMT", + "version": "2.0.16", + "tag": "@azure/msal-react_v2.0.16", + "comments": { + "patch": [ + { + "author": "kade@hatchedlabs.com", + "package": "@azure/msal-react", + "commit": "e5fa16efec03ddf9ee66ebac83b8a958e5b4a384", + "comment": "Fix useIsAuthenticated returning incorrect value during useEffect update #7057" + }, + { + "author": "beachball", + "package": "@azure/msal-react", + "comment": "Bump @azure/msal-browser to v3.14.0", + "commit": "not available" + }, + { + "author": "beachball", + "package": "@azure/msal-react", + "comment": "Bump eslint-config-msal to v0.0.0", + "commit": "not available" + } + ] + } + }, { "date": "Thu, 11 Apr 2024 21:46:57 GMT", "version": "2.0.15", diff --git a/lib/msal-react/CHANGELOG.md b/lib/msal-react/CHANGELOG.md index 85bf6beac2..42e3dd36d4 100644 --- a/lib/msal-react/CHANGELOG.md +++ b/lib/msal-react/CHANGELOG.md @@ -1,9 +1,19 @@ # Change Log - @azure/msal-react -This log was last generated on Thu, 11 Apr 2024 21:46:57 GMT and should not be manually modified. +This log was last generated on Mon, 06 May 2024 23:48:17 GMT and should not be manually modified. +## 2.0.16 + +Mon, 06 May 2024 23:48:17 GMT + +### Patches + +- Fix useIsAuthenticated returning incorrect value during useEffect update #7057 (kade@hatchedlabs.com) +- Bump @azure/msal-browser to v3.14.0 +- Bump eslint-config-msal to v0.0.0 + ## 2.0.15 Thu, 11 Apr 2024 21:46:57 GMT diff --git a/lib/msal-react/package.json b/lib/msal-react/package.json index 99d8bcddc9..99d59bfa61 100644 --- a/lib/msal-react/package.json +++ b/lib/msal-react/package.json @@ -1,6 +1,6 @@ { "name": "@azure/msal-react", - "version": "2.0.15", + "version": "2.0.16", "author": { "name": "Microsoft", "email": "nugetaad@microsoft.com", @@ -47,11 +47,11 @@ "format:fix": "prettier --ignore-path .gitignore --write src test" }, "peerDependencies": { - "@azure/msal-browser": "^3.13.0", + "@azure/msal-browser": "^3.14.0", "react": "^16.8.0 || ^17 || ^18" }, "devDependencies": { - "@azure/msal-browser": "^3.13.0", + "@azure/msal-browser": "^3.14.0", "@rollup/plugin-typescript": "^11.1.5", "@testing-library/jest-dom": "^5.11.5", "@testing-library/react": "^13.4.0", diff --git a/lib/msal-react/src/packageMetadata.ts b/lib/msal-react/src/packageMetadata.ts index 486279b202..2f93eba892 100644 --- a/lib/msal-react/src/packageMetadata.ts +++ b/lib/msal-react/src/packageMetadata.ts @@ -1,3 +1,3 @@ /* eslint-disable header/header */ export const name = "@azure/msal-react"; -export const version = "2.0.15"; +export const version = "2.0.16"; diff --git a/package-lock.json b/package-lock.json index ef323b821e..d7d479580c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,11 +47,11 @@ }, "extensions/msal-node-extensions": { "name": "@azure/msal-node-extensions", - "version": "1.0.15", + "version": "1.0.16", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@azure/msal-common": "14.9.0", + "@azure/msal-common": "14.10.0", "@azure/msal-node-runtime": "^0.13.6-alpha.0", "keytar": "^7.8.0" }, @@ -257,7 +257,7 @@ }, "lib/msal-angular": { "name": "@azure/msal-angular", - "version": "3.0.16", + "version": "3.0.17", "license": "MIT", "devDependencies": { "@angular-devkit/build-angular": "^15.1.5", @@ -271,7 +271,7 @@ "@angular/platform-browser": "^15.1.4", "@angular/platform-browser-dynamic": "^15.1.4", "@angular/router": "^15.1.4", - "@azure/msal-browser": "^3.13.0", + "@azure/msal-browser": "^3.14.0", "@types/jasmine": "~3.6.0", "@types/jasminewd2": "~2.0.3", "@types/node": "^12.11.1", @@ -292,7 +292,7 @@ "zone.js": "~0.11.8" }, "peerDependencies": { - "@azure/msal-browser": "^3.13.0", + "@azure/msal-browser": "^3.14.0", "rxjs": "^7.0.0" } }, @@ -339,10 +339,10 @@ }, "lib/msal-browser": { "name": "@azure/msal-browser", - "version": "3.13.0", + "version": "3.14.0", "license": "MIT", "dependencies": { - "@azure/msal-common": "14.9.0" + "@azure/msal-common": "14.10.0" }, "devDependencies": { "@azure/storage-blob": "^12.2.1", @@ -385,7 +385,7 @@ }, "lib/msal-common": { "name": "@azure/msal-common", - "version": "14.9.0", + "version": "14.10.0", "license": "MIT", "devDependencies": { "@babel/core": "^7.7.2", @@ -469,10 +469,10 @@ }, "lib/msal-node": { "name": "@azure/msal-node", - "version": "2.7.0", + "version": "2.8.0", "license": "MIT", "dependencies": { - "@azure/msal-common": "14.9.0", + "@azure/msal-common": "14.10.0", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" }, @@ -507,10 +507,10 @@ }, "lib/msal-react": { "name": "@azure/msal-react", - "version": "2.0.15", + "version": "2.0.16", "license": "MIT", "devDependencies": { - "@azure/msal-browser": "^3.13.0", + "@azure/msal-browser": "^3.14.0", "@rollup/plugin-typescript": "^11.1.5", "@testing-library/jest-dom": "^5.11.5", "@testing-library/react": "^13.4.0", @@ -533,7 +533,7 @@ "node": ">=10" }, "peerDependencies": { - "@azure/msal-browser": "^3.13.0", + "@azure/msal-browser": "^3.14.0", "react": "^16.8.0 || ^17 || ^18" } }, @@ -22723,9 +22723,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/ejs": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", - "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "dependencies": { "jake": "^10.8.5" },