@microsoft/teamsfx 0.4.1-rc.0 → 0.4.2-alpha.eb4575da.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.
- package/dist/index.esm2017.js +132 -382
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm2017.mjs +10 -2
- package/dist/index.esm2017.mjs.map +1 -1
- package/dist/index.esm5.js +183 -490
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +10 -2
- package/dist/index.node.cjs.js.map +1 -1
- package/package.json +6 -6
- package/types/teamsfx.d.ts +1 -1
package/dist/index.esm2017.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import jwt_decode from 'jwt-decode';
|
|
2
2
|
import * as microsoftTeams from '@microsoft/teams-js';
|
|
3
|
-
import
|
|
3
|
+
import { PublicClientApplication } from '@azure/msal-browser';
|
|
4
4
|
import { Client } from '@microsoft/microsoft-graph-client';
|
|
5
|
-
import { ManagedIdentityCredential } from '@azure/identity';
|
|
6
5
|
|
|
7
6
|
// Copyright (c) Microsoft Corporation.
|
|
8
7
|
// Licensed under the MIT license.
|
|
@@ -168,7 +167,7 @@ function getLogLevel() {
|
|
|
168
167
|
return internalLogger.level;
|
|
169
168
|
}
|
|
170
169
|
class InternalLogger {
|
|
171
|
-
constructor() {
|
|
170
|
+
constructor(name, logLevel) {
|
|
172
171
|
this.level = undefined;
|
|
173
172
|
this.defaultLogger = {
|
|
174
173
|
verbose: console.debug,
|
|
@@ -176,6 +175,8 @@ class InternalLogger {
|
|
|
176
175
|
warn: console.warn,
|
|
177
176
|
error: console.error,
|
|
178
177
|
};
|
|
178
|
+
this.name = name;
|
|
179
|
+
this.level = logLevel;
|
|
179
180
|
}
|
|
180
181
|
error(message) {
|
|
181
182
|
this.log(LogLevel.Error, (x) => x.error, message);
|
|
@@ -194,7 +195,13 @@ class InternalLogger {
|
|
|
194
195
|
return;
|
|
195
196
|
}
|
|
196
197
|
const timestamp = new Date().toUTCString();
|
|
197
|
-
|
|
198
|
+
let logHeader;
|
|
199
|
+
if (this.name) {
|
|
200
|
+
logHeader = `[${timestamp}] : @microsoft/teamsfx - ${this.name} : ${LogLevel[logLevel]} - `;
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
logHeader = `[${timestamp}] : @microsoft/teamsfx : ${LogLevel[logLevel]} - `;
|
|
204
|
+
}
|
|
198
205
|
const logMessage = `${logHeader}${message}`;
|
|
199
206
|
if (this.level !== undefined && this.level <= logLevel) {
|
|
200
207
|
if (this.customLogger) {
|
|
@@ -302,6 +309,57 @@ function getUserInfoFromSsoToken(ssoToken) {
|
|
|
302
309
|
}
|
|
303
310
|
return userInfo;
|
|
304
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
|
+
}
|
|
305
363
|
/**
|
|
306
364
|
* Format string template with replacements
|
|
307
365
|
*
|
|
@@ -541,43 +599,10 @@ class OnBehalfOfUserCredential {
|
|
|
541
599
|
}
|
|
542
600
|
|
|
543
601
|
// Copyright (c) Microsoft Corporation.
|
|
544
|
-
// Licensed under the MIT license.
|
|
545
|
-
/**
|
|
546
|
-
* Configuration used in initialization.
|
|
547
|
-
* @internal
|
|
548
|
-
*/
|
|
549
|
-
class Cache {
|
|
550
|
-
static get(key) {
|
|
551
|
-
return sessionStorage.getItem(key);
|
|
552
|
-
}
|
|
553
|
-
static set(key, value) {
|
|
554
|
-
sessionStorage.setItem(key, value);
|
|
555
|
-
}
|
|
556
|
-
static remove(key) {
|
|
557
|
-
sessionStorage.removeItem(key);
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
// Copyright (c) Microsoft Corporation.
|
|
562
|
-
// Licensed under the MIT license.
|
|
563
|
-
/**
|
|
564
|
-
* @internal
|
|
565
|
-
*/
|
|
566
|
-
var GrantType;
|
|
567
|
-
(function (GrantType) {
|
|
568
|
-
GrantType["authCode"] = "authorization_code";
|
|
569
|
-
GrantType["ssoToken"] = "sso_token";
|
|
570
|
-
})(GrantType || (GrantType = {}));
|
|
571
|
-
|
|
572
|
-
// Copyright (c) Microsoft Corporation.
|
|
573
|
-
const accessTokenCacheKeyPrefix = "accessToken";
|
|
574
|
-
const separator = "-";
|
|
575
602
|
const tokenRefreshTimeSpanInMillisecond = 5 * 60 * 1000;
|
|
576
603
|
const initializeTeamsSdkTimeoutInMillisecond = 5000;
|
|
577
604
|
const loginPageWidth = 600;
|
|
578
605
|
const loginPageHeight = 535;
|
|
579
|
-
const maxRetryCount = 3;
|
|
580
|
-
const retryTimeSpanInMillisecond = 3000;
|
|
581
606
|
/**
|
|
582
607
|
* Represent Teams current user's identity, and it is used within Teams tab application.
|
|
583
608
|
*
|
|
@@ -595,7 +620,6 @@ class TeamsUserCredential {
|
|
|
595
620
|
* ```typescript
|
|
596
621
|
* const config = {
|
|
597
622
|
* authentication: {
|
|
598
|
-
* runtimeConnectorEndpoint: "https://xxx.xxx.com",
|
|
599
623
|
* initiateLoginEndpoint: "https://localhost:3000/auth-start.html",
|
|
600
624
|
* clientId: "xxx"
|
|
601
625
|
* }
|
|
@@ -613,6 +637,7 @@ class TeamsUserCredential {
|
|
|
613
637
|
internalLogger.info("Create teams user credential");
|
|
614
638
|
this.config = this.loadAndValidateConfig();
|
|
615
639
|
this.ssoToken = null;
|
|
640
|
+
this.initialized = false;
|
|
616
641
|
}
|
|
617
642
|
/**
|
|
618
643
|
* Popup login page to get user's access token with specific scopes.
|
|
@@ -630,7 +655,6 @@ class TeamsUserCredential {
|
|
|
630
655
|
* @param scopes - The list of scopes for which the token will have access, before that, we will request user to consent.
|
|
631
656
|
*
|
|
632
657
|
* @throws {@link ErrorCode|InternalError} when failed to login with unknown error.
|
|
633
|
-
* @throws {@link ErrorCode|ServiceError} when simple auth server failed to exchange access token.
|
|
634
658
|
* @throws {@link ErrorCode|ConsentFailed} when user canceled or failed to consent.
|
|
635
659
|
* @throws {@link ErrorCode|InvalidParameter} when scopes is not a valid string or string array.
|
|
636
660
|
* @throws {@link ErrorCode|RuntimeNotSupported} when runtime is nodeJS.
|
|
@@ -641,26 +665,28 @@ class TeamsUserCredential {
|
|
|
641
665
|
validateScopesType(scopes);
|
|
642
666
|
const scopesStr = typeof scopes === "string" ? scopes : scopes.join(" ");
|
|
643
667
|
internalLogger.info(`Popup login page to get user's access token with scopes: ${scopesStr}`);
|
|
668
|
+
if (!this.initialized) {
|
|
669
|
+
await this.init();
|
|
670
|
+
}
|
|
644
671
|
return new Promise((resolve, reject) => {
|
|
645
672
|
microsoftTeams.initialize(() => {
|
|
646
673
|
microsoftTeams.authentication.authenticate({
|
|
647
|
-
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}`,
|
|
648
675
|
width: loginPageWidth,
|
|
649
676
|
height: loginPageHeight,
|
|
650
677
|
successCallback: async (result) => {
|
|
651
678
|
if (!result) {
|
|
652
|
-
const errorMsg = "Get empty authentication result from
|
|
679
|
+
const errorMsg = "Get empty authentication result from MSAL";
|
|
653
680
|
internalLogger.error(errorMsg);
|
|
654
681
|
reject(new ErrorWithCode(errorMsg, ErrorCode.InternalError));
|
|
655
682
|
return;
|
|
656
683
|
}
|
|
657
|
-
const authCodeResult = JSON.parse(result);
|
|
658
684
|
try {
|
|
659
|
-
|
|
660
|
-
resolve();
|
|
685
|
+
const accessToken = parseAccessTokenFromAuthCodeTokenResponse(result);
|
|
686
|
+
resolve(accessToken);
|
|
661
687
|
}
|
|
662
|
-
catch (
|
|
663
|
-
reject(
|
|
688
|
+
catch (error) {
|
|
689
|
+
reject(error);
|
|
664
690
|
}
|
|
665
691
|
},
|
|
666
692
|
failureCallback: (reason) => {
|
|
@@ -696,7 +722,6 @@ class TeamsUserCredential {
|
|
|
696
722
|
*
|
|
697
723
|
* @throws {@link ErrorCode|InternalError} when failed to get access token with unknown error.
|
|
698
724
|
* @throws {@link ErrorCode|UiRequiredError} when need user consent to get access token.
|
|
699
|
-
* @throws {@link ErrorCode|ServiceError} when failed to get access token from simple auth server.
|
|
700
725
|
* @throws {@link ErrorCode|InvalidParameter} when scopes is not a valid string or string array.
|
|
701
726
|
* @throws {@link ErrorCode|RuntimeNotSupported} when runtime is nodeJS.
|
|
702
727
|
*
|
|
@@ -717,21 +742,47 @@ class TeamsUserCredential {
|
|
|
717
742
|
}
|
|
718
743
|
else {
|
|
719
744
|
internalLogger.info("Get access token with scopes: " + scopeStr);
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
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);
|
|
726
774
|
}
|
|
727
|
-
|
|
728
|
-
|
|
775
|
+
catch (error) {
|
|
776
|
+
const ssoSilentFailedMessage = `Failed to call ssoSilent. Reason: ${error === null || error === void 0 ? void 0 : error.message}. `;
|
|
777
|
+
internalLogger.verbose(ssoSilentFailedMessage);
|
|
729
778
|
}
|
|
730
779
|
}
|
|
731
|
-
|
|
732
|
-
|
|
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);
|
|
733
784
|
}
|
|
734
|
-
const accessToken =
|
|
785
|
+
const accessToken = parseAccessTokenFromAuthCodeTokenResponse(tokenResponse);
|
|
735
786
|
return accessToken;
|
|
736
787
|
}
|
|
737
788
|
}
|
|
@@ -756,65 +807,22 @@ class TeamsUserCredential {
|
|
|
756
807
|
const ssoToken = await this.getSSOToken();
|
|
757
808
|
return getUserInfoFromSsoToken(ssoToken.token);
|
|
758
809
|
}
|
|
759
|
-
async
|
|
760
|
-
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
this.setTokenCache(key, {
|
|
776
|
-
token: tokenResult.access_token,
|
|
777
|
-
expiresOnTimestamp: tokenResult.expires_on,
|
|
778
|
-
});
|
|
779
|
-
return;
|
|
780
|
-
}
|
|
781
|
-
catch (err) {
|
|
782
|
-
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") {
|
|
783
|
-
internalLogger.warn("Exchange access token failed, retry...");
|
|
784
|
-
if (retryCount < maxRetryCount) {
|
|
785
|
-
await this.sleep(retryTimeSpanInMillisecond);
|
|
786
|
-
retryCount++;
|
|
787
|
-
continue;
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
throw err;
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
/**
|
|
795
|
-
* Get access token cache from authentication server
|
|
796
|
-
* @returns Access token
|
|
797
|
-
*/
|
|
798
|
-
async getAndCacheAccessTokenFromSimpleAuthServer(scopesStr) {
|
|
799
|
-
try {
|
|
800
|
-
internalLogger.verbose("Get access token from authentication server with scopes: " + scopesStr);
|
|
801
|
-
const axiosInstance = await this.getAxiosInstance();
|
|
802
|
-
const response = await axiosInstance.post("/auth/token", {
|
|
803
|
-
scope: scopesStr,
|
|
804
|
-
grant_type: GrantType.ssoToken,
|
|
805
|
-
});
|
|
806
|
-
const accessTokenResult = response.data;
|
|
807
|
-
const accessToken = {
|
|
808
|
-
token: accessTokenResult.access_token,
|
|
809
|
-
expiresOnTimestamp: accessTokenResult.expires_on,
|
|
810
|
-
};
|
|
811
|
-
const cacheKey = await this.getAccessTokenCacheKey(scopesStr);
|
|
812
|
-
this.setTokenCache(cacheKey, accessToken);
|
|
813
|
-
return accessToken;
|
|
814
|
-
}
|
|
815
|
-
catch (err) {
|
|
816
|
-
throw this.generateAuthServerError(err);
|
|
817
|
-
}
|
|
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;
|
|
818
826
|
}
|
|
819
827
|
/**
|
|
820
828
|
* Get SSO token using teams SDK
|
|
@@ -884,16 +892,13 @@ class TeamsUserCredential {
|
|
|
884
892
|
internalLogger.error(ErrorMessage.AuthenticationConfigurationNotExists);
|
|
885
893
|
throw new ErrorWithCode(ErrorMessage.AuthenticationConfigurationNotExists, ErrorCode.InvalidConfiguration);
|
|
886
894
|
}
|
|
887
|
-
if (config.initiateLoginEndpoint && config.
|
|
895
|
+
if (config.initiateLoginEndpoint && config.clientId) {
|
|
888
896
|
return config;
|
|
889
897
|
}
|
|
890
898
|
const missingValues = [];
|
|
891
899
|
if (!config.initiateLoginEndpoint) {
|
|
892
900
|
missingValues.push("initiateLoginEndpoint");
|
|
893
901
|
}
|
|
894
|
-
if (!config.simpleAuthEndpoint) {
|
|
895
|
-
missingValues.push("simpleAuthEndpoint");
|
|
896
|
-
}
|
|
897
902
|
if (!config.clientId) {
|
|
898
903
|
missingValues.push("clientId");
|
|
899
904
|
}
|
|
@@ -901,112 +906,6 @@ class TeamsUserCredential {
|
|
|
901
906
|
internalLogger.error(errorMsg);
|
|
902
907
|
throw new ErrorWithCode(errorMsg, ErrorCode.InvalidConfiguration);
|
|
903
908
|
}
|
|
904
|
-
/**
|
|
905
|
-
* Get axios instance with sso token bearer header
|
|
906
|
-
* @returns AxiosInstance
|
|
907
|
-
*/
|
|
908
|
-
async getAxiosInstance() {
|
|
909
|
-
const ssoToken = await this.getSSOToken();
|
|
910
|
-
const axiosInstance = axios.create({
|
|
911
|
-
baseURL: this.config.simpleAuthEndpoint,
|
|
912
|
-
});
|
|
913
|
-
axiosInstance.interceptors.request.use((config) => {
|
|
914
|
-
config.headers.Authorization = "Bearer " + ssoToken.token;
|
|
915
|
-
return config;
|
|
916
|
-
});
|
|
917
|
-
return axiosInstance;
|
|
918
|
-
}
|
|
919
|
-
/**
|
|
920
|
-
* Set access token to cache
|
|
921
|
-
* @param key
|
|
922
|
-
* @param token
|
|
923
|
-
*/
|
|
924
|
-
setTokenCache(key, token) {
|
|
925
|
-
Cache.set(key, JSON.stringify(token));
|
|
926
|
-
}
|
|
927
|
-
/**
|
|
928
|
-
* Get access token from cache.
|
|
929
|
-
* If there is no cache or cannot be parsed, then it will return null
|
|
930
|
-
* @param key
|
|
931
|
-
* @returns Access token or null
|
|
932
|
-
*/
|
|
933
|
-
getTokenCache(key) {
|
|
934
|
-
const value = Cache.get(key);
|
|
935
|
-
if (value === null) {
|
|
936
|
-
return null;
|
|
937
|
-
}
|
|
938
|
-
const accessToken = this.validateAndParseJson(value);
|
|
939
|
-
return accessToken;
|
|
940
|
-
}
|
|
941
|
-
/**
|
|
942
|
-
* Parses passed value as JSON access token, if value is not a valid json string JSON.parse() will throw an error.
|
|
943
|
-
* @param jsonValue
|
|
944
|
-
*/
|
|
945
|
-
validateAndParseJson(jsonValue) {
|
|
946
|
-
try {
|
|
947
|
-
const parsedJson = JSON.parse(jsonValue);
|
|
948
|
-
/**
|
|
949
|
-
* There are edge cases in which JSON.parse will successfully parse a non-valid JSON object
|
|
950
|
-
* (e.g. JSON.parse will parse an escaped string into an unescaped string), so adding a type check
|
|
951
|
-
* of the parsed value is necessary in order to be certain that the string represents a valid JSON object.
|
|
952
|
-
*
|
|
953
|
-
*/
|
|
954
|
-
return parsedJson && typeof parsedJson === "object" ? parsedJson : null;
|
|
955
|
-
}
|
|
956
|
-
catch (error) {
|
|
957
|
-
return null;
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
/**
|
|
961
|
-
* Generate cache key
|
|
962
|
-
* @param scopesStr
|
|
963
|
-
* @returns Access token cache key, a key example: accessToken-userId-clientId-tenantId-scopes
|
|
964
|
-
*/
|
|
965
|
-
async getAccessTokenCacheKey(scopesStr) {
|
|
966
|
-
const ssoToken = await this.getSSOToken();
|
|
967
|
-
const ssoTokenObj = parseJwt(ssoToken.token);
|
|
968
|
-
const clientId = this.config.clientId;
|
|
969
|
-
const userObjectId = ssoTokenObj.oid;
|
|
970
|
-
const tenantId = ssoTokenObj.tid;
|
|
971
|
-
const key = [accessTokenCacheKeyPrefix, userObjectId, clientId, tenantId, scopesStr]
|
|
972
|
-
.join(separator)
|
|
973
|
-
.replace(/" "/g, "_");
|
|
974
|
-
return key;
|
|
975
|
-
}
|
|
976
|
-
/**
|
|
977
|
-
* Check whether the token is about to expire (within 5 minutes)
|
|
978
|
-
* @returns Boolean value indicate whether the token is about to expire
|
|
979
|
-
*/
|
|
980
|
-
isAccessTokenNearExpired(token) {
|
|
981
|
-
const expireDate = new Date(token.expiresOnTimestamp);
|
|
982
|
-
if (expireDate.getTime() - Date.now() > tokenRefreshTimeSpanInMillisecond) {
|
|
983
|
-
return false;
|
|
984
|
-
}
|
|
985
|
-
return true;
|
|
986
|
-
}
|
|
987
|
-
generateAuthServerError(err) {
|
|
988
|
-
var _a, _b;
|
|
989
|
-
let errorMessage = err.message;
|
|
990
|
-
if ((_b = (_a = err.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.type) {
|
|
991
|
-
errorMessage = err.response.data.detail;
|
|
992
|
-
if (err.response.data.type === "AadUiRequiredException") {
|
|
993
|
-
const fullErrorMsg = "Failed to get access token from authentication server, please login first: " +
|
|
994
|
-
errorMessage;
|
|
995
|
-
internalLogger.warn(fullErrorMsg);
|
|
996
|
-
return new ErrorWithCode(fullErrorMsg, ErrorCode.UiRequiredError);
|
|
997
|
-
}
|
|
998
|
-
else {
|
|
999
|
-
const fullErrorMsg = "Failed to get access token from authentication server: " + errorMessage;
|
|
1000
|
-
internalLogger.error(fullErrorMsg);
|
|
1001
|
-
return new ErrorWithCode(fullErrorMsg, ErrorCode.ServiceError);
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
const fullErrorMsg = "Failed to get access token with error: " + errorMessage;
|
|
1005
|
-
return new ErrorWithCode(fullErrorMsg, ErrorCode.InternalError);
|
|
1006
|
-
}
|
|
1007
|
-
sleep(ms) {
|
|
1008
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1009
|
-
}
|
|
1010
909
|
}
|
|
1011
910
|
|
|
1012
911
|
// Copyright (c) Microsoft Corporation.
|
|
@@ -1133,174 +1032,25 @@ function createMicrosoftGraphClient(credential, scopes) {
|
|
|
1133
1032
|
|
|
1134
1033
|
// Copyright (c) Microsoft Corporation.
|
|
1135
1034
|
/**
|
|
1136
|
-
*
|
|
1035
|
+
* Generate connection configuration consumed by tedious.
|
|
1137
1036
|
* @remarks
|
|
1138
1037
|
* Only works in in server side.
|
|
1139
|
-
*
|
|
1140
1038
|
* @beta
|
|
1141
|
-
*
|
|
1142
1039
|
*/
|
|
1143
1040
|
class DefaultTediousConnectionConfiguration {
|
|
1144
1041
|
constructor() {
|
|
1145
|
-
|
|
1146
|
-
* MSSQL default scope
|
|
1147
|
-
* https://docs.microsoft.com/en-us/azure/app-service/app-service-web-tutorial-connect-msi
|
|
1148
|
-
*/
|
|
1149
|
-
this.defaultSQLScope = "https://database.windows.net/";
|
|
1042
|
+
throw new ErrorWithCode(formatString(ErrorMessage.BrowserRuntimeNotSupported, "DefaultTediousConnectionConfiguration"), ErrorCode.RuntimeNotSupported);
|
|
1150
1043
|
}
|
|
1151
1044
|
/**
|
|
1152
1045
|
* Generate connection configuration consumed by tedious.
|
|
1153
|
-
*
|
|
1154
|
-
*
|
|
1155
|
-
*
|
|
1156
|
-
* @throws {@link ErrorCode|InvalidConfiguration} when SQL config resource configuration is invalid.
|
|
1157
|
-
* @throws {@link ErrorCode|InternalError} when get user MSI token failed or MSI token is invalid.
|
|
1158
|
-
* @throws {@link ErrorCode|RuntimeNotSupported} when runtime is browser.
|
|
1159
|
-
*
|
|
1046
|
+
* @remarks
|
|
1047
|
+
* Only works in in server side.
|
|
1160
1048
|
* @beta
|
|
1161
1049
|
*/
|
|
1162
1050
|
async getConfig() {
|
|
1163
|
-
|
|
1164
|
-
const configuration = getResourceConfiguration(ResourceType.SQL);
|
|
1165
|
-
if (!configuration) {
|
|
1166
|
-
const errMsg = "SQL resource configuration not exist";
|
|
1167
|
-
internalLogger.error(errMsg);
|
|
1168
|
-
throw new ErrorWithCode(errMsg, ErrorCode.InvalidConfiguration);
|
|
1169
|
-
}
|
|
1170
|
-
try {
|
|
1171
|
-
this.isSQLConfigurationValid(configuration);
|
|
1172
|
-
}
|
|
1173
|
-
catch (err) {
|
|
1174
|
-
throw err;
|
|
1175
|
-
}
|
|
1176
|
-
if (!this.isMsiAuthentication()) {
|
|
1177
|
-
const configWithUPS = this.generateDefaultConfig(configuration);
|
|
1178
|
-
internalLogger.verbose("SQL configuration with username and password generated");
|
|
1179
|
-
return configWithUPS;
|
|
1180
|
-
}
|
|
1181
|
-
try {
|
|
1182
|
-
const configWithToken = await this.generateTokenConfig(configuration);
|
|
1183
|
-
internalLogger.verbose("SQL configuration with MSI token generated");
|
|
1184
|
-
return configWithToken;
|
|
1185
|
-
}
|
|
1186
|
-
catch (error) {
|
|
1187
|
-
throw error;
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
/**
|
|
1191
|
-
* Check SQL use MSI identity or username and password.
|
|
1192
|
-
*
|
|
1193
|
-
* @returns false - login with SQL MSI identity, true - login with username and password.
|
|
1194
|
-
* @internal
|
|
1195
|
-
*/
|
|
1196
|
-
isMsiAuthentication() {
|
|
1197
|
-
internalLogger.verbose("Check connection config using MSI access token or username and password");
|
|
1198
|
-
const configuration = getResourceConfiguration(ResourceType.SQL);
|
|
1199
|
-
if ((configuration === null || configuration === void 0 ? void 0 : configuration.sqlUsername) != null && (configuration === null || configuration === void 0 ? void 0 : configuration.sqlPassword) != null) {
|
|
1200
|
-
internalLogger.verbose("Login with username and password");
|
|
1201
|
-
return false;
|
|
1202
|
-
}
|
|
1203
|
-
internalLogger.verbose("Login with MSI identity");
|
|
1204
|
-
return true;
|
|
1205
|
-
}
|
|
1206
|
-
/**
|
|
1207
|
-
* check configuration is an available configurations.
|
|
1208
|
-
* @param { SqlConfiguration } sqlConfig
|
|
1209
|
-
*
|
|
1210
|
-
* @returns true - SQL configuration has a valid SQL endpoints, SQL username with password or identity ID.
|
|
1211
|
-
* false - configuration is not valid.
|
|
1212
|
-
* @internal
|
|
1213
|
-
*/
|
|
1214
|
-
isSQLConfigurationValid(sqlConfig) {
|
|
1215
|
-
internalLogger.verbose("Check SQL configuration if valid");
|
|
1216
|
-
if (!sqlConfig.sqlServerEndpoint) {
|
|
1217
|
-
internalLogger.error("SQL configuration is not valid without SQL server endpoint exist");
|
|
1218
|
-
throw new ErrorWithCode("SQL configuration error without SQL server endpoint exist", ErrorCode.InvalidConfiguration);
|
|
1219
|
-
}
|
|
1220
|
-
if (!(sqlConfig.sqlUsername && sqlConfig.sqlPassword) && !sqlConfig.sqlIdentityId) {
|
|
1221
|
-
const errMsg = `SQL configuration is not valid without ${sqlConfig.sqlIdentityId ? "" : "identity id "} ${sqlConfig.sqlUsername ? "" : "SQL username "} ${sqlConfig.sqlPassword ? "" : "SQL password"} exist`;
|
|
1222
|
-
internalLogger.error(errMsg);
|
|
1223
|
-
throw new ErrorWithCode(errMsg, ErrorCode.InvalidConfiguration);
|
|
1224
|
-
}
|
|
1225
|
-
internalLogger.verbose("SQL configuration is valid");
|
|
1226
|
-
}
|
|
1227
|
-
/**
|
|
1228
|
-
* Generate tedious connection configuration with default authentication type.
|
|
1229
|
-
*
|
|
1230
|
-
* @param { SqlConfiguration } SQL configuration with username and password.
|
|
1231
|
-
*
|
|
1232
|
-
* @returns Tedious connection configuration with username and password.
|
|
1233
|
-
* @internal
|
|
1234
|
-
*/
|
|
1235
|
-
generateDefaultConfig(sqlConfig) {
|
|
1236
|
-
internalLogger.verbose(`SQL server ${sqlConfig.sqlServerEndpoint}, user name ${sqlConfig.sqlUsername}, database name ${sqlConfig.sqlDatabaseName}`);
|
|
1237
|
-
const config = {
|
|
1238
|
-
server: sqlConfig.sqlServerEndpoint,
|
|
1239
|
-
authentication: {
|
|
1240
|
-
type: TediousAuthenticationType.default,
|
|
1241
|
-
options: {
|
|
1242
|
-
userName: sqlConfig.sqlUsername,
|
|
1243
|
-
password: sqlConfig.sqlPassword,
|
|
1244
|
-
},
|
|
1245
|
-
},
|
|
1246
|
-
options: {
|
|
1247
|
-
database: sqlConfig.sqlDatabaseName,
|
|
1248
|
-
encrypt: true,
|
|
1249
|
-
},
|
|
1250
|
-
};
|
|
1251
|
-
return config;
|
|
1252
|
-
}
|
|
1253
|
-
/**
|
|
1254
|
-
* Generate tedious connection configuration with azure-active-directory-access-token authentication type.
|
|
1255
|
-
*
|
|
1256
|
-
* @param { SqlConfiguration } SQL configuration with AAD access token.
|
|
1257
|
-
*
|
|
1258
|
-
* @returns Tedious connection configuration with access token.
|
|
1259
|
-
* @internal
|
|
1260
|
-
*/
|
|
1261
|
-
async generateTokenConfig(sqlConfig) {
|
|
1262
|
-
internalLogger.verbose("Generate tedious config with MSI token");
|
|
1263
|
-
let token;
|
|
1264
|
-
try {
|
|
1265
|
-
const credential = new ManagedIdentityCredential(sqlConfig.sqlIdentityId);
|
|
1266
|
-
token = await credential.getToken(this.defaultSQLScope);
|
|
1267
|
-
}
|
|
1268
|
-
catch (error) {
|
|
1269
|
-
const errMsg = "Get user MSI token failed";
|
|
1270
|
-
internalLogger.error(errMsg);
|
|
1271
|
-
throw new ErrorWithCode(errMsg, ErrorCode.InternalError);
|
|
1272
|
-
}
|
|
1273
|
-
if (token) {
|
|
1274
|
-
const config = {
|
|
1275
|
-
server: sqlConfig.sqlServerEndpoint,
|
|
1276
|
-
authentication: {
|
|
1277
|
-
type: TediousAuthenticationType.MSI,
|
|
1278
|
-
options: {
|
|
1279
|
-
token: token.token,
|
|
1280
|
-
},
|
|
1281
|
-
},
|
|
1282
|
-
options: {
|
|
1283
|
-
database: sqlConfig.sqlDatabaseName,
|
|
1284
|
-
encrypt: true,
|
|
1285
|
-
},
|
|
1286
|
-
};
|
|
1287
|
-
internalLogger.verbose(`Generate token configuration success, server endpoint is ${sqlConfig.sqlServerEndpoint}, database name is ${sqlConfig.sqlDatabaseName}`);
|
|
1288
|
-
return config;
|
|
1289
|
-
}
|
|
1290
|
-
internalLogger.error(`Generate token configuration, server endpoint is ${sqlConfig.sqlServerEndpoint}, MSI token is not valid`);
|
|
1291
|
-
throw new ErrorWithCode("MSI token is not valid", ErrorCode.InternalError);
|
|
1051
|
+
throw new ErrorWithCode(formatString(ErrorMessage.BrowserRuntimeNotSupported, "DefaultTediousConnectionConfiguration"), ErrorCode.RuntimeNotSupported);
|
|
1292
1052
|
}
|
|
1293
|
-
}
|
|
1294
|
-
/**
|
|
1295
|
-
* tedious connection config authentication type.
|
|
1296
|
-
* https://tediousjs.github.io/tedious/api-connection.html
|
|
1297
|
-
* @internal
|
|
1298
|
-
*/
|
|
1299
|
-
var TediousAuthenticationType;
|
|
1300
|
-
(function (TediousAuthenticationType) {
|
|
1301
|
-
TediousAuthenticationType["default"] = "default";
|
|
1302
|
-
TediousAuthenticationType["MSI"] = "azure-active-directory-access-token";
|
|
1303
|
-
})(TediousAuthenticationType || (TediousAuthenticationType = {}));
|
|
1053
|
+
}
|
|
1304
1054
|
|
|
1305
1055
|
// Copyright (c) Microsoft Corporation.
|
|
1306
1056
|
/**
|