Address comments from Copilot

This commit is contained in:
Yigit Colakoglu 2025-10-21 19:25:10 +02:00
parent 127e5c3c03
commit b4494595d6
6 changed files with 27 additions and 49 deletions

View File

@ -47,10 +47,10 @@ Plugin.Encrypt({
- `algorithm`: Encryption algorithm to use - `algorithm`: Encryption algorithm to use
- `"aes-256-cbc"` (default): AES-256 in CBC mode - `"aes-256-cbc"` (default): AES-256 in CBC mode
- `"aes-256-gcm"`: AES-256 in GCM mode (authenticated encryption) - `"aes-256-gcm"`: AES-256 in GCM mode (authenticated encryption)
- `"aes-256-ecb"`: AES-256 in ECB mode (not recommended) - Key length is automatically inferred from the algorithm (e.g., 256-bit = 32 bytes)
- `ttl`: Time-to-live for cached passwords in seconds (default: 7 days) - `encryptedFolders`: Object mapping folder paths to passwords or configuration objects for folder-level encryption
- `message`: Default message shown on encrypted pages - `ttl`: Time-to-live for cached passwords in seconds (default: 604800 = 7 days, set to 0 for session-only)
- `encryptedFolders`: Configure folder-level encryption - `message`: Message to be displayed in the decryption page
## How Configuration Works ## How Configuration Works

View File

@ -98,6 +98,7 @@ const decryptWithPassword = async (
if (showError) throw new Error("decryption-failed") if (showError) throw new Error("decryption-failed")
return false return false
} catch (decryptError) { } catch (decryptError) {
console.error("Decryption failed:", decryptError)
if (showError) showLoading(container, false) if (showError) showLoading(container, false)
if (showError) throw new Error("decryption-failed") if (showError) throw new Error("decryption-failed")
return false return false
@ -136,7 +137,6 @@ const decryptWithPassword = async (
} }
function updateTitle(container: HTMLElement | null) { function updateTitle(container: HTMLElement | null) {
console.log(container)
if (container) { if (container) {
const span = container.querySelector(".article-title-icon") as HTMLElement const span = container.querySelector(".article-title-icon") as HTMLElement
if (span) { if (span) {

View File

@ -44,6 +44,7 @@ const fetchContentCache: Map<FullSlug, Element[]> = new Map()
const contextWindowWords = 30 const contextWindowWords = 30
const numSearchResults = 8 const numSearchResults = 8
const numTagResults = 5 const numTagResults = 5
const RENDER_DELAY_MS = 100
const tokenizeTerm = (term: string) => { const tokenizeTerm = (term: string) => {
const tokens = term.split(/\s+/).filter((t) => t.trim() !== "") 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, (a, b) => b.innerHTML.length - a.innerHTML.length,
) )
highlights[0]?.scrollIntoView({ block: "start" }) highlights[0]?.scrollIntoView({ block: "start" })
await new Promise((resolve) => setTimeout(resolve, 100)) await new Promise((resolve) => setTimeout(resolve, RENDER_DELAY_MS))
dispatchRenderEvent(previewInner) dispatchRenderEvent(previewInner)
} }

View File

@ -29,7 +29,7 @@ export const Description: QuartzTransformerPlugin<Partial<Options>> = (userOpts)
return [ return [
() => { () => {
return async (tree: HTMLRoot, file) => { return async (tree: HTMLRoot, file) => {
if (file.data?.encrypted && file.data.encryptionConfig) { if (file.data.encryptionConfig) {
file.data.description = file.data.description =
file.data.encryptionConfig.message || file.data.encryptionConfig.message ||
i18n(ctx.cfg.configuration.locale).components.encryption.encryptedDescription i18n(ctx.cfg.configuration.locale).components.encryption.encryptedDescription

View File

@ -143,16 +143,22 @@ function getConfigurationForPath(
/** /**
* Validates the plugin configuration * 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)) { if (!SUPPORTED_ALGORITHMS.includes(config.algorithm)) {
throw new Error( throw new Error(
`[EncryptPlugin] Unsupported encryption algorithm: ${config.algorithm}. ` + `[EncryptPlugin] Unsupported encryption algorithm: ${config.algorithm}. ` +
`Supported algorithms: ${SUPPORTED_ALGORITHMS.join(", ")}`, `Supported algorithms: ${SUPPORTED_ALGORITHMS.join(", ")} ${suffixedPath}`,
) )
} }
if (config.ttl <= 0) { if (config.ttl < 0) {
throw new Error(`[EncryptPlugin] TTL must be a positive number`) throw new Error(`[EncryptPlugin] TTL cannot be negative. ${suffixedPath}`)
} }
} }
@ -168,9 +174,6 @@ export const Encrypt: QuartzTransformerPlugin<PluginOptions> = (userOpts) => {
encryptedFolders: userOpts?.encryptedFolders ?? {}, encryptedFolders: userOpts?.encryptedFolders ?? {},
} }
// Validate configuration
validateConfig(pluginConfig)
// Pre-process folder configurations for efficient lookup // Pre-process folder configurations for efficient lookup
const folderConfigs = createFolderConfigHierarchy(pluginConfig.encryptedFolders, pluginConfig) const folderConfigs = createFolderConfigHierarchy(pluginConfig.encryptedFolders, pluginConfig)
@ -241,6 +244,8 @@ export const Encrypt: QuartzTransformerPlugin<PluginOptions> = (userOpts) => {
if (!config) { if (!config) {
return return
} }
// Validate configuration
validateConfig(config, file)
file.data.encryptionConfig = config file.data.encryptionConfig = config
file.data.hash = await hashString(config.password) file.data.hash = await hashString(config.password)

View File

@ -1,7 +1,4 @@
// ============================================================================= export const SUPPORTED_ALGORITHMS = ["aes-256-cbc", "aes-256-gcm"] as const
// TYPES AND INTERFACES
// =============================================================================
export const SUPPORTED_ALGORITHMS = ["aes-256-cbc", "aes-256-gcm", "aes-256-ecb"] as const
export type SupportedEncryptionAlgorithm = (typeof SUPPORTED_ALGORITHMS)[number] export type SupportedEncryptionAlgorithm = (typeof SUPPORTED_ALGORITHMS)[number]
@ -88,7 +85,7 @@ export function base64Encode(data: string): string {
return Buffer.from(data).toString("base64") return Buffer.from(data).toString("base64")
} else { } else {
// Browser environment // 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() return Buffer.from(data, "base64").toString()
} else { } else {
// Browser environment // Browser environment
return decodeURIComponent(escape(atob(data))) return decodeURIComponent(atob(data))
} }
} }
// Utility functions for array buffer conversions // Utility functions for array buffer conversions
export function hexToArrayBuffer(hex: string): ArrayBuffer { export function hexToArrayBuffer(hex: string): ArrayBuffer {
if (!hex) return new ArrayBuffer(0) if (!hex) return new ArrayBuffer(0)
const bytes = new Uint8Array(hex.length / 2) const bytes = new Uint8Array(hex.length / 2)
for (let i = 0; i < bytes.length; i++) { 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 return bytes.buffer
} }
@ -209,8 +207,7 @@ export async function encryptContent(
} }
// Generate random salt for encryption // Generate random salt for encryption
const initializationVector = const initializationVector = crypto.getRandomValues(new Uint8Array(16))
algorithm === "aes-256-ecb" ? new Uint8Array(16) : crypto.getRandomValues(new Uint8Array(16)) // Zero IV for ECB simulation
// Create encryption hash and derive key // Create encryption hash and derive key
const encryptionHashData = await hashString(password) const encryptionHashData = await hashString(password)
@ -251,16 +248,6 @@ export async function encryptContent(
key, key,
contentBuffer, 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 { } else {
throw new Error("Unsupported algorithm: " + algorithm) throw new Error("Unsupported algorithm: " + algorithm)
} }
@ -274,10 +261,7 @@ export async function encryptContent(
const result: EncryptionResult = { const result: EncryptionResult = {
encryptedContent: arrayBufferToHex(encryptedBuffer), encryptedContent: arrayBufferToHex(encryptedBuffer),
encryptionSalt: encryptionHashData.salt, encryptionSalt: encryptionHashData.salt,
} iv: arrayBufferToHex(initializationVector.buffer as ArrayBuffer),
if (algorithm !== "aes-256-ecb") {
result.iv = arrayBufferToHex(initializationVector.buffer)
} }
if (authTag) { if (authTag) {
@ -339,18 +323,6 @@ export async function decryptContent(
key, key,
ciphertext, 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 { } else {
throw new Error("Unsupported algorithm: " + config.algorithm) throw new Error("Unsupported algorithm: " + config.algorithm)
} }