UNPKG

@kwiz/common

Version:

KWIZ common utilities and helpers for M365 platform

392 lines 19.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.GetTokenAudiencePrefix = GetTokenAudiencePrefix; exports.GetDefaultScope = GetDefaultScope; exports.GetMSALSiteScope = GetMSALSiteScope; exports.GetGraphEndpointUrl = GetGraphEndpointUrl; exports.GetMSALAdminConsentUrl = GetMSALAdminConsentUrl; exports.GetSPFxClientAuthToken = GetSPFxClientAuthToken; exports.GetSPFxClientAuthTokenSync = GetSPFxClientAuthTokenSync; const collections_base_1 = require("../../helpers/collections.base"); const promises_1 = require("../../helpers/promises"); const random_1 = require("../../helpers/random"); const sharepoint_1 = require("../../helpers/sharepoint"); const typecheckers_1 = require("../../helpers/typecheckers"); const url_1 = require("../../helpers/url"); const auth_1 = require("../../types/auth"); const consolelogger_1 = require("../consolelogger"); const localstoragecache_1 = require("../localstoragecache"); const rest_1 = require("../rest"); const common_1 = require("../sharepoint.rest/common"); const discovery_1 = require("./discovery"); const logger = consolelogger_1.ConsoleLogger.get("utils/auth/common"); function GetTokenAudiencePrefix(appId) { return `api://${appId}`; } function GetDefaultScope(appId) { return `${GetTokenAudiencePrefix(appId)}/access_as_user`; } function GetMSALSiteScope(hostName) { return `https://${hostName}`; } function _getGraphUrlFromHost(loginHostOrsharePointHost) { loginHostOrsharePointHost = loginHostOrsharePointHost.toLowerCase(); if (loginHostOrsharePointHost.endsWith('/')) { return loginHostOrsharePointHost.slice(0, -1); } if (loginHostOrsharePointHost.endsWith(".us")) { return "https://graph.microsoft.us"; } else if (loginHostOrsharePointHost.endsWith(".sharepoint.com") || loginHostOrsharePointHost.endsWith("login.microsoftonline.com")) { return "https://graph.microsoft.com"; } else if (loginHostOrsharePointHost.endsWith(".de")) { return "https://graph.microsoft.de"; } else if (loginHostOrsharePointHost.endsWith(".cn")) { return "https://microsoftgraph.chinacloudapi.cn"; } return null; } function GetGraphEndpointUrl() { let url = ""; if ("location" in globalThis && !(0, typecheckers_1.isNullOrEmptyString)(globalThis.location.host)) { url = _getGraphUrlFromHost(globalThis.location.host); } if ((0, typecheckers_1.isNullOrEmptyString)(url) && (0, common_1.hasGlobalContext)() === true) { if (!(0, typecheckers_1.isNullOrEmptyString)(_spPageContextInfo["msGraphEndpointUrl"])) { url = _spPageContextInfo["msGraphEndpointUrl"]; } if ((0, typecheckers_1.isNullOrEmptyString)(url) && !(0, typecheckers_1.isNullOrEmptyString)(_spPageContextInfo["aadInstanceUrl"])) { url = _getGraphUrlFromHost(_spPageContextInfo["aadInstanceUrl"]); } if ((0, typecheckers_1.isNullOrEmptyString)(url) && !(0, typecheckers_1.isNullOrEmptyString)(_spPageContextInfo["aadTenantId"])) { let tenantInfo = (0, discovery_1.DiscoverTenantInfo)(_spPageContextInfo["aadTenantId"], true); if (!(0, typecheckers_1.isNullOrUndefined)(tenantInfo) && !(0, typecheckers_1.isNullOrEmptyString)(tenantInfo.msGraphHost)) { url = `https://${tenantInfo.msGraphHost}`; } } } if ((0, typecheckers_1.isNullOrEmptyString)(url)) { let tenantInfo = (0, discovery_1.AutoDiscoverTenantInfo)(true); if (!(0, typecheckers_1.isNullOrUndefined)(tenantInfo) && !(0, typecheckers_1.isNullOrEmptyString)(tenantInfo.msGraphHost)) { url = `https://${tenantInfo.msGraphHost}`; } } return !(0, typecheckers_1.isNullOrEmptyString)(url) ? url : "https://graph.microsoft.com"; } function GetMSALAdminConsentUrl(params) { const stateParam = (0, typecheckers_1.isNullOrEmptyString)(params.state) ? '' : `&state=${encodeURIComponent(params.state)}`; const url = `https://login.microsoftonline.com/${params.tenantId || "common"}/adminconsent?client_id=${encodeURIComponent(params.clientId)}&redirect_uri=${encodeURIComponent(params.redirectUri)}${stateParam}`; return url; } function _getGetSPFxClientAuthTokenParams(siteUrl, spfxTokenType = auth_1.SPFxAuthTokenType.Graph) { let acquireURL = `${(0, common_1.GetRestBaseUrl)(siteUrl)}/SP.OAuth.Token/Acquire`; //todo: add all the resource end points (ie. OneNote, Yammer, Stream) let resource = ""; let isSPOToken = false; switch (spfxTokenType) { case auth_1.SPFxAuthTokenType.Outlook: resource = "https://outlook.office365.com/search"; break; case auth_1.SPFxAuthTokenType.SharePoint: case auth_1.SPFxAuthTokenType.MySite: isSPOToken = true; let absUrl = (0, url_1.makeFullUrl)(acquireURL); resource = new URL(absUrl).origin; if (spfxTokenType === auth_1.SPFxAuthTokenType.MySite) { let split = resource.split("."); split[0] += "-my"; resource = split.join("."); } break; default: resource = GetGraphEndpointUrl(); break; } let data = { resource: resource, tokenType: isSPOToken ? "SPO" : undefined }; let params = { url: acquireURL, body: JSON.stringify(data), options: { allowCache: true, localStorageExpiration: { minutes: 1 }, includeDigestInPost: true, headers: { "Accept": "application/json;odata.metadata=minimal", "content-type": "application/json; charset=UTF-8", "odata-version": "4.0", } } }; return params; } function _parseAndCacheGetSPFxClientAuthTokenResult(result, spfxTokenType = auth_1.SPFxAuthTokenType.Graph) { if ((0, common_1.hasGlobalContext)() && !(0, typecheckers_1.isNullOrUndefined)(result) && !(0, typecheckers_1.isNullOrEmptyString)(result.access_token)) { let expiration = new Date(); if ((0, typecheckers_1.isNumeric)(result.expires_on)) { expiration = new Date(parseInt(result.expires_on.toString()) * 1000); } else if ((0, typecheckers_1.isNumber)(result.expires_in)) { expiration.setSeconds(expiration.getSeconds() + result.expires_in); } else { expiration.setSeconds(expiration.getSeconds() + 60 * 30); } (0, localstoragecache_1.setCacheItem)(`spfx_access_token_${spfxTokenType}`, result.access_token, expiration); return result.access_token; } return null; } function _getSPFxClientAuthTokenFromCache(spfxTokenType = auth_1.SPFxAuthTokenType.Graph) { if ((0, common_1.hasGlobalContext)()) { let cachedToken = (0, localstoragecache_1.getCacheItem)(`spfx_access_token_${spfxTokenType}`); if (!(0, typecheckers_1.isNullOrEmptyString)(cachedToken)) { return cachedToken; } } return null; } function _getSPFxClientAuthTokenFromMSALCache(resource, spfxTokenType = auth_1.SPFxAuthTokenType.Graph) { try { let cachedToken; for (let key in localStorage) { if (key.startsWith(`Identity.OAuth.${_spPageContextInfo.systemUserKey}`) && key.indexOf(resource) !== -1) { cachedToken = JSON.parse(localStorage.getItem(key)); break; } } if (!(0, typecheckers_1.isNullOrUndefined)(cachedToken) && !(0, typecheckers_1.isNullOrEmptyString)(cachedToken.value) && (0, typecheckers_1.isNumber)(cachedToken.expiration) && cachedToken.expiration > new Date().getTime()) { return _parseAndCacheGetSPFxClientAuthTokenResult({ access_token: cachedToken.value, // convert milliseconds to seconds expires_on: Math.floor(new Date(cachedToken.expiration).getTime() / 1000), resource: resource, scope: null, token_type: "Bearer" }, spfxTokenType); } } catch { } return null; } /** Acquire an authorization token for a Outlook, Graph, or SharePoint the same way SPFx clients do */ async function GetSPFxClientAuthToken(siteUrl, spfxTokenType = auth_1.SPFxAuthTokenType.Graph) { await (0, sharepoint_1.isSPPageContextInfoReady)(); if ((0, typecheckers_1.isTypeofFullNameNullOrUndefined)("_spPageContextInfo") || _spPageContextInfo.isSPO !== true || _spPageContextInfo.isAppWeb === true || _spPageContextInfo.isAnonymousGuestUser === true || _spPageContextInfo["isEmailAuthenticationGuestUser"] === true) { return null; } let cachedToken = _getSPFxClientAuthTokenFromCache(spfxTokenType); if (!(0, typecheckers_1.isNullOrEmptyString)(cachedToken)) { return cachedToken; } let key = `GetSPFxClientAuthToken_${_spPageContextInfo.userLoginName}_${_spPageContextInfo.siteId}_${spfxTokenType}`; return await (0, promises_1.promiseLock)(key, async () => { if (spfxTokenType === auth_1.SPFxAuthTokenType.Graph) { let graphResource = GetGraphEndpointUrl(); let token = _getSPFxClientAuthTokenFromMSALCache(graphResource, spfxTokenType); if (!(0, typecheckers_1.isNullOrEmptyString)(token)) { return token; } try { let _spComponentLoader = window["_spComponentLoader"]; let manifests = _spComponentLoader.getManifests(); let manifest = (0, collections_base_1.firstOrNull)(manifests, (manifest) => { return manifest.alias === "@microsoft/sp-http-base"; }); let module = await _spComponentLoader.loadComponentById(manifest.id); let factory = new module.AadTokenProviderFactory(); let provider = await factory.getTokenProvider(); let token = await provider.getToken(graphResource, true); if (!(0, typecheckers_1.isNullOrEmptyString)(token)) { return _parseAndCacheGetSPFxClientAuthTokenResult({ access_token: token, expires_on: null, resource: graphResource, scope: null, token_type: "Bearer" }, spfxTokenType); } } catch (ex) { logger.error(ex); } try { let bufferToString = (buffer) => { let result = Array.from(buffer, (c) => { return String.fromCodePoint(c); }).join(""); return window.btoa(result); }; let ni = new Uint32Array(1); let ci = () => { let b = window.crypto.getRandomValues(ni); return b[0]; }; let generateNonce = () => { let ti = "0123456789abcdef"; const e = Date.now(), t = 1024 * ci() + (1023 & ci()), n = new Uint8Array(16), a = Math.trunc(t / 2 ** 30), i = t & 2 ** 30 - 1, r = ci(); n[0] = e / 2 ** 40, n[1] = e / 2 ** 32, n[2] = e / 2 ** 24, n[3] = e / 65536, n[4] = e / 256, n[5] = e, n[6] = 112 | a >>> 8, n[7] = a, n[8] = 128 | i >>> 24, n[9] = i >>> 16, n[10] = i >>> 8, n[11] = i, n[12] = r >>> 24, n[13] = r >>> 16, n[14] = r >>> 8, n[15] = r; let o = ""; for (let e = 0; e < n.length; e++) o += ti.charAt(n[e] >>> 4), o += ti.charAt(15 & n[e]), 3 !== e && 5 !== e && 7 !== e && 9 !== e || (o += "-"); return o; }; let requestId = (0, random_1.getUniqueId)(); let stateBuffer = new TextEncoder().encode(JSON.stringify({ id: (0, random_1.getUniqueId)(), meta: { interactionType: "silent" } })); let state = bufferToString(stateBuffer); let redirectUri = `https://${window.location.host}/_forms/spfxsinglesignon.aspx`; let sid = _spPageContextInfo["aadSessionId"]; let codeVerifierBuffer = window.crypto.getRandomValues(new Uint8Array(32)); let codeVerifier = bufferToString(codeVerifierBuffer).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_"); let codeChallengeBuffer = await window.crypto.subtle.digest("SHA-256", new TextEncoder().encode(codeVerifier)); let codeChallenge = bufferToString(new Uint8Array(codeChallengeBuffer)).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_"); let nonce = generateNonce(); let url = `${_spPageContextInfo["aadInstanceUrl"]}/${_spPageContextInfo.aadTenantId}/oauth2/v2.0/authorize?`; url += `client_id=08e18876-6177-487e-b8b5-cf950c1e598c`; url += `&scope=${encodeURIComponent(`${graphResource}/.default openid profile offline_access`)}`; url += `&redirect_uri=${encodeURIComponent(redirectUri)}`; url += `&client-request-id=${encodeURIComponent(requestId)}`; url += `&response_mode=fragment`; url += `&response_type=code`; url += `&code_challenge=${codeChallenge}&code_challenge_method=S256&prompt=none`; url += `&sid=${encodeURIComponent(sid)}&nonce=${nonce}`; url += `&state=${encodeURIComponent(state)}`; let getCodeFromIframe = async () => { return new Promise((resolve, reject) => { try { let iframe = document.createElement("iframe"); iframe.style.visibility = "hidden"; iframe.style.position = "absolute"; iframe.style.width = iframe.style.height = "0"; iframe.style.border = "0"; iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms"); iframe.src = url; iframe.onload = () => { window.setTimeout(() => { let params = new URLSearchParams(iframe.contentWindow.location.hash.replace("#", "?")); let pCode = params.get("code"); let pState = params.get("state"); let pSid = params.get("session_state"); if (!(0, typecheckers_1.isNullOrEmptyString)(pCode) && pState === state && pSid === sid) { resolve(pCode); } else { reject(); } document.body.removeChild(iframe); }, 100); }; document.body.appendChild(iframe); } catch { reject(); } }); }; let authCode = await getCodeFromIframe(); if (!(0, typecheckers_1.isNullOrEmptyString)(authCode)) { let url = `${_spPageContextInfo["aadInstanceUrl"]}/${_spPageContextInfo.aadTenantId}/oauth2/v2.0/token?`; url += `client-request-id=${encodeURIComponent(requestId)}`; let fd = new FormData(); fd.append("client_id", "08e18876-6177-487e-b8b5-cf950c1e598c"); fd.append("scope", `https://${graphResource}/.default openid profile offline_access`); fd.append("redirect_uri", redirectUri); fd.append("code", authCode); fd.append("grant_type", "authorization_code"); fd.append("code_verifier", codeVerifier); let response = await fetch(url, { method: "POST", body: fd }); if (response.ok) { let authToken = await response.json(); return _parseAndCacheGetSPFxClientAuthTokenResult(authToken, spfxTokenType); } } } catch (ex) { logger.error(ex); } } else { try { let { url, body, options } = _getGetSPFxClientAuthTokenParams(siteUrl, spfxTokenType); let result = await (0, rest_1.GetJson)(url, body, options); return _parseAndCacheGetSPFxClientAuthTokenResult(result, spfxTokenType); } catch { } } return null; }, 6000); } /** Acquire an authorization token for a Outlook, Graph, or SharePoint the same way SPFx clients do */ function GetSPFxClientAuthTokenSync(siteUrl, spfxTokenType = auth_1.SPFxAuthTokenType.Graph) { (0, sharepoint_1.isSPPageContextInfoReadySync)(); if ((0, typecheckers_1.isTypeofFullNameNullOrUndefined)("_spPageContextInfo") || _spPageContextInfo.isSPO !== true || _spPageContextInfo.isAppWeb === true || _spPageContextInfo.isAnonymousGuestUser === true || _spPageContextInfo["isEmailAuthenticationGuestUser"] === true) { return null; } let cachedToken = _getSPFxClientAuthTokenFromCache(spfxTokenType); if (!(0, typecheckers_1.isNullOrEmptyString)(cachedToken)) { return cachedToken; } if (spfxTokenType === auth_1.SPFxAuthTokenType.Graph) { let resource = GetGraphEndpointUrl(); let token = _getSPFxClientAuthTokenFromMSALCache(resource, spfxTokenType); if (!(0, typecheckers_1.isNullOrEmptyString)(token)) { return token; } // Cache it for next time using the async method GetSPFxClientAuthToken(siteUrl, spfxTokenType); } else { try { let { url, body, options } = _getGetSPFxClientAuthTokenParams(siteUrl, spfxTokenType); let response = (0, rest_1.GetJsonSync)(url, body, options); return _parseAndCacheGetSPFxClientAuthTokenResult(response.result, spfxTokenType); } catch { } } return null; } //# sourceMappingURL=common.js.map