Files
004_comission/nssheung/School-Management-System/defects/app/public/index.html
louiscklaw 653422de08 update,
2025-01-31 21:09:49 +08:00

819 lines
36 KiB
HTML

<!DOCTYPE html>
<html class="staticrypt-html">
<head>
<meta charset="utf-8" />
<title>Protected Page</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- do not cache this page -->
<meta http-equiv="cache-control" content="max-age=0" />
<meta http-equiv="cache-control" content="no-cache" />
<meta http-equiv="expires" content="0" />
<meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
<meta http-equiv="pragma" content="no-cache" />
<style>
.staticrypt-hr {
margin-top: 20px;
margin-bottom: 20px;
border: 0;
border-top: 1px solid #eee;
}
.staticrypt-page {
width: 360px;
padding: 8% 0 0;
margin: auto;
box-sizing: border-box;
}
.staticrypt-form {
position: relative;
z-index: 1;
background: #ffffff;
max-width: 360px;
margin: 0 auto 100px;
padding: 45px;
text-align: center;
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24);
}
.staticrypt-form input[type="password"] {
outline: 0;
background: #f2f2f2;
width: 100%;
border: 0;
margin: 0 0 15px;
padding: 15px;
box-sizing: border-box;
font-size: 14px;
}
.staticrypt-form .staticrypt-decrypt-button {
text-transform: uppercase;
outline: 0;
background: #4CAF50;
width: 100%;
border: 0;
padding: 15px;
color: #ffffff;
font-size: 14px;
cursor: pointer;
}
.staticrypt-form .staticrypt-decrypt-button:hover,
.staticrypt-form .staticrypt-decrypt-button:active,
.staticrypt-form .staticrypt-decrypt-button:focus {
background: #4CAF50;
filter: brightness(92%);
}
.staticrypt-html {
height: 100%;
}
.staticrypt-body {
height: 100%;
margin: 0;
}
.staticrypt-content {
height: 100%;
margin-bottom: 1em;
background: #76B852;
font-family: "Arial", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.staticrypt-instructions {
margin-top: -1em;
margin-bottom: 1em;
}
.staticrypt-title {
font-size: 1.5em;
}
label.staticrypt-remember {
display: flex;
align-items: center;
margin-bottom: 1em;
}
.staticrypt-remember input[type="checkbox"] {
transform: scale(1.5);
margin-right: 1em;
}
.hidden {
display: none !important;
}
.staticrypt-spinner-container {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.staticrypt-spinner {
display: inline-block;
width: 2rem;
height: 2rem;
vertical-align: text-bottom;
border: 0.25em solid gray;
border-right-color: transparent;
border-radius: 50%;
-webkit-animation: spinner-border 0.75s linear infinite;
animation: spinner-border 0.75s linear infinite;
animation-duration: 0.75s;
animation-timing-function: linear;
animation-delay: 0s;
animation-iteration-count: infinite;
animation-direction: normal;
animation-fill-mode: none;
animation-play-state: running;
animation-name: spinner-border;
}
@keyframes spinner-border {
100% {
transform: rotate(360deg);
}
}
</style>
</head>
<body class="staticrypt-body">
<div id="staticrypt_loading" class="staticrypt-spinner-container">
<div class="staticrypt-spinner"></div>
</div>
<div id="staticrypt_content" class="staticrypt-content hidden">
<div class="staticrypt-page">
<div class="staticrypt-form">
<div class="staticrypt-instructions">
<p class="staticrypt-title">Protected Page</p>
<p></p>
</div>
<hr class="staticrypt-hr" />
<form id="staticrypt-form" action="#" method="post">
<input
id="staticrypt-password"
type="password"
name="password"
placeholder="Password"
autofocus
/>
<label id="staticrypt-remember-label" class="staticrypt-remember hidden">
<input id="staticrypt-remember" type="checkbox" name="remember" />
Remember me
</label>
<input type="submit" class="staticrypt-decrypt-button" value="DECRYPT" />
</form>
</div>
</div>
</div>
<script>
// these variables will be filled when generating the file - the template format is 'variable_name'
const staticryptInitiator = ((function(){
const exports = {};
const cryptoEngine = ((function(){
const exports = {};
const { subtle } = crypto;
const IV_BITS = 16 * 8;
const HEX_BITS = 4;
const ENCRYPTION_ALGO = "AES-CBC";
/**
* Translates between utf8 encoded hexadecimal strings
* and Uint8Array bytes.
*/
const HexEncoder = {
/**
* hex string -> bytes
* @param {string} hexString
* @returns {Uint8Array}
*/
parse: function (hexString) {
if (hexString.length % 2 !== 0) throw "Invalid hexString";
const arrayBuffer = new Uint8Array(hexString.length / 2);
for (let i = 0; i < hexString.length; i += 2) {
const byteValue = parseInt(hexString.substring(i, i + 2), 16);
if (isNaN(byteValue)) {
throw "Invalid hexString";
}
arrayBuffer[i / 2] = byteValue;
}
return arrayBuffer;
},
/**
* bytes -> hex string
* @param {Uint8Array} bytes
* @returns {string}
*/
stringify: function (bytes) {
const hexBytes = [];
for (let i = 0; i < bytes.length; ++i) {
let byteString = bytes[i].toString(16);
if (byteString.length < 2) {
byteString = "0" + byteString;
}
hexBytes.push(byteString);
}
return hexBytes.join("");
},
};
/**
* Translates between utf8 strings and Uint8Array bytes.
*/
const UTF8Encoder = {
parse: function (str) {
return new TextEncoder().encode(str);
},
stringify: function (bytes) {
return new TextDecoder().decode(bytes);
},
};
/**
* Salt and encrypt a msg with a password.
*/
async function encrypt(msg, hashedPassword) {
// Must be 16 bytes, unpredictable, and preferably cryptographically random. However, it need not be secret.
// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/encrypt#parameters
const iv = crypto.getRandomValues(new Uint8Array(IV_BITS / 8));
const key = await subtle.importKey("raw", HexEncoder.parse(hashedPassword), ENCRYPTION_ALGO, false, ["encrypt"]);
const encrypted = await subtle.encrypt(
{
name: ENCRYPTION_ALGO,
iv: iv,
},
key,
UTF8Encoder.parse(msg)
);
// iv will be 32 hex characters, we prepend it to the ciphertext for use in decryption
return HexEncoder.stringify(iv) + HexEncoder.stringify(new Uint8Array(encrypted));
}
exports.encrypt = encrypt;
/**
* Decrypt a salted msg using a password.
*
* @param {string} encryptedMsg
* @param {string} hashedPassword
* @returns {Promise<string>}
*/
async function decrypt(encryptedMsg, hashedPassword) {
const ivLength = IV_BITS / HEX_BITS;
const iv = HexEncoder.parse(encryptedMsg.substring(0, ivLength));
const encrypted = encryptedMsg.substring(ivLength);
const key = await subtle.importKey("raw", HexEncoder.parse(hashedPassword), ENCRYPTION_ALGO, false, ["decrypt"]);
const outBuffer = await subtle.decrypt(
{
name: ENCRYPTION_ALGO,
iv: iv,
},
key,
HexEncoder.parse(encrypted)
);
return UTF8Encoder.stringify(new Uint8Array(outBuffer));
}
exports.decrypt = decrypt;
/**
* Salt and hash the password so it can be stored in localStorage without opening a password reuse vulnerability.
*
* @param {string} password
* @param {string} salt
* @returns {Promise<string>}
*/
async function hashPassword(password, salt) {
// we hash the password in multiple steps, each adding more iterations. This is because we used to allow less
// iterations, so for backward compatibility reasons, we need to support going from that to more iterations.
let hashedPassword = await hashLegacyRound(password, salt);
hashedPassword = await hashSecondRound(hashedPassword, salt);
return hashThirdRound(hashedPassword, salt);
}
exports.hashPassword = hashPassword;
/**
* This hashes the password with 1k iterations. This is a low number, we need this function to support backwards
* compatibility.
*
* @param {string} password
* @param {string} salt
* @returns {Promise<string>}
*/
function hashLegacyRound(password, salt) {
return pbkdf2(password, salt, 1000, "SHA-1");
}
exports.hashLegacyRound = hashLegacyRound;
/**
* Add a second round of iterations. This is because we used to use 1k, so for backwards compatibility with
* remember-me/autodecrypt links, we need to support going from that to more iterations.
*
* @param hashedPassword
* @param salt
* @returns {Promise<string>}
*/
function hashSecondRound(hashedPassword, salt) {
return pbkdf2(hashedPassword, salt, 14000, "SHA-256");
}
exports.hashSecondRound = hashSecondRound;
/**
* Add a third round of iterations to bring total number to 600k. This is because we used to use 1k, then 15k, so for
* backwards compatibility with remember-me/autodecrypt links, we need to support going from that to more iterations.
*
* @param hashedPassword
* @param salt
* @returns {Promise<string>}
*/
function hashThirdRound(hashedPassword, salt) {
return pbkdf2(hashedPassword, salt, 585000, "SHA-256");
}
exports.hashThirdRound = hashThirdRound;
/**
* Salt and hash the password so it can be stored in localStorage without opening a password reuse vulnerability.
*
* @param {string} password
* @param {string} salt
* @param {int} iterations
* @param {string} hashAlgorithm
* @returns {Promise<string>}
*/
async function pbkdf2(password, salt, iterations, hashAlgorithm) {
const key = await subtle.importKey("raw", UTF8Encoder.parse(password), "PBKDF2", false, ["deriveBits"]);
const keyBytes = await subtle.deriveBits(
{
name: "PBKDF2",
hash: hashAlgorithm,
iterations,
salt: UTF8Encoder.parse(salt),
},
key,
256
);
return HexEncoder.stringify(new Uint8Array(keyBytes));
}
function generateRandomSalt() {
const bytes = crypto.getRandomValues(new Uint8Array(128 / 8));
return HexEncoder.stringify(new Uint8Array(bytes));
}
exports.generateRandomSalt = generateRandomSalt;
async function signMessage(hashedPassword, message) {
const key = await subtle.importKey(
"raw",
HexEncoder.parse(hashedPassword),
{
name: "HMAC",
hash: "SHA-256",
},
false,
["sign"]
);
const signature = await subtle.sign("HMAC", key, UTF8Encoder.parse(message));
return HexEncoder.stringify(new Uint8Array(signature));
}
exports.signMessage = signMessage;
function getRandomAlphanum() {
const possibleCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
let byteArray;
let parsedInt;
// Keep generating new random bytes until we get a value that falls
// within a range that can be evenly divided by possibleCharacters.length
do {
byteArray = crypto.getRandomValues(new Uint8Array(1));
// extract the lowest byte to get an int from 0 to 255 (probably unnecessary, since we're only generating 1 byte)
parsedInt = byteArray[0] & 0xff;
} while (parsedInt >= 256 - (256 % possibleCharacters.length));
// Take the modulo of the parsed integer to get a random number between 0 and totalLength - 1
const randomIndex = parsedInt % possibleCharacters.length;
return possibleCharacters[randomIndex];
}
/**
* Generate a random string of a given length.
*
* @param {int} length
* @returns {string}
*/
function generateRandomString(length) {
let randomString = "";
for (let i = 0; i < length; i++) {
randomString += getRandomAlphanum();
}
return randomString;
}
exports.generateRandomString = generateRandomString;
return exports;
})());
const codec = ((function(){
const exports = {};
/**
* Initialize the codec with the provided cryptoEngine - this return functions to encode and decode messages.
*
* @param cryptoEngine - the engine to use for encryption / decryption
*/
function init(cryptoEngine) {
const exports = {};
/**
* Top-level function for encoding a message.
* Includes password hashing, encryption, and signing.
*
* @param {string} msg
* @param {string} password
* @param {string} salt
*
* @returns {string} The encoded text
*/
async function encode(msg, password, salt) {
const hashedPassword = await cryptoEngine.hashPassword(password, salt);
const encrypted = await cryptoEngine.encrypt(msg, hashedPassword);
// we use the hashed password in the HMAC because this is effectively what will be used a password (so we can store
// it in localStorage safely, we don't use the clear text password)
const hmac = await cryptoEngine.signMessage(hashedPassword, encrypted);
return hmac + encrypted;
}
exports.encode = encode;
/**
* Encode using a password that has already been hashed. This is useful to encode multiple messages in a row, that way
* we don't need to hash the password multiple times.
*
* @param {string} msg
* @param {string} hashedPassword
*
* @returns {string} The encoded text
*/
async function encodeWithHashedPassword(msg, hashedPassword) {
const encrypted = await cryptoEngine.encrypt(msg, hashedPassword);
// we use the hashed password in the HMAC because this is effectively what will be used a password (so we can store
// it in localStorage safely, we don't use the clear text password)
const hmac = await cryptoEngine.signMessage(hashedPassword, encrypted);
return hmac + encrypted;
}
exports.encodeWithHashedPassword = encodeWithHashedPassword;
/**
* Top-level function for decoding a message.
* Includes signature check and decryption.
*
* @param {string} signedMsg
* @param {string} hashedPassword
* @param {string} salt
* @param {int} backwardCompatibleAttempt
* @param {string} originalPassword
*
* @returns {Object} {success: true, decoded: string} | {success: false, message: string}
*/
async function decode(signedMsg, hashedPassword, salt, backwardCompatibleAttempt = 0, originalPassword = "") {
const encryptedHMAC = signedMsg.substring(0, 64);
const encryptedMsg = signedMsg.substring(64);
const decryptedHMAC = await cryptoEngine.signMessage(hashedPassword, encryptedMsg);
if (decryptedHMAC !== encryptedHMAC) {
// we have been raising the number of iterations in the hashing algorithm multiple times, so to support the old
// remember-me/autodecrypt links we need to try bringing the old hashes up to speed.
originalPassword = originalPassword || hashedPassword;
if (backwardCompatibleAttempt === 0) {
const updatedHashedPassword = await cryptoEngine.hashThirdRound(originalPassword, salt);
return decode(signedMsg, updatedHashedPassword, salt, backwardCompatibleAttempt + 1, originalPassword);
}
if (backwardCompatibleAttempt === 1) {
let updatedHashedPassword = await cryptoEngine.hashSecondRound(originalPassword, salt);
updatedHashedPassword = await cryptoEngine.hashThirdRound(updatedHashedPassword, salt);
return decode(signedMsg, updatedHashedPassword, salt, backwardCompatibleAttempt + 1, originalPassword);
}
return { success: false, message: "Signature mismatch" };
}
return {
success: true,
decoded: await cryptoEngine.decrypt(encryptedMsg, hashedPassword),
};
}
exports.decode = decode;
return exports;
}
exports.init = init;
return exports;
})());
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<boolean>}
*/
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 { isRememberEnabled, rememberDurationInDays, staticryptSaltUniqueVariableName } = staticryptConfig;
const { rememberExpirationKey, rememberPassphraseKey } = templateConfig;
// decrypt and replace the whole page
const hashedPassword = await cryptoEngine.hashPassword(password, staticryptSaltUniqueVariableName);
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 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;
return exports;
})());
const templateError = "Bad password!",
isRememberEnabled = true,
staticryptConfig = {"staticryptEncryptedMsgUniqueVariableName":"80a044d379a711505f38ec34d3f866f3dcf5f69d759a48ea96538ffd22e74b6c98840a63e9c3f6afaf0053d28d44678bcf3d7f5aef8f427539a1d1196d19fcb41d162f90475f0f691fe515af6e7cdf897ca0911fe77805bf773dc514cdfdfdcd2a40dc2d98ac4eeb55c331d81012cb980660c85b19c66284091291d07d2f39cae96fce5e3a3bc19991c5de9ab68072f194979a8b9dc04de209bd6a6279ad8602c20bdeaff48c5c4cb572b1fe6d86986d02e91e010b594bcde2799446bc4185ff7777b6150f3d08eb5abba6f12d05710da8ca5c09b8adf2797dbfa270d15e209e4f075d507e685a21c0e6bf85114d67a86fa1c4bd2953d6add88bfcfa9205253e34835e8f125f053ed519ccbce5a6a3bd13be381adb0cb39c8c5bd608ceb241414fc270bbc69f52008d04f79d098d99af2d33355abfa8e5094b3cb38241a41b1b3e80f3a5fffd99a4ccf8b925d335c9e77b47d99387e4af43bf7fbdd89b455fdca1edcbdf003f2eff7fcbe6ca27960c6ab1897a2c4f34d8a73e851701d2d97334d1b7889ba8d8acc9bf1e289dd8ebf2587c807827239df0db0f8edd220bc04eca74c4161696eadd833c35b64ceac0172a12516df58694a01cc89d61061383e8642aab5ac46d545c3b7cc73572303b4bccbb3cad83ada0fd0cd23cd9d809a5df7ffa787e748c8c2895acbc6697aa87bbe3eaff292b287f574b64f74e83370b497671a525e27367204792e377de1bd9e806889836f979b4dc417760a2e9334ff72ceade8b050ca52d892a119aea6a5fc5ba01753b6755cfa365884f53205975de66c736580fa48ae4fcd37ce9d823ee8ec086933b0876da9715d72b5532e376de26535f4e5c7c307542bf2ebb7ec706a8f3f77450f91dc6f39ce22f6a47956109716f015d676538a5138ae3bda8a893e3ebbb137ebbb48b0c06dfcbcc1e00e8dc4f1a43dcdb8b160e39f5fa15a47ffb35647e453773b6acbf68935612b51f53f871f5d3ec338854938b8e8a7e99cdfbf08a77daf366a2439d35a421db4bac8f4564ef1a0452dfeba59c0475089f69887098b5f9932184b195f5f3a5b5693beefff255db7bfe66f89a5d5651c9816ec8641e51242b7ff06ac886e65246e58deb1e15beb7513cac9ef78cd2b18db54ee39923d72e6c5f4514bcf32cdffdc785d39ea308b413bad5b7157d2d4477182bde0ed772daf12fa4ab0b346d3bdd64b1c66b62eb86305412d0e7e3acc43de10b87de0999cf012de39df94a4b24b7b4d4fa902004a02eed4e828fd77d645482d43729ecc6c188199957ae08e412e7870299d903f773a7acad94c882e6a5cfcf110ef90745a0c84d0542505c6c8dd80449e07ff07255ab87b9407b5ad77dac6b2c019272c63484b24cba47dc0c2bce46813b90da6f47bc913dde77dd070c39c39c7bbcd04428cdffb378613d3249d6250f0813a5f8fcf85c1f134117792dc53ed8a5db87e8730c82266c4550cd09f6e638950ccd3b625ff6a2a97b2c802a046abc88ee01e1c8db3b1cf5dcf9ef58f46efe681841d3f0b4ddded401e30d63af92601bf500159af7f5dbd497c823dfb26cb09f50244427593a6cd6b54be479a0efd41eb7a731b2292bb8ee3f8059da4e287c36b842760b682037ffb5074a1ed5ba879e8a6367ec7ed4bf0b5506ce43c3d7822dc1fe8ae582104a34631756c7db587a4ffb781916de8c207a124511a3262a02308888b302c2ace564a129b84f843589cb6fbb1ead2c5193888ff0c184b28442eeda50cd50e01f460855b16332cfe9854d0d7ee3d3590a625e8ba77dcb1d3334593c2372b520e01599605678d38042cc8a5684d8ae028f44d87795cd4c72635a626e2f118976c172b1190150d95666f78946b682d937d4284ed628b2c635e0302328110eed24a89f8929af1934785efe50277a1bda990f92a1b11668a4eb1ceb3870010afa1323ee3a7791a498292c054a4e8293659973c9c3b1ea25b28d7959802844d1a9ce3d557d0388fed0d19b37d63dd84a528c9b1b1ea2859c1504cb7aa0e6b2977e6831479b0907f1d5443bb0c14cb78f47f97a888349ef954fc90839e88d004fd8eef8cf581fd32874880e94b92fb0fc475d995938220dcfdb91d0bd69d5cb4ae3a6e56e04ead1d91423e1f81d0e006619db2e52f26e7a270f7bb851529845d4ad23ad178d7c3b04fcd5cb5fe68120ea6b06579ac4146eacb1634bc2a44a97b44b09ce3e5f8b63c1ee47ab90091b711555e05d1b2c07a788c09f9c28be5394669a30e7d13dfac6c5f78a01f28f40291b99d498a9abcc6753832fb5c8e6b3865d63982081e6059d57931fe84f8d2562d4d8854e1d39be43142956f135a7b4a7a7d28f6d09c037ba0b914120bf4ae5f1fcdb4415a254ca3afe5f51c54ac4be54ca47d721b9c4be1b9bcca4c86de00ffc852593b8eadf0e9f147fda8c9f7f5f5253ab39e880705a529a576adf44d93de39acecffd172e9dbc01b07b0501c9076ae0ad1f19ec9d7088194296813a6d8be7bf911ee9e05b06a5a1e563af31fb47ab5a2b218cb6095b79e70a5907b9da26f87bdaded6612d59d01d94710ff25b2ae8bd44ba233285f6271f40dcd896130bf8cb37b7984fc2a419693b0c8431f17a01c0b53dff80f997ad242f7b596857c3f958dae509f7b696565eab6fdde019afd393e71373e5273e3e0cbc9c92e78415d51208aba55d31418043a9bfd9006f87539fe97cc059ac7769f69f8eff942e0b2956eae1be03c8d9c85b973b7fdd24c932931880d597f624720830302c5e86950b747e147295c2f978ecebc2b2c19be5c9f93ecb1e1f07a8235be967d84549e578130ccbecaa0c1b33bc7850cc9bec2c8c57ec9e7dec99faff0daf89660c62f05c4bc04eebdd85a029aab45d4f19c1efdb82034f2aa49f13da7f19aca662858316d900449699a70f9cba71cf21c1e3f259443d4076fe2c7a2119013115cfe522cc31b3ef0a210970d5c161d61d1473c263a720f0005cdf890005f469b042cfd3489ae933359c285856e77fba3bd0163733bceeebe0299b031fd237a72bfee0c09199b7fef64b978480436542b07cc1eaa7869a0cf1210d2aea6882af48a646faa8c7c6d26b79d265546fe6edd73fcbbb19e30cd80d9a543554c3603ffc2fc3826e1ad273b81e459f3b797994106ddf42f3ef36141d6c19c139a2fa45b8efc3c4a7f6d13b3993878eec3c6bc54093489a39cd78717e3278739acd457a46b9ff59ab7e9c63ab8afade10013e2e79ba54fcce4bd15fc3b981311ec4735b9bc0297aaf613f4dcc307019c28e0827691131e9ed6c51af8293a9cfe0d213c3089c2a8e92856ea4d5f2a66dd49e04f69c43776baa0195f6ebcacb4e49c84884625cefd3bbd42b1e34c9e7c58fb85dc69eb04bf6bc2a6c70c1650825256fcf1f3f2416b60708cba0ec4574629220193807e87aca5bc04f454a2ead3c790ee09d6dfb14a895b3e5301752fe70dd55bd99694981ad6e8578fc3e858633d191ec46c19e5d152ca028dd42f619081044499020632d6f7ff14c9604acebb425f4d505b3dd0e1bd58c6eac6dafcf5942276eda427fee49b867cfb4c5cec8adbd282f2ccf2c56e00bf739b602da3293b33894de64f71444bece80c53dec6ebe29c14b3e6d0dad0316d0aac7cec3af4f1b80b84f1a9aa042165f0a1e805ceabca57876bddab4420119c99abae0672fde391bba12eb0c04f57a15c124f443b7d57eb79ecfb8c77e468165dd03e509efa1c33bd5b937e7285ec6142bb955903a31d1847556e19562f11544b1327c21566ce53e60d5ac5b5e4804758ea7d2fe250b8f795c7be27d943c10c5dcf3fcb1ddf3b2aba872257f50c0872975c2df99f76b3e91eef7c9a90bae42ac15653c8689930781a2e5691048e51aa4bd74463e30458a7ce6d842c2202b92ac4fd7597c8a08da4c2bd6ae41202c872e88965112f0a188e75f563874fde4322d7151a6845c10bd89c1c4f5f206ffeb43422451a6df9cf8ba331f546ce241fdc1475da2d916bee40395ffc754af4b498ca9213115d357f1c98e17d9f80798ef9c5743ef1ea5acf19d3df88ea86ef634a3e28d5fa2fa8c3896270973f54302db8b6dbecc584f0ebbfe5ff37dc9a22a4b70a1bbb990e8ed603c1a4841559c770f0432b32f40b6016c8bb9013372690f2b800d5721f0d4fc7b16c223c584ae2c1b8e1ef5bf80ef4cbabed936254d17958f260fdca83c4763271065241233b210a9e2b75176d461564fd95baf435f2f5ec405e86e2496d3da7704292bf2c321af8413b3bd8f46d260b9d9f5391dab62ee931309308a5d615a5217a9d7e6bc634b198c9f7c1197f93417edb89a0dd636356b2160a7f3678766d03cde2a5cd2f7e07f0e7b40071764671ed09d6230f7c436d0123e9ad12cc0363b6391817b2b7e3d0a3a94255e68fc914b6a5c662ebc8c1298793296a2f9526fedf662c2917fb26335d3b43d1c8b725a60dacf194d25d5bd9e0935350fc8e0ff101e061982d1014d724c1a57fd0910f5164f00739c1766f937338ceb0bb1bce1e57f0593749116755ca29ebc958e5f229d405d8785ea8a0d48711c4622a9f2b39ecd23a3fe243b35780dcf51496ab967a1719e7bf43c35cb50779e5556c21bce8e540d3146e4a902f6f5e9dde7789631e9b9f6e40a4d33b53ab07d31302747213ef9a3ace6fdc06581e6ce65c0896d0a3e89d351ccb651981cf705c9cd804b5813d934fc7cd876deb32ad3c0548719ce6836b02fc74f7727f9258222695b470a07f688c431fa35b30908f3abb5446725f584def07e76475df7e8b208d09ae4e6b4fba9e3c12b8eb9dfd4b3aefff4e4178b0a2410af6266f5b6396d0ce5d4f155eaddd46573f74694fbf581698ea6378cb0fe6be103709fe335f7aeee320af46969a220f9e32dd0f0497c0b4b1241a110b929e3c759e6de4fb8bfe6a79c94d65fd16b4d354c9bff61878fcd841c6b49af9594f187c02bd7a6d3612f200ff2b193321a407b1706033e7f1b58ac7e5369280c9e540c17412d93ee286afa67eee62665460030db105bebe8cc559b0ee1bcf10eb956f718af69b25de72a2a7ef7d53c31371e782240385e226331e838e58f0dd888d586242e493c54ef6dbc6decb64240df5f0ce24a61a368aec62a94bd0f563ebd55953e65e361c76b567915954c9f6588750c0a9868d68edf385929bc912a3e775fd16ac60bee6e33e7ded47c0cfe1fcc2547757f76350c0c634ba26c8bff43e4b1b299b7ba8250f1b2cbcf02216a39bc2a221038f5baa4f436d0296902428f53e7ed793d1e8522af7b782fd6a94b93ad724a43baf0a991d14e0d409d545dfeddc55522cb00da4e939f057975613edb022362c34d9586e94ab3418413e2f16d19e51ac4da3e7b84cef0c9f7e4bf987053b7eb28efba3b6188d1cbcb6329926fae289eb8c258fcb50d8075e3204cd6a818a7e852f681116042677b924baa601cdf7ed7d8afe0a51e7384e9ee534767b774f421c03ed4e1493dbb48cd653f31100fb0297f8c9238255d7edb2f25698a061d20199ae2de73dad06a47b7cb8c067dc8ca6f5756ad58522e97c6aba33ae647d4ea1f3fcf639fffc96fe6e643923a2015b8ec33e8525ba8a8401a60a70bae1520f1f89c826823e90c0aaad996e7e83174023c7665035dc59c3d45d75bde428356dbb7ea940554f3e85773704a54777309bb6425db2883d008db12d60e38e48ecd7433a65a67c7225f2ff3fd855329e9dfcee98f4de539ad9ad3083d5ca204cd8edef0181554618e80c1df64df428d8f506f09abd40ac4019e324121df47a8fea2e16e517f883f1c8a9d9b6c2fb625941242deafdc09102660e20f8d000094e84c2eac988a088b85fb0661a5e9a99fc49ed7bc5a01f1467a7600b62f9bde9d1136ae0e72f4f83e9e1f93060a5c172399f323edeabb6b9193c9e224af4a1787033c532e94925a5b99aa8150b03513cf708db73fb704c735144b7aaa43b859b7b88f7259a0a6891ab0269b6a9cba80bec938d3e6050660e0ff92f29d6378afc52592bdf26295b68d771189fabe834729090f5e16ea2dc53bc1cbad78f2ea78b39cdb4d56846e86ca7ee1d109ffcd1c66366be63980b011e2b2debf6baccda1887ff70ee6118ec050b2f01820c9865109f07d4e19561ddcdbde3146b6f84a6af102635181271957d587473e20c97281199e8c0aea4107f4644766de84319146af9b3c0afa1364f43cda13dd4b344707418278f430a402a6c98c28c8b459eda68c4f8abb78c5180a3a28c4ad33faed130b6d430995f36faa8b1ae4a2538d235d65632c408bafa442c9e93363a7d5c089290467a92810232b586c5a4dc88fa482e9babbf0ee6af186518ec68d3e1c6943fa2b6312055924b1f62d3203b61099e2d3f78725c0bf8668fabfd8c04bc43a97099668ce4c3d010ac6b36415852a16880d2f34916d88d654f713f7ca554cc748480927d21fefbf6edfbc66cde1864a8e1b1d2a2aa4e22c67a3ac1c5760ae22b0abb9c9d1fb48ed6fdc354c9d8e498b328ec96d27fde10cc63a05d0a81d84a44a08b000b5f3b1cb0f5a79479ac0d6b1918b55f126ca8425785d67eedae206700f9bbed181bd48a53e1d66764c706160b682a96dbc4175899e785cdbfc712ff93028d2c6bd7d534920e9a0","isRememberEnabled":true,"rememberDurationInDays":0,"staticryptSaltUniqueVariableName":"6afd70ebde4e587a19a4631b70ff6112"};
// you can edit these values to customize some of the behavior of StatiCrypt
const templateConfig = {
rememberExpirationKey: "staticrypt_expiration",
rememberPassphraseKey: "staticrypt_passphrase",
replaceHtmlCallback: null,
clearLocalStorageCallback: null,
};
// init the staticrypt engine
const staticrypt = staticryptInitiator.init(staticryptConfig, templateConfig);
// try to automatically decrypt on load if there is a saved password
window.onload = async function () {
const { isSuccessful } = await staticrypt.handleDecryptOnLoad();
// if we didn't decrypt anything on load, show the password prompt. Otherwise the content has already been
// replaced, no need to do anything
if (!isSuccessful) {
// hide loading screen
document.getElementById("staticrypt_loading").classList.add("hidden");
document.getElementById("staticrypt_content").classList.remove("hidden");
document.getElementById("staticrypt-password").focus();
// show the remember me checkbox
if (isRememberEnabled) {
document.getElementById("staticrypt-remember-label").classList.remove("hidden");
}
}
};
// handle password form submission
document.getElementById("staticrypt-form").addEventListener("submit", async function (e) {
e.preventDefault();
const password = document.getElementById("staticrypt-password").value,
isRememberChecked = document.getElementById("staticrypt-remember").checked;
const { isSuccessful } = await staticrypt.handleDecryptionOfPage(password, isRememberChecked);
if (!isSuccessful) {
alert(templateError);
}
});
</script>
</body>
</html>