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