From a86d13431b2e2ba5002423e25fa17039fcfaadba Mon Sep 17 00:00:00 2001 From: Minoricew <154642983+Minoricew@users.noreply.github.com> Date: Mon, 2 Jun 2025 00:22:46 +0800 Subject: [PATCH] [Feat] Settings UI Auth (#3) & Prepare for v0.1.1-rel --- src/aura/init/shared/configManager.js | 230 ++++++++++++++++-- src/aura/init/shared/default.json | 7 + src/aura/types/shared/config.d.ts | 9 + src/aura/ui/css/form.css | 26 ++ src/aura/ui/pages/config/config.css | 77 ++++++ src/aura/ui/pages/config/config.html | 28 ++- src/aura/ui/pages/config/config.js | 80 +++++- .../disableLimitations.html | 22 +- .../disableLimitations/disableLimitations.js | 7 + .../disableLimitations/settings/aura.js | 113 +++++++++ .../disableLimitations/settings/auth.js | 2 +- src/core/preload.js | 2 +- 12 files changed, 569 insertions(+), 34 deletions(-) create mode 100755 src/aura/types/shared/config.d.ts create mode 100755 src/aura/ui/pages/configSubPages/disableLimitations/settings/aura.js diff --git a/src/aura/init/shared/configManager.js b/src/aura/init/shared/configManager.js index e97a9a7..73c1d2e 100755 --- a/src/aura/init/shared/configManager.js +++ b/src/aura/init/shared/configManager.js @@ -1,11 +1,34 @@ -const fs = require('fs'); -const path = require('path'); -const os = require('os'); +// @ts-check +const fs = require("fs"); +const path = require("path"); +const os = require("os"); +const crypto = require("crypto"); +const childProc = require("child_process"); + +// Constants + +const CRYPTO_SETTINGS_AES = { + mode: "aes-256-gcm", + keyLength: 32, + keyIter: 100000, + ivLength: 12, + tagLength: 16, + saltLength: 16, + obfuscateStr: "eCybsseK", + hash: "sha256", +}; + +/** + * + * @param {Record} target + * @param {Record} source + * @returns + */ const deepMerge = (target, source) => { const result = JSON.parse(JSON.stringify(target)); - if (!source || typeof source !== 'object') { + if (!source || typeof source !== "object") { return {}; } @@ -15,9 +38,9 @@ const deepMerge = (target, source) => { if (!(key in source)) { keysToDelete.push(key); } else if ( - typeof result[key] === 'object' && + typeof result[key] === "object" && result[key] !== null && - typeof source[key] === 'object' && + typeof source[key] === "object" && source[key] !== null ) { result[key] = deepMerge(result[key], source[key]); @@ -42,13 +65,11 @@ const deepMerge = (target, source) => { class ConfigManager { constructor() { - this.configPath = path.join( - os.homedir(), - 'Documents', - 'HugoAura', - 'config.json' - ); - this.defaultConfigPath = path.join(__dirname, 'default.json'); + this.configDir = path.join(os.homedir(), "Documents", "HugoAura"); + this.configPath = path.join(this.configDir, "config.json"); + this.encConfigPath = path.join(this.configDir, ".cache_2eafc8d0.dat"); // (雾 + /* ↑ 不使用 .tmp 扩展名, 不然容易真被清理了 */ + this.defaultConfigPath = path.join(__dirname, "default.json"); } getHugoAuraConfigPath() { @@ -61,10 +82,10 @@ class ConfigManager { getDefaultConfig() { try { - return JSON.parse(fs.readFileSync(this.defaultConfigPath, 'utf8')); + return JSON.parse(fs.readFileSync(this.defaultConfigPath, "utf8")); } catch (err) { console.warn( - '[HugoAura / Config] No default config found, using empty config' + "[HugoAura / Config] No default config found, using empty config" ); return { rewrite: {} }; } @@ -74,12 +95,12 @@ class ConfigManager { if (global.__HUGO_AURA__.configInit) return; const hugoAuraPath = this.getHugoAuraConfigPath(); if (!fs.existsSync(hugoAuraPath)) { - console.log('[HugoAura / Config] Creating HugoAura directory'); + console.log("[HugoAura / Config] Creating HugoAura directory"); fs.mkdirSync(hugoAuraPath, { recursive: true }); } if (!fs.existsSync(this.configPath)) { - console.log('[HugoAura / Config] Creating default config file'); + console.log("[HugoAura / Config] Creating default config file"); const defaultConfig = this.getDefaultConfig(); this.writeConfig(defaultConfig); } @@ -87,25 +108,30 @@ class ConfigManager { readConfig() { try { - const config = JSON.parse(fs.readFileSync(this.configPath, 'utf8')); - console.log('[HugoAura / Config] Successfully loaded config:', config); + const config = JSON.parse(fs.readFileSync(this.configPath, "utf8")); + console.log("[HugoAura / Config] Successfully loaded config:", config); return config; } catch (err) { - console.error('[HugoAura / Config] Failed to read config:', err); + console.error("[HugoAura / Config] Failed to read config:", err); return this.getDefaultConfig(); } } + /** + * + * @param {Record} config + * @returns + */ writeConfig(config) { try { fs.writeFileSync( this.configPath, JSON.stringify(config, null, 2), - 'utf8' + "utf8" ); return true; } catch (err) { - console.error('[HugoAura / Config] Failed to write config:', err); + console.error("[HugoAura / Config] Failed to write config:", err); return false; } } @@ -115,23 +141,177 @@ class ConfigManager { let config = {}; try { if (fs.existsSync(this.configPath)) { - const userConfig = JSON.parse(fs.readFileSync(this.configPath, 'utf8')); + const userConfig = JSON.parse(fs.readFileSync(this.configPath, "utf8")); if (global.__HUGO_AURA__.configInit) { config = userConfig; return userConfig; } else { config = deepMerge(userConfig, defaultConfig); - console.log('[HugoAura / Config] Merged with user config'); + console.log("[HugoAura / Config] Merged with user config"); this.writeConfig(config); } } } catch (err) { - console.error('[HugoAura / Config] Failed to load user config:', err); + console.error("[HugoAura / Config] Failed to load user config:", err); config = defaultConfig; } return config; } + + /** + * + * @param {Record} configData + * @param {string} passwd + */ + encryptConfig(configData, passwd) { + const salt = crypto.randomBytes(CRYPTO_SETTINGS_AES.saltLength); + const key = crypto.pbkdf2Sync( + passwd, + salt, + CRYPTO_SETTINGS_AES.keyIter, + CRYPTO_SETTINGS_AES.keyLength, + CRYPTO_SETTINGS_AES.hash + ); + const iv = crypto.randomBytes(CRYPTO_SETTINGS_AES.ivLength); + + const cipherIns = crypto.createCipheriv(CRYPTO_SETTINGS_AES.mode, key, iv, { + // @ts-expect-error + authTagLength: CRYPTO_SETTINGS_AES.tagLength, + }); + + const stringifyConfigData = JSON.stringify(configData); + let encryptedCfg = cipherIns.update(stringifyConfigData, "utf-8", "hex"); + encryptedCfg += cipherIns.final("hex"); + const authTag = cipherIns.getAuthTag(); + + /** @type {EncryptedConfig} */ + const encConfigFinal = {}; + encConfigFinal.content = encryptedCfg; + encConfigFinal.authTag = authTag.toString("base64"); + encConfigFinal.salt = salt.toString("base64"); + encConfigFinal.iv = iv.toString("base64"); + + const base64EncConfig = + CRYPTO_SETTINGS_AES.obfuscateStr + + Buffer.from(JSON.stringify(encConfigFinal)).toString("base64"); + + try { + fs.writeFileSync(this.encConfigPath, base64EncConfig, "utf-8"); + // fs.rmSync(this.configPath); + + const _hideFileProc = childProc.spawnSync( + "cmd.exe", + ["/c", "attrib", "+h", this.encConfigPath], + { + stdio: "inherit", + } + ); + return true; + } catch (err) { + console.error( + "[HugoAura / Config] Failed to write encrypted config:", + err + ); + console.error( + "[HugoAura / Config] Pending config data:", + base64EncConfig + ); + return false; + } + } + + /** + * + * @param {string} passwd + * @returns + */ + decryptConfig(passwd) { + const FAILED_RET = { + success: false, + data: {}, + }; + + let base64EncConfig = null; + + try { + if (!fs.existsSync(this.encConfigPath)) { + return FAILED_RET; + } + base64EncConfig = fs.readFileSync(this.encConfigPath, "utf-8"); + } catch (err) { + console.error( + "[HugoAura / Config] Failed to read encrypted config:", + err + ); + return FAILED_RET; + } + + if (base64EncConfig) { + const strip64EncCfg = base64EncConfig.split( + CRYPTO_SETTINGS_AES.obfuscateStr + )[1]; + const encryptCfg = Buffer.from(strip64EncCfg, "base64").toString("utf-8"); + /** @type {null | EncryptedConfig} */ + let parsedEncCfg = null; + try { + parsedEncCfg = JSON.parse(encryptCfg); + } catch (err) { + console.error( + "[HugoAura / Config] Failed to parse encrypted config:", + err + ); + console.error("[HugoAura / Config] Pending data:", encryptCfg); + } + if (parsedEncCfg === null) return FAILED_RET; + + const salt = Buffer.from(parsedEncCfg.salt, "base64"); + const iv = Buffer.from(parsedEncCfg.iv, "base64"); + + const authTag = Buffer.from(parsedEncCfg.authTag, "base64"); + + const key = crypto.pbkdf2Sync( + passwd, + salt, + CRYPTO_SETTINGS_AES.keyIter, + CRYPTO_SETTINGS_AES.keyLength, + CRYPTO_SETTINGS_AES.hash + ); + + const decipherIns = crypto.createDecipheriv( + CRYPTO_SETTINGS_AES.mode, + key, + iv, + { + // @ts-expect-error + authTagLength: CRYPTO_SETTINGS_AES.tagLength, + } + ); + + decipherIns.setAuthTag(authTag); + + let stringifyDecCfg = Buffer.concat([ + decipherIns.update(parsedEncCfg.content, "hex"), + decipherIns.final(), + ]).toString(); + + /** @type {null | Record} */ + let decConfig = null; + try { + decConfig = JSON.parse(stringifyDecCfg); + } catch (err) { + console.error( + "[HugoAura / Config] Failed to parse decrypted config:", + err + ); + console.error("[HugoAura / Config] Pending data:", decConfig); + return FAILED_RET; + } + if (decConfig === null) return FAILED_RET; + + console.debug(decConfig); + } + } } module.exports = new ConfigManager(); diff --git a/src/aura/init/shared/default.json b/src/aura/init/shared/default.json index e5fa983..48b30ac 100755 --- a/src/aura/init/shared/default.json +++ b/src/aura/init/shared/default.json @@ -19,5 +19,12 @@ } }, "plsToken": "66ccff0d000721114514191981023333", + "auraSettings": { + "settingsPasswordEnabled": false, + "settingsPasswordWithSalt": "32703D292460CC9A3B867494D6AD9A8E4A3ADF0FAA4D6867BC4D412CC3927D02E47C6D0B1763BB53E57B2241C6193433561CDA09D7C48CA03983072B876F0965", + "appearance": { + "enablePasswdDialogBlur": true + } + }, "devTools": false } diff --git a/src/aura/types/shared/config.d.ts b/src/aura/types/shared/config.d.ts new file mode 100755 index 0000000..2034ec0 --- /dev/null +++ b/src/aura/types/shared/config.d.ts @@ -0,0 +1,9 @@ +type AES256EncryptedConfig = string; +type Base64String = string; + +interface EncryptedConfig { + content: AES256EncryptedConfig; + authTag: Base64String; + salt: Base64String; + iv: Base64String; +} diff --git a/src/aura/ui/css/form.css b/src/aura/ui/css/form.css index 5abbc26..1aede04 100755 --- a/src/aura/ui/css/form.css +++ b/src/aura/ui/css/form.css @@ -68,3 +68,29 @@ .aura-settings-entry-property-icon.layui-icon-refresh { color: rgb(0, 106, 188); } + +/* Animations */ + +@keyframes invalidShake { + 0% { + margin-left: calc(-10px * 2); + } + 16% { + margin-left: calc(9px * 2); + } + 33% { + margin-left: calc(-6px * 2); + } + 50% { + margin-left: calc(5px * 2); + } + 66% { + margin-left: calc(-2px * 2); + } + 83% { + margin-left: calc(1px * 2); + } + 100% { + margin-left: calc(0px * 2); + } +} diff --git a/src/aura/ui/pages/config/config.css b/src/aura/ui/pages/config/config.css index 5c6811e..2c4daed 100755 --- a/src/aura/ui/pages/config/config.css +++ b/src/aura/ui/pages/config/config.css @@ -45,6 +45,7 @@ padding-left: 8px; padding-right: 8px; color: white; + z-index: 12000; opacity: 1; transform: translateY(0); @@ -368,6 +369,82 @@ font-size: small; } +/* Auth Dialog */ + +.aura-config-page-auth-dialog-area { + position: absolute; + height: calc(100% - 40px); + width: 100%; + top: 40px; + left: 0; + display: flex; + justify-content: center; + align-items: center; + z-index: 10000; + background-color: rgba(255, 255, 255, 0.35); + + opacity: 1; + transition: all 0.5s; +} + +.aura-config-page-auth-dialog-area.blur-enabled { + height: 100%; + top: 0; + background-color: rgba(255, 255, 255, 0.15); + backdrop-filter: blur(5px); + filter: blur(0.1px); + /* ↑ 似乎会导致性能问题 */ +} + +.acp-ada-hidden { + opacity: 0; +} + +.acp-ada-hidden.blur-enabled { + backdrop-filter: blur(0.1px); + filter: unset; +} + +.aura-config-page-auth-dialog { + height: 40%; + width: 100%; + background-color: rgba(255, 255, 255, 0.625); + display: flex; + flex-direction: column; + text-align: center; + align-items: center; + padding-top: 2rem; + padding-bottom: 2rem; +} + +.acp-auth-dialog-title { + font-size: x-large; + margin-bottom: 1.5rem; +} + +#acp-auth-user-input { + max-width: 50%; + /* background-color: rgba(255, 255, 255, 0.5); */ + border-radius: 35px; + margin-bottom: 2rem; +} + +#acp-auth-user-input.invalid { + animation: invalidShake 0.6s linear; +} + +.acp-auth-confirm-btn { + background-color: transparent; + border-radius: 35px; + border: 1px solid rgba(0, 0, 0, 0.3); + padding: 0.5rem; +} + +.acp-auth-confirm-btn .layui-icon { + font-size: 24px; + margin-left: 2px; +} + /* Toast */ .aura-config-page-toast-area { diff --git a/src/aura/ui/pages/config/config.html b/src/aura/ui/pages/config/config.html index 42d7c99..e9f06bb 100755 --- a/src/aura/ui/pages/config/config.html +++ b/src/aura/ui/pages/config/config.html @@ -80,8 +80,10 @@
+ +
@@ -114,6 +116,30 @@
+ +
{ + const showFailedAnimation = async (el) => { + el.classList.remove("invalid"); + await window.__HUGO_AURA_GLOBAL__.utils.sleep(50); + el.classList.add("invalid"); // Custom Anim + el.classList.add("is-invalid"); // Bootstrap + }; + + const inputEl = document.getElementById("acp-auth-user-input"); + const userPasswdInput = inputEl.value; + + if (!userPasswdInput || userPasswdInput.length < 8) { + showFailedAnimation(inputEl); + return false; + } + + const crypto = require("crypto"); + const encPasswd = crypto + .createHash("sha512") + .update(userPasswdInput + "EndlessX") + .digest("hex") + .toUpperCase(); + + if ( + encPasswd === + global.__HUGO_AURA_CONFIG__.auraSettings.settingsPasswordWithSalt + ) { + const acsDialogAreaEl = document.getElementsByClassName( + "aura-config-page-auth-dialog-area" + )[0]; + acsDialogAreaEl.classList.add("acp-ada-hidden"); + await window.__HUGO_AURA_GLOBAL__.utils.sleep(500); + acsDialogAreaEl.style = "display: none;"; + await window.__HUGO_AURA_GLOBAL__.utils.sleep(250); + global.__HUGO_AURA_UI_FUNCTIONS__.config.showSecondPhaseAnim(); + return true; + } else { + showFailedAnimation(inputEl); + return false; + } + }, }; (() => { @@ -167,23 +209,51 @@ global.__HUGO_AURA_UI_FUNCTIONS__.config = { }); }; - const showAnimation = async () => { - const defaultHeader = document.getElementsByClassName( - "index__header__16DmR2a5" - )[0]; + global.__HUGO_AURA_UI_FUNCTIONS__.config.showSecondPhaseAnim = () => { + showOperationsAnimation(); + }; + const handleSettingsAuth = async () => { + const isAuthEnabled = + global.__HUGO_AURA_CONFIG__.auraSettings.settingsPasswordEnabled; + + if (!isAuthEnabled) { + showOperationsAnimation(); + } else { + await window.__HUGO_AURA_GLOBAL__.utils.sleep(50); + const acsDialogAreaEl = document.getElementsByClassName( + "aura-config-page-auth-dialog-area" + )[0]; + acsDialogAreaEl.style = ""; + if ( + global.__HUGO_AURA_CONFIG__.auraSettings.appearance + .enablePasswdDialogBlur + ) { + acsDialogAreaEl.classList.add("blur-enabled"); + } + await window.__HUGO_AURA_GLOBAL__.utils.sleep(500); + acsDialogAreaEl.classList.remove("acp-ada-hidden"); + } + }; + + const showAnimation = async () => { const auraConfigPageRoot = document.getElementsByClassName( "aura-config-page-root" )[0]; await window.__HUGO_AURA_GLOBAL__.utils.sleep(200); auraConfigPageRoot.className = "aura-config-page-root"; + const defaultHeader = document.getElementsByClassName( + "index__header__16DmR2a5" + )[0]; + await window.__HUGO_AURA_GLOBAL__.utils.sleep(500); defaultHeader.style = "display: none;"; showVersionContainerAnimation(); showHeaderAnimation(); await window.__HUGO_AURA_GLOBAL__.utils.sleep(500); - showOperationsAnimation(); + + await handleSettingsAuth(); }; const onMounted = () => { diff --git a/src/aura/ui/pages/configSubPages/disableLimitations/disableLimitations.html b/src/aura/ui/pages/configSubPages/disableLimitations/disableLimitations.html index 9455ab8..f1f771a 100755 --- a/src/aura/ui/pages/configSubPages/disableLimitations/disableLimitations.html +++ b/src/aura/ui/pages/configSubPages/disableLimitations/disableLimitations.html @@ -6,6 +6,20 @@ +