♻️ Refactor: refactor content encryption

This commit is contained in:
Cell
2024-08-28 18:11:36 +08:00
parent e7dd4c838c
commit b7ed9cac69
8 changed files with 158 additions and 120 deletions
@@ -1,12 +1,16 @@
.fixit-decryptor-container {
font-family: $global-font-family;
text-align: center;
margin-top: 3rem;
margin-block: var(--fixit-decryptor-margin-block);
.fixit-decryptor-loading {
vertical-align: middle;
}
.fixit-decryptor-input,
.fixit-decryptor-btn,
.fixit-encryptor-btn {
display: inline-block;
display: none;
box-sizing: border-box;
outline: none;
color: $global-font-color;
@@ -60,12 +64,33 @@
background-color: $header-background-color-dark;
}
}
.fixit-encryptor-btn {
display: none;
}
}
// fixit-encryptor shortcodes
fixit-encryptor {
&.initialized > .fixit-decryptor-container {
.fixit-decryptor-input,
.fixit-decryptor-btn {
display: inline-block;
}
.fixit-decryptor-loading {
display: none;
}
}
cipher-text {
display: none !important;
}
}
// fixit-encryptor for for the encrypted pages
article fixit-encryptor {
.fixit-decryptor-container {
margin-top: 1rem;
--fixit-decryptor-margin-block: 2rem;
}
&.decrypted > .fixit-decryptor-container {
.fixit-decryptor-loading,
@@ -73,6 +98,20 @@ fixit-encryptor {
.fixit-decryptor-btn {
display: none;
}
.fixit-encryptor-btn {
display: inline-block;
}
}
}
// fixit-encryptor shortcodes
#content fixit-encryptor {
.fixit-decryptor-container {
--fixit-decryptor-margin-block: 1rem;
}
&.decrypted > .fixit-decryptor-container {
display: none;
}
}
+70 -65
View File
@@ -14,52 +14,41 @@ FixItDecryptor = function (options = {}) {
this.decryptedEventSet = new Set();
this.partialDecryptedEventSet = new Set();
this.resetEventSet = new Set();
this.$el = document.querySelector('.fixit-decryptor-container');
customElements.get('fixit-encryptor') || customElements.define('fixit-encryptor', class extends HTMLElement {});
customElements.get('cipher-text') || customElements.define('cipher-text', class extends HTMLElement {});
/**
* decrypt content
* @param {Element} $content content element
* @param {Element} $cipherText cipher text element
* @param {Element} $target target content element
* @param {String} salt salt string
* @param {Boolean} [isAll=true] whether to decrypt all content
*/
var _decryptContent = ($content, salt, isAll=true) => {
var _decryptContent = ($cipherText, $target, salt) => {
try {
if (isAll) {
// decrypt all content
this.$el.querySelector('.fixit-decryptor-loading').classList.add('d-none');
this.$el.querySelector('.fixit-decryptor-input').classList.add('d-none');
this.$el.querySelector('.fixit-decryptor-btn').classList.add('d-none');
this.$el.querySelector('.fixit-encryptor-btn').classList.remove('d-none');
} else {
// decrypt shortcode content
$content.parentElement.classList.add('decrypted');
}
$content.insertAdjacentHTML(
'afterbegin',
CryptoJS.enc.Base64
.parse($content.getAttribute('data-content').replace(salt, ''))
.toString(CryptoJS.enc.Utf8)
);
$target.innerHTML = CryptoJS.enc.Base64
.parse($cipherText.innerText.replace(salt, ''))
.toString(CryptoJS.enc.Utf8);
$cipherText.parentElement.classList.add('decrypted');
} catch (err) {
return console.error(err);
}
// decrypted hook
const eventSet = isAll ? this.decryptedEventSet : this.partialDecryptedEventSet;
const eventSet = $target.id === 'content' ? this.decryptedEventSet : this.partialDecryptedEventSet;
for (const event of eventSet) {
event($content);
event($target);
}
};
/**
* validate password
* @param {Element} $decryptor decryptor element
* @param {Element} $content content element
* @param {Element} $encryptor fixit-encryptor element
* @param {Function} callback callback function after password validation
* @returns
*/
var _validatePassword = async ($decryptor, $content, callback) => {
const password = $content.getAttribute('data-password');
const inputEl = $decryptor.querySelector('.fixit-decryptor-input');
var _validatePassword = async ($encryptor, callback) => {
const $cipherText = $encryptor.querySelector('cipher-text');
const password = $cipherText.dataset.password;
const inputEl = $encryptor.querySelector('.fixit-decryptor-input');
const input = inputEl.value.trim();
const { h64ToString } = await xxhash();
const inputHash = h64ToString(input);
@@ -76,21 +65,45 @@ FixItDecryptor = function (options = {}) {
alert(`Password error: ${input} not the correct password!`);
return console.warn(`Password error: ${input} not the correct password!`);
}
callback(inputHash, inputSha256.slice(saltLen));
callback($cipherText, inputHash, inputSha256.slice(saltLen));
}
/**
* initialize FixIt decryptor
* @param {Object} options
* @param {Boolean} options.all whether to decrypt all content
* @param {String} options.shortcode whether to decrypt fixit-encryptor shortcode
*/
_proto.init = () => {
_proto.init = ({ all, shortcode }) => {
this.addEventListener('decrypted', this.options?.decrypted);
this.addEventListener('partial-decrypted', this.options?.partialDecrypted);
this.addEventListener('reset', this.options?.reset);
const $content = document.querySelector('#content');
if (shortcode) {
this.addEventListener('decrypted', () => {
this.initShortcodes($content);
});
this.addEventListener('partial-decrypted', ($parent) => {
this.initShortcodes($parent);
});
}
if (all) {
this.initPage();
} else if (shortcode) {
this.initShortcodes($content);
}
};
/**
* initialize FixIt decryptor for the encrypted pages
*/
_proto.initPage = () => {
this.validateCache();
const $encryptor = document.querySelector('article > fixit-encryptor');
const $content = document.querySelector('#content');
const decryptorHandler = () => {
const $content = document.querySelector('#content');
_validatePassword(this.$el, $content, (passwordHash, salt) => {
_validatePassword($encryptor, ($cipherText, passwordHash, salt) => {
// cache decryption statistics
window.localStorage?.setItem(
`fixit-decryptor/#${location.pathname}`,
@@ -100,12 +113,12 @@ FixItDecryptor = function (options = {}) {
salt,
})
);
_decryptContent($content, salt);
_decryptContent($cipherText, $content, salt);
});
};
// bind decryptor input enter keydown event
this.$el.querySelector('#fixit-decryptor-input')?.addEventListener('keydown', (e) => {
$encryptor.querySelector('.fixit-decryptor-input')?.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
decryptorHandler();
@@ -113,44 +126,38 @@ FixItDecryptor = function (options = {}) {
});
// bind decryptor button click event
this.$el.querySelector('.fixit-decryptor-btn')?.addEventListener('click', (e) => {
$encryptor.querySelector('.fixit-decryptor-btn')?.addEventListener('click', (e) => {
e.preventDefault();
decryptorHandler();
});
// bind encryptor button click event
this.$el.querySelector('.fixit-encryptor-btn')?.addEventListener('click', (e) => {
$encryptor.querySelector('.fixit-encryptor-btn')?.addEventListener('click', (e) => {
e.preventDefault();
e.target.classList.add('d-none')
this.$el.querySelector('.fixit-decryptor-input').classList.remove('d-none');
this.$el.querySelector('.fixit-decryptor-btn').classList.remove('d-none');
document.querySelector('#content').innerHTML = '';
document.querySelector('#content').insertAdjacentElement(
'afterbegin',
this.$el
);
$encryptor.classList.remove('decrypted');
$content.innerHTML = '';
window.localStorage?.removeItem(`fixit-decryptor/#${location.pathname}`);
// reset hook
for (const event of this.resetEventSet) {
event();
}
});
$encryptor.classList.add('initialized');
};
/**
* initialize fixit-encryptor shortcodes
* @param {Element} [$container=document] container element
* initialize FixIt decryptor for fixit-encryptor shortcodes
* @param {Element} $parent parent element
*/
_proto.initShortcodes = ($container = document) => {
customElements.get('fixit-encryptor') || customElements.define('fixit-encryptor', class extends HTMLElement {});
const $shortcodes = $container.querySelectorAll('fixit-encryptor:not(:has(.decrypted))');
_proto.initShortcodes = ($parent) => {
const $shortcodes = $parent.querySelectorAll('fixit-encryptor:not(.initialized)');
$shortcodes.forEach($shortcode => {
const decryptorHandler = () => {
const $decryptor = $shortcode.querySelector('.fixit-decryptor-container');
const $content = $shortcode.querySelector('[data-password][data-content]');
_validatePassword($decryptor, $content, (passwordHash, salt) => {
_decryptContent($content, salt, false);
const $content = $shortcode.querySelector('.decryptor-content');
_validatePassword($shortcode, ($cipherText, passwordHash, salt) => {
_decryptContent($cipherText, $content, salt);
});
};
@@ -167,6 +174,8 @@ FixItDecryptor = function (options = {}) {
e.preventDefault();
decryptorHandler();
});
$shortcode.classList.add('initialized');
});
};
@@ -176,23 +185,19 @@ FixItDecryptor = function (options = {}) {
*/
_proto.validateCache = () => {
const $content = document.querySelector('#content');
const password = $content.getAttribute('data-password');
const $encryptor = document.querySelector('article > fixit-encryptor');
const $cipherText = $encryptor.querySelector('cipher-text');
const password = $cipherText.dataset.password;
const cachedStat = JSON.parse(window.localStorage?.getItem(`fixit-decryptor/#${location.pathname}`));
if (!cachedStat) {
this.$el.querySelector('.fixit-decryptor-loading').classList.add('d-none');
this.$el.querySelector('.fixit-decryptor-input').classList.remove('d-none');
this.$el.querySelector('.fixit-decryptor-btn').classList.remove('d-none');
if (!cachedStat || cachedStat?.password !== password || Number(cachedStat?.expiration) < Math.ceil(Date.now() / 1000)) {
if (cachedStat) {
window.localStorage?.removeItem(`fixit-decryptor/#${location.pathname}`);
console.warn('The password has expired, please re-enter!');
}
return this;
}
if (cachedStat?.password !== password || Number(cachedStat?.expiration) < Math.ceil(Date.now() / 1000)) {
this.$el.querySelector('.fixit-decryptor-loading').classList.add('d-none');
this.$el.querySelector('.fixit-decryptor-input').classList.remove('d-none');
window.localStorage?.removeItem(`fixit-decryptor/#${location.pathname}`);
console.warn('The password has expired, please re-enter!');
return this;
}
_decryptContent($content, cachedStat.salt);
_decryptContent($cipherText, $content, cachedStat.salt);
return this;
};
+1 -10
View File
@@ -1043,16 +1043,7 @@ class FixIt {
});
}
});
if (this.config.encryption?.shortcode) {
this.decryptor.addEventListener('decrypted', () => {
this.decryptor.initShortcodes();
})
this.decryptor.addEventListener('partial-decrypted', ($content) => {
this.decryptor.initShortcodes($content);
})
this.decryptor.initShortcodes();
}
this.config.encryption?.all && this.decryptor.init();
this.decryptor.init(this.config.encryption);
}
initAutoMark() {
+4 -8
View File
@@ -16,19 +16,15 @@
{{- /* Content */ -}}
{{- $content := dict "Content" .Content "Ruby" $params.ruby "Fraction" $params.fraction "Fontawesome" $params.fontawesome | partial "function/content.html" | safeHTML -}}
{{- /* Content Encryption */ -}}
{{- $content = dict "Content" $content "Password" $params.password | partial "function/content-encryption.html" | safeHTML -}}
<div class="content" id="content"
{{- with $params.password }} data-password="{{ hash.XxHash . }}"{{ end }}
{{- with $params.password }} data-content="{{ $content }}"{{ end -}}
>
<div class="content" id="content">
{{- if not $params.password -}}
{{- $content -}}
{{- else -}}
{{- partial "single/fixit-decryptor.html" . -}}
{{- end -}}
</div>
{{- /* Content Encryption */ -}}
{{- dict "Content" $content "Password" $params.password "Message" $params.message | partial "plugin/fixit-encryptor.html" -}}
{{- /* Comment */ -}}
{{- partial "single/comment.html" . -}}
</article>
+1 -1
View File
@@ -1,4 +1,4 @@
{{- .Scratch.Set "version" "v0.3.10-91f3c7c3" -}}
{{- .Scratch.Set "version" "v0.3.10-d0888c45" -}}
{{- .Scratch.Set "this" dict -}}
{{- partial "init/detection-env.html" . -}}
@@ -0,0 +1,33 @@
{{- /* If Hugo Support AES, refactor with AES. */ -}}
{{- if .Password -}}
{{- /* Content Encryption */ -}}
{{- $content := dict "Content" .Content "Password" .Password | partial "function/content-encryption.html" -}}
{{- /* Generating fixit-encryptor DOM */ -}}
{{- $message := .Message | default (T "single.encryptedMessage") -}}
{{- $loading := resources.Get "images/loading.svg" | minify -}}
{{- $id := "fixit-decryptor-input" -}}
{{- if .IsPartial -}}
{{- $id = dict "Scratch" page.Scratch | partial "function/id.html" -}}
{{- end -}}
<fixit-encryptor>
<div class="fixit-decryptor-container">
<img class="fixit-decryptor-loading" src="{{ $loading.RelPermalink }}" alt="decryptor loading" width="48" height="48" />
<label for="{{ $id }}" title="{{ T `single.password` }}">
<input type="password" id="{{ $id }}" class="fixit-decryptor-input" placeholder="🔑 {{ $message }}" />
</label>
<button class="fixit-decryptor-btn">
{{- dict "Class" "fa-solid fa-unlock" | partial "plugin/icon.html" }} {{ T "single.enterBtn" -}}
</button>
{{- if not .IsPartial -}}
<button class="fixit-encryptor-btn">
{{- dict "Class" "fa-solid fa-lock" | partial "plugin/icon.html" }} {{ T "single.encryptyAgain" -}}
</button>
{{- end -}}
</div>
{{- if .IsPartial }}<div class="decryptor-content"></div>{{ end -}}
<cipher-text data-password="{{ xxhash .Password }}">{{ $content }}</cipher-text>
</fixit-encryptor>
{{- end -}}
+3 -9
View File
@@ -160,13 +160,7 @@
{{- /* Content */ -}}
{{- $content := dict "Content" .Content "Ruby" $params.ruby "Fraction" $params.fraction "Fontawesome" $params.fontawesome | partial "function/content.html" | safeHTML -}}
{{- /* Content Encryption */ -}}
{{- $content = dict "Content" $content "Password" $params.password | partial "function/content-encryption.html" | safeHTML -}}
<div class="content" id="content"
{{- with $params.endFlag }} data-end-flag="{{ . }}"{{ end }}
{{- with $params.password }} data-password="{{ xxhash . }}"{{ end }}
{{- with $params.password }} data-content="{{ $content }}"{{ end -}}
>
<div class="content" id="content"{{ with $params.endFlag }} data-end-flag="{{ . }}"{{ end }}>
{{- if not $params.password -}}
{{- /* Expiration Reminder */ -}}
{{- partial "single/expiration-reminder.html" . -}}
@@ -186,8 +180,8 @@
{{- /* Collection Navigation */ -}}
{{- partial "single/collection-nav.html" . -}}
{{- /* FixIt Decryptor */ -}}
{{- partial "single/fixit-decryptor.html" . -}}
{{- /* Content Encryption */ -}}
{{- dict "Content" $content "Password" $params.password "Message" $params.message | partial "plugin/fixit-encryptor.html" -}}
{{- /* Custom block before post footer */ -}}
{{- block "custom-post__footer:before" . }}{{ end -}}
+3 -23
View File
@@ -1,31 +1,11 @@
{{- $password := "" -}}
{{- $message := "" -}}
{{- if .IsNamedParams -}}
{{- $password = .Get "password" | default "" -}}
{{- $message = .Get "message" | default (T "single.encryptedMessage") -}}
{{- else -}}
{{- $password = .Get 0 | default "" -}}
{{- $message = .Get 1 | default (T "single.encryptedMessage") -}}
{{- end -}}
{{- $password := cond .IsNamedParams (.Get "password") (.Get 0) | default "" -}}
{{- $message := cond .IsNamedParams (.Get "message") (.Get 1) | default (T "single.encryptedMessage") -}}
{{- if $password -}}
{{- /* Content */ -}}
{{- $content := .Inner | .Page.RenderString -}}
{{- /* Content Encryption */ -}}
{{- $content = dict "Content" $content "Password" $password | partial "function/content-encryption.html" | safeHTML -}}
<fixit-encryptor class="fixit-encryptor-shortcode">
<div class="fixit-decryptor-container">
<label title='{{ T "single.password" }}'>
<input type="password" class="fixit-decryptor-input" placeholder="🔑 {{ $message }}" />
</label>
<button class="fixit-decryptor-btn">
{{- dict "Class" "fa-solid fa-unlock" | partial "plugin/icon.html" }} {{ T "single.enterBtn" -}}
</button>
</div>
<div data-password="{{ xxhash $password }}" data-content="{{ $content }}"></div>
</fixit-encryptor>
{{- dict "Content" $content "Password" $password "Message" $message "IsPartial" true | partial "plugin/fixit-encryptor.html" -}}
{{- .Page.Scratch.SetInMap "this" "encryptPartial" true -}}
{{- else -}}
{{- .Inner -}}