const cryptoEngine = /*[|js_crypto_engine|]*/ 0; const codec = /*[|js_codec|]*/ 0; const decode = codec.init(cryptoEngine).decode; /** * Initialize the staticrypt module, that exposes functions callbable by the password_template. * * @param {{ * staticryptEncryptedMsgUniqueVariableName: string, * isRememberEnabled: boolean, * rememberDurationInDays: number, * salt: string, * }} staticryptConfig - object of data that is stored on the password_template at encryption time. * * @param {{ * rememberExpirationKey: string, * rememberPassphraseKey: string, * replaceHtmlCallback: function, * clearLocalStorageCallback: function, * }} templateConfig - object of data that can be configured by a custom password_template. */ function init(staticryptConfig, templateConfig) { const exports = {}; /** * Decrypt our encrypted page, replace the whole HTML. * * @param {string} hashedPassword * @returns {Promise} */ async function decryptAndReplaceHtml(hashedPassword) { const { staticryptEncryptedMsgUniqueVariableName, salt } = staticryptConfig; const { replaceHtmlCallback } = templateConfig; const result = await decode(staticryptEncryptedMsgUniqueVariableName, hashedPassword, salt); if (!result.success) { return false; } const plainHTML = result.decoded; // if the user configured a callback call it, otherwise just replace the whole HTML if (typeof replaceHtmlCallback === "function") { replaceHtmlCallback(plainHTML); } else { document.write(plainHTML); document.close(); } return true; } /** * Attempt to decrypt the page and replace the whole HTML. * * @param {string} password * @param {boolean} isRememberChecked * * @returns {Promise<{isSuccessful: boolean, hashedPassword?: string}>} - we return an object, so that if we want to * expose more information in the future we can do it without breaking the password_template */ async function handleDecryptionOfPage(password, isRememberChecked) { const { isRememberEnabled, rememberDurationInDays, salt } = staticryptConfig; const { rememberExpirationKey, rememberPassphraseKey } = templateConfig; // decrypt and replace the whole page const hashedPassword = await cryptoEngine.hashPassword(password, salt); const isDecryptionSuccessful = await decryptAndReplaceHtml(hashedPassword); if (!isDecryptionSuccessful) { return { isSuccessful: false, hashedPassword, }; } // remember the hashedPassword and set its expiration if necessary if (isRememberEnabled && isRememberChecked) { window.localStorage.setItem(rememberPassphraseKey, hashedPassword); // set the expiration if the duration isn't 0 (meaning no expiration) if (rememberDurationInDays > 0) { window.localStorage.setItem( rememberExpirationKey, (new Date().getTime() + rememberDurationInDays * 24 * 60 * 60 * 1000).toString() ); } } return { isSuccessful: true, hashedPassword, }; } exports.handleDecryptionOfPage = handleDecryptionOfPage; /** * Clear localstorage from staticrypt related values */ function clearLocalStorage() { const { clearLocalStorageCallback, rememberExpirationKey, rememberPassphraseKey } = templateConfig; if (typeof clearLocalStorageCallback === "function") { clearLocalStorageCallback(); } else { localStorage.removeItem(rememberPassphraseKey); localStorage.removeItem(rememberExpirationKey); } } async function handleDecryptOnLoad() { let isSuccessful = await decryptOnLoadFromUrl(); if (!isSuccessful) { isSuccessful = await decryptOnLoadFromRememberMe(); } return { isSuccessful }; } exports.handleDecryptOnLoad = handleDecryptOnLoad; /** * Clear storage if we are logging out * * @returns {boolean} - whether we logged out */ function logoutIfNeeded() { const logoutKey = "staticrypt_logout"; // handle logout through query param const queryParams = new URLSearchParams(window.location.search); if (queryParams.has(logoutKey)) { clearLocalStorage(); return true; } // handle logout through URL fragment const hash = window.location.hash.substring(1); if (hash.includes(logoutKey)) { clearLocalStorage(); return true; } return false; } /** * To be called on load: check if we want to try to decrypt and replace the HTML with the decrypted content, and * try to do it if needed. * * @returns {Promise} true if we derypted and replaced the whole page, false otherwise */ async function decryptOnLoadFromRememberMe() { const { rememberDurationInDays } = staticryptConfig; const { rememberExpirationKey, rememberPassphraseKey } = templateConfig; // if we are login out, terminate if (logoutIfNeeded()) { return false; } // if there is expiration configured, check if we're not beyond the expiration if (rememberDurationInDays && rememberDurationInDays > 0) { const expiration = localStorage.getItem(rememberExpirationKey), isExpired = expiration && new Date().getTime() > parseInt(expiration); if (isExpired) { clearLocalStorage(); return false; } } const hashedPassword = localStorage.getItem(rememberPassphraseKey); if (hashedPassword) { // try to decrypt const isDecryptionSuccessful = await decryptAndReplaceHtml(hashedPassword); // if the decryption is unsuccessful the password might be wrong - silently clear the saved data and let // the user fill the password form again if (!isDecryptionSuccessful) { clearLocalStorage(); return false; } return true; } return false; } function decryptOnLoadFromUrl() { const passwordKey = "staticrypt_pwd"; // get the password from the query param const queryParams = new URLSearchParams(window.location.search); const hashedPasswordQuery = queryParams.get(passwordKey); // get the password from the url fragment const hashRegexMatch = window.location.hash.substring(1).match(new RegExp(passwordKey + "=(.*)")); const hashedPasswordFragment = hashRegexMatch ? hashRegexMatch[1] : null; const hashedPassword = hashedPasswordFragment || hashedPasswordQuery; if (hashedPassword) { return decryptAndReplaceHtml(hashedPassword); } return false; } return exports; } exports.init = init;