mirror of
https://github.com/jackyzha0/quartz.git
synced 2025-12-19 10:54:06 -06:00
Address comments from Copilot
This commit is contained in:
parent
127e5c3c03
commit
b4494595d6
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user