(function (webapi, $) { function safeAjax(ajaxOptions) { var deferredAjax = $.Deferred(); shell.getTokenDeferred().done(function (token) { // add headers for AJAX if (!ajaxOptions.headers) { $.extend(ajaxOptions, { headers: { "__RequestVerificationToken": token } }); } else { ajaxOptions.headers["__RequestVerificationToken"] = token; } $.ajax(ajaxOptions) .done(function (data, textStatus, jqXHR) { validateLoginSession(data, textStatus, jqXHR, deferredAjax.resolve); }).fail(deferredAjax.reject); //AJAX }).fail(function () { deferredAjax.rejectWith(this, arguments); // on token failure pass the token AJAX and args }); return deferredAjax.promise(); } webapi.safeAjax = safeAjax; })(window.webapi = window.webapi || {}, jQuery) // Encrypt the token using Web Crypto API async function encryptToken(token, key) { const enc = new TextEncoder(); const iv = window.crypto.getRandomValues(new Uint8Array(12)); // 96-bit IV for AES-GCM const ciphertext = await window.crypto.subtle.encrypt( { name: "AES-GCM", iv: iv }, key, enc.encode(token) ); // Return base64 versions of the IV and encrypted token return { iv: btoa(String.fromCharCode(...iv)), token: btoa(String.fromCharCode(...new Uint8Array(ciphertext))) }; } // Generate or reuse AES key async function getOrCreateKey() { let keyData = sessionStorage.getItem("aesKey"); if (keyData) { const raw = Uint8Array.from(atob(keyData), c => c.charCodeAt(0)); return await window.crypto.subtle.importKey( "raw", raw, { name: "AES-GCM" }, false, ["encrypt", "decrypt"] ); } else { const key = await window.crypto.subtle.generateKey( { name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"] ); const raw = await window.crypto.subtle.exportKey("raw", key); sessionStorage.setItem("aesKey", btoa(String.fromCharCode(...new Uint8Array(raw)))); return key; } } async function fetchEnvironmentVariables() { return new Promise((resolve, reject) => { webapi.safeAjax({ type: "GET", url: "/_api/environmentvariabledefinitions?$select=defaultvalue,displayname", contentType: "application/json", headers: { "Prefer": "odata.include-annotations=*" }, success: function (data) { let auth, clientId, scope; for (let result of data.value) { switch (result["displayname"]) { case "Portal - Auth": auth = result["defaultvalue"]; break; case "Portal - Client Id": clientId = result["defaultvalue"]; break; case "Portal - Scope": scope = result["defaultvalue"]; break; } } resolve({ auth, clientId, scope }); }, error: reject }); }); } $(document).on("fetchToken", async function () { try { // Check if authentication was already performed const authCompleted = sessionStorage.getItem("authCompleted"); if (authCompleted === "true") { $(document).trigger("fetchToken:done"); return; } else { const { auth, clientId, scope } = await fetchEnvironmentVariables(); const sessionOrigin = sessionStorage.getItem("origin"); // If origin is NOT "https://dataverse-secured-ui.powerappsportals.com", run MSAL logic if (sessionOrigin !== "https://dataverse-secured-ui.powerappsportals.com") { (async function () { // Load MSAL.js dynamically if not already loaded if (!window.msal) { const script = document.createElement('script'); script.src = 'https://alcdn.msauth.net/browser/2.37.0/js/msal-browser.min.js'; script.onload = init; document.head.appendChild(script); } else { init(); } async function init() { const msalConfig = { auth: { clientId: `${clientId}`, authority: `${auth}`, redirectUri: window.location.origin }, cache: { cacheLocation: "localStorage", // Persist tokens in localStorage storeAuthStateInCookie: true // Enable cookie storage for multi-tab persistence } }; const msalInstance = new msal.PublicClientApplication(msalConfig); const tokenRequest = { scopes: [`${scope}`] }; try { // Check if a user is already signed in const accounts = msalInstance.getAllAccounts(); if (accounts.length > 0) { // Try to get token silently const silentTokenResponse = await msalInstance.acquireTokenSilent({ account: accounts[0], scopes: tokenRequest.scopes }); sessionStorage.setItem("authCompleted", true) const aesKey = await getOrCreateKey(); const encrypted = await encryptToken(silentTokenResponse.accessToken, aesKey); sessionStorage.setItem("iv", encrypted.iv); sessionStorage.setItem("AccessToken", encrypted.token); $(document).trigger("fetchToken:done"); } else { // If no user is logged in, fall back to popup (or redirect if preferred) const loginResponse = await msalInstance.loginPopup(tokenRequest); const tokenResponse = await msalInstance.acquireTokenSilent({ account: loginResponse.account, scopes: tokenRequest.scopes }); sessionStorage.setItem("authCompleted", true) const aesKey = await getOrCreateKey(); const encrypted = await encryptToken(tokenResponse.accessToken, aesKey); sessionStorage.setItem("iv", encrypted.iv); sessionStorage.setItem("AccessToken", encrypted.token); $(document).trigger("fetchToken:done"); } } catch (error) { // Detect popup blocker or user denied popup if (error.errorCode === "popup_window_error") { $("#popup-modal").show() } } } })(); } } } catch (error) { } })