Skip to content

Commit

Permalink
♻️ Refactor: refactor content encryption
Browse files Browse the repository at this point in the history
  • Loading branch information
Lruihao committed Aug 29, 2024
1 parent e7dd4c8 commit b7ed9ca
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 120 deletions.
47 changes: 43 additions & 4 deletions assets/css/_partials/_single/_fixit-decryptor.scss
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -60,19 +64,54 @@
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,
.fixit-decryptor-input,
.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;
}
}

Expand Down
135 changes: 70 additions & 65 deletions assets/js/fixit-decryptor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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}`,
Expand All @@ -100,57 +113,51 @@ 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();
}
});

// 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);
});
};

Expand All @@ -167,6 +174,8 @@ FixItDecryptor = function (options = {}) {
e.preventDefault();
decryptorHandler();
});

$shortcode.classList.add('initialized');
});
};

Expand All @@ -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');
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!');
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;
}
_decryptContent($content, cachedStat.salt);
_decryptContent($cipherText, $content, cachedStat.salt);
return this;
};

Expand Down
11 changes: 1 addition & 10 deletions assets/js/theme.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
12 changes: 4 additions & 8 deletions layouts/_default/single.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,15 @@ <h1 class="single-title animate__animated animate__pulse animate__faster">{{- ti

{{- /* 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>
Expand Down
2 changes: 1 addition & 1 deletion layouts/partials/init/index.html
Original file line number Diff line number Diff line change
@@ -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" . -}}
Expand Down
33 changes: 33 additions & 0 deletions layouts/partials/plugin/fixit-encryptor.html
Original file line number Diff line number Diff line change
@@ -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 -}}

Loading

0 comments on commit b7ed9ca

Please sign in to comment.