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, * staticryptSaltUniqueVariableName: 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, staticryptSaltUniqueVariableName } = staticryptConfig; const { replaceHtmlCallback } = templateConfig; const result = await decode( staticryptEncryptedMsgUniqueVariableName, hashedPassword, staticryptSaltUniqueVariableName ); 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 { staticryptSaltUniqueVariableName } = staticryptConfig; // decrypt and replace the whole page const hashedPassword = await cryptoEngine.hashPassword(password, staticryptSaltUniqueVariableName); return handleDecryptionOfPageFromHash(hashedPassword, isRememberChecked); } exports.handleDecryptionOfPage = handleDecryptionOfPage; async function handleDecryptionOfPageFromHash(hashedPassword, isRememberChecked) { const { isRememberEnabled, rememberDurationInDays } = staticryptConfig; const { rememberExpirationKey, rememberPassphraseKey } = templateConfig; 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.handleDecryptionOfPageFromHash = handleDecryptionOfPageFromHash; /** * 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; } async function decryptOnLoadFromUrl() { const passwordKey = "staticrypt_pwd"; const rememberMeKey = "remember_me"; // try to get the password from the query param (for backward compatibility - we now want to avoid this method, // since it sends the hashed password to the server which isn't needed) const queryParams = new URLSearchParams(window.location.search); const hashedPasswordQuery = queryParams.get(passwordKey); const rememberMeQuery = queryParams.get(rememberMeKey); const urlFragment = window.location.hash.substring(1); // get the password from the url fragment const hashedPasswordRegexMatch = urlFragment.match(new RegExp(passwordKey + "=([^&]*)")); const hashedPasswordFragment = hashedPasswordRegexMatch ? hashedPasswordRegexMatch[1] : null; const rememberMeFragment = urlFragment.includes(rememberMeKey); const hashedPassword = hashedPasswordFragment || hashedPasswordQuery; const rememberMe = rememberMeFragment || rememberMeQuery; if (hashedPassword) { return handleDecryptionOfPageFromHash(hashedPassword, rememberMe); } return false; } return exports; } exports.init = init;