kopia lustrzana https://github.com/robinmoisson/staticrypt
215 wiersze
7.0 KiB
JavaScript
215 wiersze
7.0 KiB
JavaScript
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 {{
|
|
* encryptedMsg: 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} hashedPassphrase
|
|
* @returns {Promise<boolean>}
|
|
*/
|
|
async function decryptAndReplaceHtml(hashedPassphrase) {
|
|
const { encryptedMsg, salt } = staticryptConfig;
|
|
const { replaceHtmlCallback } = templateConfig;
|
|
|
|
const result = await decode(encryptedMsg, hashedPassphrase, 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.hashPassphrase(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<boolean>} 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 hashedPassphrase = localStorage.getItem(rememberPassphraseKey);
|
|
|
|
if (hashedPassphrase) {
|
|
// try to decrypt
|
|
const isDecryptionSuccessful = await decryptAndReplaceHtml(hashedPassphrase);
|
|
|
|
// 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 hashedPassphraseQuery = queryParams.get(passwordKey);
|
|
|
|
// get the password from the url fragment
|
|
const hashRegexMatch = window.location.hash.substring(1).match(new RegExp(passwordKey + "=(.*)"));
|
|
const hashedPassphraseFragment = hashRegexMatch ? hashRegexMatch[1] : null;
|
|
|
|
const hashedPassphrase = hashedPassphraseFragment || hashedPassphraseQuery;
|
|
|
|
if (hashedPassphrase) {
|
|
return decryptAndReplaceHtml(hashedPassphrase);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
return exports;
|
|
}
|
|
exports.init = init; |