@microsoft/teamsfx 0.4.1-alpha.fcc60ca0.0 → 0.4.2-alpha.e84c0d19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import jwt_decode from 'jwt-decode';
2
2
  import * as microsoftTeams from '@microsoft/teams-js';
3
- import axios from 'axios';
3
+ import { PublicClientApplication } from '@azure/msal-browser';
4
4
  import { Client } from '@microsoft/microsoft-graph-client';
5
5
 
6
6
  // Copyright (c) Microsoft Corporation.
@@ -309,6 +309,57 @@ function getUserInfoFromSsoToken(ssoToken) {
309
309
  }
310
310
  return userInfo;
311
311
  }
312
+ /**
313
+ * @internal
314
+ */
315
+ function getTenantIdAndLoginHintFromSsoToken(ssoToken) {
316
+ if (!ssoToken) {
317
+ const errorMsg = "SSO token is undefined.";
318
+ internalLogger.error(errorMsg);
319
+ throw new ErrorWithCode(errorMsg, ErrorCode.InvalidParameter);
320
+ }
321
+ const tokenObject = parseJwt(ssoToken);
322
+ const userInfo = {
323
+ tid: tokenObject.tid,
324
+ loginHint: tokenObject.ver === "2.0"
325
+ ? tokenObject.preferred_username
326
+ : tokenObject.upn,
327
+ };
328
+ return userInfo;
329
+ }
330
+ /**
331
+ * @internal
332
+ */
333
+ function parseAccessTokenFromAuthCodeTokenResponse(tokenResponse) {
334
+ try {
335
+ const tokenResponseObject = typeof tokenResponse == "string"
336
+ ? JSON.parse(tokenResponse)
337
+ : tokenResponse;
338
+ if (!tokenResponseObject || !tokenResponseObject.accessToken) {
339
+ const errorMsg = "Get empty access token from Auth Code token response.";
340
+ internalLogger.error(errorMsg);
341
+ throw new Error(errorMsg);
342
+ }
343
+ const token = tokenResponseObject.accessToken;
344
+ const tokenObject = parseJwt(token);
345
+ if (tokenObject.ver !== "1.0" && tokenObject.ver !== "2.0") {
346
+ const errorMsg = "SSO token is not valid with an unknown version: " + tokenObject.ver;
347
+ internalLogger.error(errorMsg);
348
+ throw new Error(errorMsg);
349
+ }
350
+ const accessToken = {
351
+ token: token,
352
+ expiresOnTimestamp: tokenObject.exp * 1000,
353
+ };
354
+ return accessToken;
355
+ }
356
+ catch (error) {
357
+ const errorMsg = "Parse access token failed from Auth Code token response in node env with error: " +
358
+ error.message;
359
+ internalLogger.error(errorMsg);
360
+ throw new ErrorWithCode(errorMsg, ErrorCode.InternalError);
361
+ }
362
+ }
312
363
  /**
313
364
  * Format string template with replacements
314
365
  *
@@ -548,43 +599,10 @@ class OnBehalfOfUserCredential {
548
599
  }
549
600
 
550
601
  // Copyright (c) Microsoft Corporation.
551
- // Licensed under the MIT license.
552
- /**
553
- * Configuration used in initialization.
554
- * @internal
555
- */
556
- class Cache {
557
- static get(key) {
558
- return sessionStorage.getItem(key);
559
- }
560
- static set(key, value) {
561
- sessionStorage.setItem(key, value);
562
- }
563
- static remove(key) {
564
- sessionStorage.removeItem(key);
565
- }
566
- }
567
-
568
- // Copyright (c) Microsoft Corporation.
569
- // Licensed under the MIT license.
570
- /**
571
- * @internal
572
- */
573
- var GrantType;
574
- (function (GrantType) {
575
- GrantType["authCode"] = "authorization_code";
576
- GrantType["ssoToken"] = "sso_token";
577
- })(GrantType || (GrantType = {}));
578
-
579
- // Copyright (c) Microsoft Corporation.
580
- const accessTokenCacheKeyPrefix = "accessToken";
581
- const separator = "-";
582
602
  const tokenRefreshTimeSpanInMillisecond = 5 * 60 * 1000;
