From b4494595d6c8568b8019fccc7040fcf934965059 Mon Sep 17 00:00:00 2001 From: Yigit Colakoglu Date: Tue, 21 Oct 2025 19:25:10 +0200 Subject: [PATCH] Address comments from Copilot --- docs/plugins/Encrypt.md | 8 ++-- quartz/components/scripts/encrypt.inline.ts | 2 +- quartz/components/scripts/search.inline.ts | 3 +- quartz/plugins/transformers/description.ts | 2 +- quartz/plugins/transformers/encrypt.ts | 19 ++++++---- quartz/util/encryption.ts | 42 ++++----------------- 6 files changed, 27 insertions(+), 49 deletions(-) diff --git a/docs/plugins/Encrypt.md b/docs/plugins/Encrypt.md index cf966ae0f..276a588c1 100644 --- a/docs/plugins/Encrypt.md +++ b/docs/plugins/Encrypt.md @@ -47,10 +47,10 @@ Plugin.Encrypt({ - `algorithm`: Encryption algorithm to use - `"aes-256-cbc"` (default): AES-256 in CBC mode - `"aes-256-gcm"`: AES-256 in GCM mode (authenticated encryption) - - `"aes-256-ecb"`: AES-256 in ECB mode (not recommended) -- `ttl`: Time-to-live for cached passwords in seconds (default: 7 days) -- `message`: Default message shown on encrypted pages -- `encryptedFolders`: Configure folder-level encryption + - Key length is automatically inferred from the algorithm (e.g., 256-bit = 32 bytes) +- `encryptedFolders`: Object mapping folder paths to passwords or configuration objects for folder-level encryption +- `ttl`: Time-to-live for cached passwords in seconds (default: 604800 = 7 days, set to 0 for session-only) +- `message`: Message to be displayed in the decryption page ## How Configuration Works diff --git a/quartz/components/scripts/encrypt.inline.ts b/quartz/components/scripts/encrypt.inline.ts index aec95793e..c61f178f0 100644 --- a/quartz/components/scripts/encrypt.inline.ts +++ b/quartz/components/scripts/encrypt.inline.ts @@ -98,6 +98,7 @@ const decryptWithPassword = async ( if (showError) throw new Error("decryption-failed") return false } catch (decryptError) { + console.error("Decryption failed:", decryptError) if (showError) showLoading(container, false) if (showError) throw new Error("decryption-failed") return false @@ -136,7 +137,6 @@ const decryptWithPassword = async ( } function updateTitle(container: HTMLElement | null) { - console.log(container) if (container) { const span = container.querySelector(".article-title-icon") as HTMLElement if (span) { diff --git a/quartz/components/scripts/search.inline.ts b/quartz/components/scripts/search.inline.ts index 38f36de32..c743f9bf2 100644 --- a/quartz/components/scripts/search.inline.ts +++ b/quartz/components/scripts/search.inline.ts @@ -44,6 +44,7 @@ const fetchContentCache: Map = new Map() const contextWindowWords = 30 const numSearchResults = 8 const numTagResults = 5 +const RENDER_DELAY_MS = 100 const tokenizeTerm = (term: string) => { const tokens = term.split(/\s+/).filter((t) => t.trim() !== "") @@ -395,7 +396,7 @@ async function setupSearch(searchElement: Element, currentSlug: FullSlug, data: (a, b) => b.innerHTML.length - a.innerHTML.length, ) highlights[0]?.scrollIntoView({ block: "start" }) - await new Promise((resolve) => setTimeout(resolve, 100)) + await new Promise((resolve) => setTimeout(resolve, RENDER_DELAY_MS)) dispatchRenderEvent(previewInner) } diff --git a/quartz/plugins/transformers/description.ts b/quartz/plugins/transformers/description.ts index 9036436b9..cf7a20360 100644 --- a/quartz/plugins/transformers/description.ts +++ b/quartz/plugins/transformers/description.ts @@ -29,7 +29,7 @@ export const Description: QuartzTransformerPlugin> = (userOpts) return [ () => { return async (tree: HTMLRoot, file) => { - if (file.data?.encrypted && file.data.encryptionConfig) { + if (file.data.encryptionConfig) { file.data.description = file.data.encryptionConfig.message || i18n(ctx.cfg.configuration.locale).components.encryption.encryptedDescription diff --git a/quartz/plugins/transformers/encrypt.ts b/quartz/plugins/transformers/encrypt.ts index 5470f8325..a3541ecb1 100644 --- a/quartz/plugins/transformers/encrypt.ts +++ b/quartz/plugins/transformers/encrypt.ts @@ -143,16 +143,22 @@ function getConfigurationForPath( /** * Validates the plugin configuration */ -function validateConfig(config: PluginConfig): void { +function validateConfig(config: EncryptionConfig, file: VFile | null = null): void { + let suffixedPath = "" + + if (file && file.data && file.data.relativePath) { + suffixedPath = `(in file: ${file.data.relativePath})` + } + if (!SUPPORTED_ALGORITHMS.includes(config.algorithm)) { throw new Error( `[EncryptPlugin] Unsupported encryption algorithm: ${config.algorithm}. ` + - `Supported algorithms: ${SUPPORTED_ALGORITHMS.join(", ")}`, + `Supported algorithms: ${SUPPORTED_ALGORITHMS.join(", ")} ${suffixedPath}`, ) } - if (config.ttl <= 0) { - throw new Error(`[EncryptPlugin] TTL must be a positive number`) + if (config.ttl < 0) { + throw new Error(`[EncryptPlugin] TTL cannot be negative. ${suffixedPath}`) } } @@ -168,9 +174,6 @@ export const Encrypt: QuartzTransformerPlugin = (userOpts) => { encryptedFolders: userOpts?.encryptedFolders ?? {}, } - // Validate configuration - validateConfig(pluginConfig) - // Pre-process folder configurations for efficient lookup const folderConfigs = createFolderConfigHierarchy(pluginConfig.encryptedFolders, pluginConfig) @@ -241,6 +244,8 @@ export const Encrypt: QuartzTransformerPlugin = (userOpts) => { if (!config) { return } + // Validate configuration + validateConfig(config, file) file.data.encryptionConfig = config file.data.hash = await hashString(config.password) diff --git a/quartz/util/encryption.ts b/quartz/util/encryption.ts index 90bb2c093..c09d06210 100644 --- a/quartz/util/encryption.ts +++ b/quartz/util/encryption.ts @@ -1,7 +1,4 @@ -// ============================================================================= -// TYPES AND INTERFACES -// ============================================================================= -export const SUPPORTED_ALGORITHMS = ["aes-256-cbc", "aes-256-gcm", "aes-256-ecb"] as const +export const SUPPORTED_ALGORITHMS = ["aes-256-cbc", "aes-256-gcm"] as const export type SupportedEncryptionAlgorithm = (typeof SUPPORTED_ALGORITHMS)[number] @@ -88,7 +85,7 @@ export function base64Encode(data: string): string { return Buffer.from(data).toString("base64") } else { // Browser environment - return btoa(unescape(encodeURIComponent(data))) + return btoa(encodeURIComponent(data)) } } @@ -98,16 +95,17 @@ export function base64Decode(data: string): string { return Buffer.from(data, "base64").toString() } else { // Browser environment - return decodeURIComponent(escape(atob(data))) + return decodeURIComponent(atob(data)) } } // Utility functions for array buffer conversions export function hexToArrayBuffer(hex: string): ArrayBuffer { if (!hex) return new ArrayBuffer(0) + const bytes = new Uint8Array(hex.length / 2) for (let i = 0; i < bytes.length; i++) { - bytes[i] = parseInt(hex.substr(i * 2, 2), 16) + bytes[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16) } return bytes.buffer } @@ -209,8 +207,7 @@ export async function encryptContent( } // Generate random salt for encryption - const initializationVector = - algorithm === "aes-256-ecb" ? new Uint8Array(16) : crypto.getRandomValues(new Uint8Array(16)) // Zero IV for ECB simulation + const initializationVector = crypto.getRandomValues(new Uint8Array(16)) // Create encryption hash and derive key const encryptionHashData = await hashString(password) @@ -251,16 +248,6 @@ export async function encryptContent( key, contentBuffer, ) - } else if (algorithm === "aes-256-ecb") { - // ECB simulation using CBC with zero IV - encryptedBuffer = await crypto.subtle.encrypt( - { - name: "AES-CBC", - iv: initializationVector, // Zero IV for ECB simulation - }, - key, - contentBuffer, - ) } else { throw new Error("Unsupported algorithm: " + algorithm) } @@ -274,10 +261,7 @@ export async function encryptContent( const result: EncryptionResult = { encryptedContent: arrayBufferToHex(encryptedBuffer), encryptionSalt: encryptionHashData.salt, - } - - if (algorithm !== "aes-256-ecb") { - result.iv = arrayBufferToHex(initializationVector.buffer) + iv: arrayBufferToHex(initializationVector.buffer as ArrayBuffer), } if (authTag) { @@ -339,18 +323,6 @@ export async function decryptContent( key, ciphertext, ) - } else if (config.algorithm === "aes-256-ecb") { - // ECB simulation using CBC with zero IV - const zeroInitializationVector = new ArrayBuffer(16) - - decryptedBuffer = await crypto.subtle.decrypt( - { - name: "AES-CBC", - iv: zeroInitializationVector, - }, - key, - ciphertext, - ) } else { throw new Error("Unsupported algorithm: " + config.algorithm) }