Skip to content

Commit fca35fc

Browse files
authored
Try fallback valid domains list before CDN list (#2837)
* Updated fetch logic * comment * use fallback list before waiting on CDN fetch * increase package size limit * update tests * Update comment on reset function
1 parent 2b40230 commit fca35fc

File tree

4 files changed

+105
-29
lines changed

4 files changed

+105
-29
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "Updated valid domains CDN fetch logic to prevent duplicate requests.",
4+
"packageName": "@microsoft/teams-js",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@
162162
"brotli": false,
163163
"path": "./packages/teams-js/dist/esm/packages/teams-js/src/index.js",
164164
"import": "{ app, authentication, pages }",
165-
"limit": "57.40 KB"
165+
"limit": "57.60 KB"
166166
},
167167
{
168168
"brotli": false,

packages/teams-js/src/internal/validOrigins.ts

Lines changed: 57 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ import { inServerSideRenderingEnvironment, isValidHttpsURL } from './utils';
55

66
let validOriginsCache: string[] = [];
77
const validateOriginLogger = getLogger('validateOrigin');
8+
let validOriginsPromise: Promise<string[]> | undefined;
89

910
export async function prefetchOriginsFromCDN(): Promise<void> {
10-
await getValidOriginsListFromCDN();
11+
if (!validOriginsPromise) {
12+
await getValidOriginsListFromCDN();
13+
}
1114
}
1215

1316
function isValidOriginsCacheEmpty(): boolean {
@@ -18,13 +21,17 @@ async function getValidOriginsListFromCDN(shouldDisableCache: boolean = false):
1821
if (!isValidOriginsCacheEmpty() && !shouldDisableCache) {
1922
return validOriginsCache;
2023
}
24+
if (validOriginsPromise) {
25+
// Fetch has already been initiated, return the existing promise
26+
return validOriginsPromise;
27+
}
2128
if (!inServerSideRenderingEnvironment()) {
2229
validateOriginLogger('Initiating fetch call to acquire valid origins list from CDN');
2330

2431
const controller = new AbortController();
2532
const timeoutId = setTimeout(() => controller.abort(), ORIGIN_LIST_FETCH_TIMEOUT_IN_MS);
2633

27-
return fetch(validOriginsCdnEndpoint, { signal: controller.signal })
34+
validOriginsPromise = fetch(validOriginsCdnEndpoint, { signal: controller.signal })
2835
.then((response) => {
2936
clearTimeout(timeoutId);
3037
if (!response.ok) {
@@ -51,6 +58,7 @@ async function getValidOriginsListFromCDN(shouldDisableCache: boolean = false):
5158
validOriginsCache = validOriginsFallback;
5259
return validOriginsCache;
5360
});
61+
return validOriginsPromise;
5462
} else {
5563
validOriginsCache = validOriginsFallback;
5664
return validOriginsFallback;
@@ -111,36 +119,58 @@ function validateHostAgainstPattern(pattern: string, host: string): boolean {
111119
* Limited to Microsoft-internal use
112120
*/
113121
export function validateOrigin(messageOrigin: URL, disableCache?: boolean): Promise<boolean> {
114-
return getValidOriginsListFromCDN(disableCache).then((validOriginsList) => {
115-
// Check whether the url is in the pre-known allowlist or supplied by user
116-
if (!isValidHttpsURL(messageOrigin)) {
117-
validateOriginLogger(
118-
'Origin %s is invalid because it is not using https protocol. Protocol being used: %s',
119-
messageOrigin,
120-
messageOrigin.protocol,
121-
);
122-
return false;
123-
}
124-
const messageOriginHost = messageOrigin.host;
125-
if (validOriginsList.some((pattern) => validateHostAgainstPattern(pattern, messageOriginHost))) {
126-
return true;
127-
}
128-
129-
for (const domainOrPattern of GlobalVars.additionalValidOrigins) {
130-
const pattern = domainOrPattern.substring(0, 8) === 'https://' ? domainOrPattern.substring(8) : domainOrPattern;
131-
if (validateHostAgainstPattern(pattern, messageOriginHost)) {
132-
return true;
133-
}
134-
}
122+
// Try origin against the cache or hardcoded fallback list first before fetching from CDN
123+
const localList = !disableCache && !isValidOriginsCacheEmpty() ? validOriginsCache : validOriginsFallback;
124+
if (validateOriginWithValidOriginsList(messageOrigin, localList)) {
125+
return Promise.resolve(true);
126+
} else {
127+
validateOriginLogger('Origin %s is not in the local valid origins list, fetching from CDN', messageOrigin);
128+
return getValidOriginsListFromCDN(disableCache).then((validOriginsList) => {
129+
return validateOriginWithValidOriginsList(messageOrigin, validOriginsList);
130+
});
131+
}
132+
}
135133

134+
function validateOriginWithValidOriginsList(messageOrigin: URL, validOriginsList: string[]): boolean {
135+
// Check whether the url is in the pre-known allowlist or supplied by user
136+
if (!isValidHttpsURL(messageOrigin)) {
136137
validateOriginLogger(
137-
'Origin %s is invalid because it is not an origin approved by this library or included in the call to app.initialize.\nOrigins approved by this library: %o\nOrigins included in app.initialize: %o',
138+
'Origin %s is invalid because it is not using https protocol. Protocol being used: %s',
138139
messageOrigin,
139-
validOriginsList,
140-
GlobalVars.additionalValidOrigins,
140+
messageOrigin.protocol,
141141
);
142142
return false;
143-
});
143+
}
144+
const messageOriginHost = messageOrigin.host;
145+
if (validOriginsList.some((pattern) => validateHostAgainstPattern(pattern, messageOriginHost))) {
146+
return true;
147+
}
148+
149+
for (const domainOrPattern of GlobalVars.additionalValidOrigins) {
150+
const pattern = domainOrPattern.substring(0, 8) === 'https://' ? domainOrPattern.substring(8) : domainOrPattern;
151+
if (validateHostAgainstPattern(pattern, messageOriginHost)) {
152+
return true;
153+
}
154+
}
155+
156+
validateOriginLogger(
157+
'Origin %s is invalid because it is not an origin approved by this library or included in the call to app.initialize.\nOrigins approved by this library: %o\nOrigins included in app.initialize: %o',
158+
messageOrigin,
159+
validOriginsList,
160+
GlobalVars.additionalValidOrigins,
161+
);
162+
return false;
163+
}
164+
165+
/**
166+
* @internal
167+
* Limited to Microsoft-internal use
168+
*
169+
* This function is only used for testing to reset the valid origins cache and ignore prefetched values.
170+
*/
171+
export function resetValidOriginsCache(): void {
172+
validOriginsCache = [];
173+
validOriginsPromise = undefined;
144174
}
145175

146176
prefetchOriginsFromCDN();

0 commit comments

Comments
 (0)