mirror of
https://github.com/hugo-fixit/FixIt.git
synced 2026-08-24 15:28:57 +00:00
♻️ Refactor: refactor content encryption
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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() {
|
||||
|
||||
@@ -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,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 -}}
|
||||
|
||||
@@ -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 -}}
|
||||
|
||||
@@ -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 -}}
|
||||
|
||||
Reference in New Issue
Block a user