diff --git a/mkdocs_nype/plugins/nype_tweaks/plugin.py b/mkdocs_nype/plugins/nype_tweaks/plugin.py index 3a001bf..17f7682 100644 --- a/mkdocs_nype/plugins/nype_tweaks/plugin.py +++ b/mkdocs_nype/plugins/nype_tweaks/plugin.py @@ -17,6 +17,11 @@ Jinja2.loaders.FileSystemLoader supports a list of paths, so override the macros plugin reference to FileSystemLoader. +4. HEX data obfuscation tweak: +Some data should be obfuscated in plain HTML to make it harder for bots to scrape +them. The obfuscation happens just before passing data to JavaScript. Later on +this data is deobfuscated in JavaScript. + MIT License 2024 Kamil Krzyśków (HRY) for Nype (npe.cm) """ @@ -33,8 +38,9 @@ from mkdocs_macros import plugin as macros_module from ...utils import MACROS_INCLUDES_ROOT +from . import utils from .config import NypeTweaksConfig -from .utils import ServeMode, get_file_system_loader +from .utils import ServeMode # region Core Logic Events @@ -63,10 +69,16 @@ def on_config(self, config: MkDocsConfig) -> MkDocsConfig | None: # Extend macros includes directory tweak if not ServeMode.run_once: - macros_module.FileSystemLoader = get_file_system_loader + macros_module.FileSystemLoader = utils.get_file_system_loader LOG.info("Tweaks initialized") + def on_env( + self, env: macros_module.Environment, /, *, config: MkDocsConfig, files: Files + ) -> macros_module.Environment | None: + # HEX data obfuscation tweak + env.filters["obfuscate"] = utils.obfuscate + @event_priority(-100) def on_post_page(self, output: str, /, *, page: Page, config: MkDocsConfig) -> str | None: diff --git a/mkdocs_nype/plugins/nype_tweaks/utils.py b/mkdocs_nype/plugins/nype_tweaks/utils.py index 48676bf..461fb66 100644 --- a/mkdocs_nype/plugins/nype_tweaks/utils.py +++ b/mkdocs_nype/plugins/nype_tweaks/utils.py @@ -1,3 +1,5 @@ +import base64 +import string from pathlib import Path from jinja2.loaders import FileSystemLoader @@ -24,3 +26,37 @@ def get_file_system_loader(value: str | list[str]): value.append(theme_includes) return FileSystemLoader(value) + + +def is_hex_string(text: str): + """Check if strings are represented in hex digits. Doesn't support 0x notation.""" + + # empty or None is False + if not text: + return False + + for char in text: + if char not in string.hexdigits: + return False + + return True + + +def obfuscate(text: str): + """Turn plain text into base64 and obfuscate it as hex""" + + if is_hex_string(text): + return text + + base64_encoded = base64.b64encode(text.encode()).decode() + hex_data = "".join(format(ord(c), "02x") for c in base64_encoded) + + assert text == deobfuscate(hex_data) + + return hex_data + + +def deobfuscate(hex_text: str): + """Turn hex back to string""" + + return base64.b64decode(bytes.fromhex(hex_text)).decode() diff --git a/mkdocs_nype/templates/assets/javascripts/nype-core.js b/mkdocs_nype/templates/assets/javascripts/nype-core.js index 02dc6e2..d56155b 100644 --- a/mkdocs_nype/templates/assets/javascripts/nype-core.js +++ b/mkdocs_nype/templates/assets/javascripts/nype-core.js @@ -63,8 +63,8 @@ document.addEventListener("DOMContentLoaded", () => { const allowPersonalEmails = config["contact_form_allow_personal_emails"]; // Support legacy option free_subject const freeSubject = config["contact_form_free_subject"] ?? config["contact_form_subject"]; - _gNypeDebug("actionHex", actionHex); - _gNypeDebug("emailHex", emailHex); + _gNypeDebug("actionHex", actionHex, _gNypeConvertHexToString(actionHex)); + _gNypeDebug("emailHex", emailHex, _gNypeConvertHexToString(emailHex)); _gNypeDebug("freeSubject", freeSubject); const form = document.querySelector(".nype-form"); diff --git a/mkdocs_nype/templates/nype-base.html b/mkdocs_nype/templates/nype-base.html index da7608e..109a1e4 100644 --- a/mkdocs_nype/templates/nype-base.html +++ b/mkdocs_nype/templates/nype-base.html @@ -84,7 +84,7 @@ {% if has_meta_config -%} {% for name, value in meta_config.items() -%} {% if name != 'js' -%} - {% set _ = page_nype_config.update({ name: value }) -%} + {% set _ = page_nype_config.update({ name.lower(): value }) -%} {% endif -%} {% endfor -%} {% endif -%} @@ -96,7 +96,7 @@ {% set value = page_nype_config.get(name) %} {% if not value and {}.__getitem__('ERROR: The value for page_nype_config.' ~ name ~ ' is undefined') %} {% endif %} - {% set _ = page_nype_config.js.update({ name: value })-%} + {% set _ = page_nype_config.js.update({ name.lower(): value })-%} {% endfor -%} {% endif %} @@ -107,6 +107,16 @@ {%- set _ = page_nype_config.js.update({ name.lower(): value })-%} {% endfor -%} {% endif -%} + + {#- Obfuscate values that should not be in plain text in the HTML -#} + {% if page_nype_config.obfuscate_keys %} + {% for name, value in page_nype_config.js.items() %} + {% if name in page_nype_config.obfuscate_keys %} + {% set value = (value | obfuscate) %} + {% endif %} + {%- set _ = page_nype_config.js.update({ name: value })-%} + {% endfor %} + {% endif %}