583
603
  const initializeTeamsSdkTimeoutInMillisecond = 5000;
584
604
  const loginPageWidth = 600;
585
605
  const loginPageHeight = 535;
586
- const maxRetryCount = 3;
587
- const retryTimeSpanInMillisecond = 3000;
588
606
  /**
589
607
  * Represent Teams current user's identity, and it is used within Teams tab application.
590
608
  *
@@ -602,7 +620,6 @@ class TeamsUserCredential {
602
620
  * ```typescript
603
621
  * const config = {
604
622
  * authentication: {
605
- * runtimeConnectorEndpoint: "https://xxx.xxx.com",
606
623
  * initiateLoginEndpoint: "https://localhost:3000/auth-start.html",
607
624
  * clientId: "xxx"
608
625
  * }
@@ -620,6 +637,7 @@ class TeamsUserCredential {
620
637
  internalLogger.info("Create teams user credential");
621
638
  this.config = this.loadAndValidateConfig();
622
639
  this.ssoToken = null;
640
+ this.initialized = false;
623
641
  }
624
642
  /**
625
643
  * Popup login page to get user's access token with specific scopes.
@@ -637,7 +655,6 @@ class TeamsUserCredential {
637
655
  * @param scopes - The list of scopes for which the token will have access, before that, we will request user to consent.
638
656
  *
639
657
  * @throws {@link ErrorCode|InternalError} when failed to login with unknown error.
640
- * @throws {@link ErrorCode|ServiceError} when simple auth server failed to exchange access token.
641
658
  * @throws {@link ErrorCode|ConsentFailed} when user canceled or failed to consent.
642
659
  * @throws {@link ErrorCode|InvalidParameter} when scopes is not a valid string or string array.
643
660
  * @throws {@link ErrorCode|RuntimeNotSupported} when runtime is nodeJS.
@@ -648,26 +665,28 @@ class TeamsUserCredential {
648
665
  validateScopesType(scopes);
649
666
  const scopesStr = typeof scopes === "string" ? scopes : scopes.join(" ");
650
667
  internalLogger.info(`Popup login page to get user's access token with scopes: ${scopesStr}`);
668
+ if (!this.initialized) {
669
+ await this.init();
670
+ }
651
671
  return new Promise((resolve, reject) => {
652
672
  microsoftTeams.initialize(() => {
653
673
  microsoftTeams.authentication.authenticate({
654
- url: `${this.config.initiateLoginEndpoint}?clientId=${this.config.clientId}&scope=${encodeURI(scopesStr)}`,
674
+ url: `${this.config.initiateLoginEndpoint}?clientId=${this.config.clientId}&scope=${encodeURI(scopesStr)}&loginHint=${this.loginHint}`,
655
675
  width: loginPageWidth,
656
676
  height: loginPageHeight,
657
677
  successCallback: async (result) => {
658
678
  if (!result) {
659
- const errorMsg = "Get empty authentication result from Teams";
679
+ const errorMsg = "Get empty authentication result from MSAL";
660
680
  internalLogger.error(errorMsg);
661
681
  reject(new ErrorWithCode(errorMsg, ErrorCode.InternalError));
662
682
  return;
663
683
  }
664
- const authCodeResult = JSON.parse(result);
665
684
  try {
666
- await this.exchangeAccessTokenFromSimpleAuthServer(scopesStr, authCodeResult);
667
- resolve();
685
+ const accessToken = parseAccessTokenFromAuthCodeTokenResponse(result);
686
+ resolve(accessToken);
668
687
  }
669
- catch (err) {
670
- reject(this.generateAuthServerError(err));
688
+ catch (error) {
689
+ reject(error);
671
690
  }
672
691
  },
673
692
  failureCallback: (reason) => {
@@ -703,7 +722,6 @@ class TeamsUserCredential {
703
722
  *
704
723
  * @throws {@link ErrorCode|InternalError} when failed to get access token with unknown error.
705
724
  * @throws {@link ErrorCode|UiRequiredError} when need user consent to get access token.
706
- * @throws {@link ErrorCode|ServiceError} when failed to get access token from simple auth server.
707
725
  * @throws {@link ErrorCode|InvalidParameter} when scopes is not a valid string or string array.
708
726
  * @throws {@link ErrorCode|RuntimeNotSupported} when runtime is nodeJS.
709
727
  *
@@ -724,21 +742,47 @@ class TeamsUserCredential {
724
742
  }
725
743
  else {
726
744
  internalLogger.info("Get access token with scopes: " + scopeStr);
727
- const cachedKey = await this.getAccessTokenCacheKey(scopeStr);
728
- const cachedToken = this.getTokenCache(cachedKey);
729
- if (cachedToken) {
730
- if (!this.isAccessTokenNearExpired(cachedToken)) {
731
- internalLogger.verbose("Get access token from cache");
732
- return cachedToken;
745
+ if (!this.initialized) {
746
+ await this.init();
747
+ }
748
+ let tokenResponse;
749
+ const scopesArray = typeof scopes === "string" ? scopes.split(" ") : scopes;
750
+ const domain = window.location.origin;
751
+ // First try to get Access Token from cache.
752
+ try {
753
+ const account = this.msalInstance.getAccountByUsername(this.loginHint);
754
+ const scopesRequestForAcquireTokenSilent = {
755
+ scopes: scopesArray,
756
+ account: account !== null && account !== void 0 ? account : undefined,
757
+ redirectUri: `${domain}/blank-auth-end.html`,
758
+ };
759
+ tokenResponse = await this.msalInstance.acquireTokenSilent(scopesRequestForAcquireTokenSilent);
760
+ }
761
+ catch (error) {
762
+ const acquireTokenSilentFailedMessage = `Failed to call acquireTokenSilent. Reason: ${error === null || error === void 0 ? void 0 : error.message}. `;
763
+ internalLogger.verbose(acquireTokenSilentFailedMessage);
764
+ }
765
+ if (!tokenResponse) {
766
+ // If fail to get Access Token from cache, try to get Access token by silent login.
767
+ try {
768
+ const scopesRequestForSsoSilent = {
769
+ scopes: scopesArray,
770
+ loginHint: this.loginHint,
771
+ redirectUri: `${domain}/blank-auth-end.html`,
772
+ };
773
+ tokenResponse = await this.msalInstance.ssoSilent(scopesRequestForSsoSilent);
733
774
  }
734
- else {
735
- internalLogger.verbose("Cached access token is expired");
775
+ catch (error) {
776
+ const ssoSilentFailedMessage = `Failed to call ssoSilent. Reason: ${error === null || error === void 0 ? void 0 : error.message}. `;
777
+ internalLogger.verbose(ssoSilentFailedMessage);
736
778
  }
737
779
  }
738
- else {
739
- internalLogger.verbose("No cached access token");
780
+ if (!tokenResponse) {
781
+ const errorMsg = `Failed to get access token cache silently, please login first: you need login first before get access token.`;
782
+ internalLogger.error(errorMsg);
783
+ throw new ErrorWithCode(errorMsg, ErrorCode.UiRequiredError);
740
784
  }
741
- const accessToken = await this.getAndCacheAccessTokenFromSimpleAuthServer(scopeStr);
785
+ const accessToken = parseAccessTokenFromAuthCodeTokenResponse(tokenResponse);
742
786
  return accessToken;
743
787
  }
744
788
  }
@@ -763,65 +807,22 @@ class TeamsUserCredential {
763
807
  const ssoToken = await this.getSSOToken();
764
808
  return getUserInfoFromSsoToken(ssoToken.token);
765
809
  }
766
- async exchangeAccessTokenFromSimpleAuthServer(scopesStr, authCodeResult) {
767
- var _a, _b;
768
- const axiosInstance = await this.getAxiosInstance();
769
- let retryCount = 0;
770
- while (true) {
771
- try {
772
- const response = await axiosInstance.post("/auth/token", {
773
- scope: scopesStr,
774
- code: authCodeResult.code,
775
- code_verifier: authCodeResult.codeVerifier,
776
- redirect_uri: authCodeResult.redirectUri,
777
- grant_type: GrantType.authCode,
778
- });
779
- const tokenResult = response.data;
780
- const key = await this.getAccessTokenCacheKey(scopesStr);
781
- // Important: tokens are stored in sessionStorage, read more here: https://aka.ms/teamsfx-session-storage-notice
782
- this.setTokenCache(key, {
783
- token: tokenResult.access_token,
784
- expiresOnTimestamp: tokenResult.expires_on,
785
- });
786
- return;
787
- }
788
- catch (err) {
789
- if (((_b = (_a = err.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.type) && err.response.data.type === "AadUiRequiredException") {
790
- internalLogger.warn("Exchange access token failed, retry...");
791
- if (retryCount < maxRetryCount) {
792
- await this.sleep(retryTimeSpanInMillisecond);
793
- retryCount++;
794
- continue;
795
- }
796
- }
797
- throw err;
798
- }
799
- }
800
- }
801
- /**
802
- * Get access token cache from authentication server
803
- * @returns Access token
804
- */
805
- async getAndCacheAccessTokenFromSimpleAuthServer(scopesStr) {
806
- try {
807
- internalLogger.verbose("Get access token from authentication server with scopes: " + scopesStr);
808
- const axiosInstance = await this.getAxiosInstance();
809
- const response = await axiosInstance.post("/auth/token", {
810
- scope: scopesStr,
811
- grant_type: GrantType.ssoToken,
812
- });
813
- const accessTokenResult = response.data;
814
- const accessToken = {
815
- token: accessTokenResult.access_token,
816
- expiresOnTimestamp: accessTokenResult.expires_on,
817
- };
818
- const cacheKey = await this.getAccessTokenCacheKey(scopesStr);
819
- this.setTokenCache(cacheKey, accessToken);
820
- return accessToken;
821
- }
822
- catch (err) {
823
- throw this.generateAuthServerError(err);
824
- }
810
+ async init() {
811
+ const ssoToken = await this.getSSOToken();
812
+ const info = getTenantIdAndLoginHintFromSsoToken(ssoToken.token);
813
+ this.loginHint = info.loginHint;
814
+ this.tid = info.tid;
815
+ const msalConfig = {
816
+ auth: {
817
+ clientId: this.config.clientId,
818
+ authority: `https://login.microsoftonline.com/${this.tid}`,
819
+ },
820
+ cache: {
821
+ cacheLocation: "sessionStorage",
822
+ },
823
+ };
824
+ this.msalInstance = new PublicClientApplication(msalConfig);
825
+ this.initialized = true;
825
826
  }
826
827
  /**
827
828
  * Get SSO token using teams SDK
@@ -891,16 +892,13 @@ class TeamsUserCredential {
891
892
  internalLogger.error(ErrorMessage.AuthenticationConfigurationNotExists);
892
893
  throw new ErrorWithCode(ErrorMessage.AuthenticationConfigurationNotExists, ErrorCode.InvalidConfiguration);
893
894
  }
894
- if (config.initiateLoginEndpoint && config.simpleAuthEndpoint && config.clientId) {
895
+ if (config.initiateLoginEndpoint && config.clientId) {
895
896
  return config;
896
897
  }
897
898
  const missingValues = [];
898
899
  if (!config.initiateLoginEndpoint) {
899
900
  missingValues.push("initiateLoginEndpoint");
900
901
  }
901
- if (!config.simpleAuthEndpoint) {
902
- missingValues.push("simpleAuthEndpoint");
903
- }
904
902
  if (!config.clientId) {
905
903
  missingValues.push("clientId");
906
904
  }
@@ -908,112 +906,6 @@ class TeamsUserCredential {
908
906
  internalLogger.error(errorMsg);
909
907
  throw new ErrorWithCode(errorMsg, ErrorCode.InvalidConfiguration);
910
908
  }
911
- /**
912
- * Get axios instance with sso token bearer header
913
- * @returns AxiosInstance
914
- */
915
- async getAxiosInstance() {
916
- const ssoToken = await this.getSSOToken();
917
- const axiosInstance = axios.create({
918
- baseURL: this.config.simpleAuthEndpoint,
919
- });
920
- axiosInstance.interceptors.request.use((config) => {
921
- config.headers.Authorization = "Bearer " + ssoToken.token;
922
- return config;
923
- });
924
- return axiosInstance;
925
- }
926
- /**
927
- * Set access token to cache
928
- * @param key
929
- * @param token
930
- */
931
- setTokenCache(key, token) {
932
- Cache.set(key, JSON.stringify(token));
933
- }
934
- /**
935
- * Get access token from cache.
936
- * If there is no cache or cannot be parsed, then it will return null
937
- * @param key
938
- * @returns Access token or null
939
- */
940
- getTokenCache(key) {
941
- const value = Cache.get(key);
942
- if (value === null) {
943
- return null;
944
- }
945
- const accessToken = this.validateAndParseJson(value);
946
- return accessToken;
947
- }
948
- /**
949
- * Parses passed value as JSON access token, if value is not a valid json string JSON.parse() will throw an error.
950
- * @param jsonValue
951
- */
952
- validateAndParseJson(jsonValue) {
953
- try {
954
- const parsedJson = JSON.parse(jsonValue);
955
- /**
956
- * There are edge cases in which JSON.parse will successfully parse a non-valid JSON object
957
- * (e.g. JSON.parse will parse an escaped string into an unescaped string), so adding a type check
958
- * of the parsed value is necessary in order to be certain that the string represents a valid JSON object.
959
- *
960
- */
961
- return parsedJson && typeof parsedJson === "object" ? parsedJson : null;
962
- }
963
- catch (error) {
964
- return null;
965
- }
966
- }
967
- /**
968
- * Generate cache key
969
- * @param scopesStr
970
- * @returns Access token cache key, a key example: accessToken-userId-clientId-tenantId-scopes
971
- */
972
- async getAccessTokenCacheKey(scopesStr) {
973
- const ssoToken = await this.getSSOToken();
974
- const ssoTokenObj = parseJwt(ssoToken.token);
975
- const clientId = this.config.clientId;
976
- const userObjectId = ssoTokenObj.oid;
977
- const tenantId = ssoTokenObj.tid;
978
- const key = [accessTokenCacheKeyPrefix, userObjectId, clientId, tenantId, scopesStr]
979
- .join(separator)
980
- .replace(/" "/g, "_");
981
- return key;
982
- }
983
- /**
984
- * Check whether the token is about to expire (within 5 minutes)
985
- * @returns Boolean value indicate whether the token is about to expire
986
- */
987
- isAccessTokenNearExpired(token) {
988
- const expireDate = new Date(token.expiresOnTimestamp);
989
- if (expireDate.getTime() - Date.now() > tokenRefreshTimeSpanInMillisecond) {
990
- return false;
991
- }
992
- return true;
993
- }
994
- generateAuthServerError(err) {
995
- var _a, _b;
996
- let errorMessage = err.message;
997
- if ((_b = (_a = err.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.type) {
998
- errorMessage = err.response.data.detail;
999
- if (err.response.data.type === "AadUiRequiredException") {
1000
- const fullErrorMsg = "Failed to get access token from authentication server, please login first: " +
1001
- errorMessage;
1002
- internalLogger.warn(fullErrorMsg);
1003
- return new ErrorWithCode(fullErrorMsg, ErrorCode.UiRequiredError);
1004
- }
1005
- else {
1006
- const fullErrorMsg = "Failed to get access token from authentication server: " + errorMessage;
1007
- internalLogger.error(fullErrorMsg);
1008
- return new ErrorWithCode(fullErrorMsg, ErrorCode.ServiceError);
1009
- }
1010
- }
1011
- const fullErrorMsg = "Failed to get access token with error: " + errorMessage;
1012
- return new ErrorWithCode(fullErrorMsg, ErrorCode.InternalError);
1013
- }
1014
- sleep(ms) {
1015
- return new Promise((resolve) => setTimeout(resolve, ms));
1016
- }
1017
909
  }
1018
910
 
1019
911
  // Copyright (c) Microsoft Corporation.