@quartz-community/encrypted-pages 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +15 -0
- package/dist/index.js +64 -32
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 43a26d8: Encrypts the pages that page types render from files (quartz-community/bases-page#11), through Quartz 5.1's `virtualPageHtmlPlugins`: a drawing stored as Markdown with a `password` in its frontmatter is encrypted like a note, and a `.base`, `.canvas` or `.excalidraw` page, which has no frontmatter, is encrypted with the password of the encrypted published note that links to it (the first by slug when several do). Such pages are also added to the shadow content index when unlisted. Before, these pages were always served in the clear.
|
|
8
|
+
|
|
9
|
+
## 1.0.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- d72090b: Releases are published from GitHub Actions with npm trusted publishing, so each version on npm now carries provenance linking it to the commit and workflow that built it. No changes to the package itself.
|
|
14
|
+
|
|
3
15
|
## 1.0.0
|
|
4
16
|
|
|
5
17
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -71,6 +71,21 @@ All options below are set at the single plugin-entry level and are shared betwee
|
|
|
71
71
|
| `unlisted` | `boolean` | Overrides `unlistWhenEncrypted`. `true` forces the page unlisted; `false` forces it listed even when `unlistWhenEncrypted` is also set. |
|
|
72
72
|
| `stealth` | `boolean` | When `true` and the page is encrypted, suppresses the page's entry in the shadow content index. The page stays hidden from every listing surface **even after successful client-side decryption**. Implies `unlisted: true` (overrides any explicit `unlisted: false`). No effect on non-encrypted pages. |
|
|
73
73
|
|
|
74
|
+
## Bases, canvases and drawings
|
|
75
|
+
|
|
76
|
+
Pages that page types render from files are encrypted too (Quartz 5.1 or later):
|
|
77
|
+
|
|
78
|
+
- A drawing stored as Markdown (`.excalidraw.md`) has frontmatter: a `password` there encrypts its
|
|
79
|
+
page, as for a note.
|
|
80
|
+
- A `.base`, `.canvas` or `.excalidraw` file has no frontmatter. Its page is encrypted with the
|
|
81
|
+
password of the encrypted published note that links to it (`[[Board.canvas]]`), so a board
|
|
82
|
+
linked from a protected note is protected with it. When several encrypted notes with different
|
|
83
|
+
passwords link to it, the first by slug wins. Linked only from unencrypted notes, it stays
|
|
84
|
+
unencrypted; with `explicit-publish`, a file that no published note links to is not published.
|
|
85
|
+
|
|
86
|
+
An embed in an encrypted note (`![[Board.canvas]]`) shows as a link to the embedded page after
|
|
87
|
+
unlocking the note: embeds are resolved when the page is rendered, after the note was encrypted.
|
|
88
|
+
|
|
74
89
|
## How `unlisted` works
|
|
75
90
|
|
|
76
91
|
When an encrypted page is marked `unlisted: true`:
|
package/dist/index.js
CHANGED
|
@@ -2254,50 +2254,82 @@ function decrypt(encryptedBase64, password, iterations) {
|
|
|
2254
2254
|
decipher.setAuthTag(authTag);
|
|
2255
2255
|
return decipher.update(ciphertext, void 0, "utf8") + decipher.final("utf8");
|
|
2256
2256
|
}
|
|
2257
|
+
var inheritedPasswords = /* @__PURE__ */ new WeakMap();
|
|
2258
|
+
function encryptTree(tree, file, password, options) {
|
|
2259
|
+
const frontmatter = file.data?.frontmatter ?? {};
|
|
2260
|
+
const html5 = toHtml(tree, { allowDangerousHtml: true });
|
|
2261
|
+
const encryptedData = encryptAesGcm(html5, password, options.iterations);
|
|
2262
|
+
const encryptedContainer = {
|
|
2263
|
+
type: "element",
|
|
2264
|
+
tagName: "div",
|
|
2265
|
+
properties: {
|
|
2266
|
+
className: ["encrypted-page", "popover-hint"],
|
|
2267
|
+
"data-encrypted": encryptedData,
|
|
2268
|
+
"data-iterations": String(options.iterations)
|
|
2269
|
+
},
|
|
2270
|
+
children: []
|
|
2271
|
+
};
|
|
2272
|
+
tree.children = [encryptedContainer];
|
|
2273
|
+
const data = file.data;
|
|
2274
|
+
data.encrypted = true;
|
|
2275
|
+
data.text = "";
|
|
2276
|
+
data.description = "";
|
|
2277
|
+
const frontmatterUnlisted = frontmatter.unlisted;
|
|
2278
|
+
if (typeof frontmatterUnlisted === "boolean") {
|
|
2279
|
+
data.unlisted = frontmatterUnlisted;
|
|
2280
|
+
} else if (options.unlistWhenEncrypted) {
|
|
2281
|
+
data.unlisted = true;
|
|
2282
|
+
}
|
|
2283
|
+
if (frontmatter.stealth === true) {
|
|
2284
|
+
data.stealth = true;
|
|
2285
|
+
data.unlisted = true;
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
function passwordIn(frontmatter, options) {
|
|
2289
|
+
const password = frontmatter?.[options.passwordField];
|
|
2290
|
+
return typeof password === "string" && password.length > 0 ? password : void 0;
|
|
2291
|
+
}
|
|
2257
2292
|
var rehypeEncryptedPages = (options) => {
|
|
2258
2293
|
return () => (tree, file) => {
|
|
2259
|
-
const
|
|
2260
|
-
|
|
2261
|
-
|
|
2294
|
+
const password = passwordIn(file.data?.frontmatter, options);
|
|
2295
|
+
if (password) encryptTree(tree, file, password, options);
|
|
2296
|
+
};
|
|
2297
|
+
};
|
|
2298
|
+
var rehypeEncryptedVirtualPages = (options, content) => {
|
|
2299
|
+
const passwordBySlug = /* @__PURE__ */ new Map();
|
|
2300
|
+
for (const [, file] of content) {
|
|
2301
|
+
const password = passwordIn(file.data?.frontmatter, options);
|
|
2302
|
+
if (password && typeof file.data?.slug === "string")
|
|
2303
|
+
passwordBySlug.set(file.data.slug, password);
|
|
2304
|
+
}
|
|
2305
|
+
return () => (tree, file) => {
|
|
2306
|
+
const own4 = passwordIn(file.data?.frontmatter, options);
|
|
2307
|
+
if (own4) {
|
|
2308
|
+
encryptTree(tree, file, own4, options);
|
|
2262
2309
|
return;
|
|
2263
2310
|
}
|
|
2264
|
-
const
|
|
2265
|
-
|
|
2266
|
-
const
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
"data-iterations": String(options.iterations)
|
|
2273
|
-
},
|
|
2274
|
-
children: []
|
|
2275
|
-
};
|
|
2276
|
-
tree.children = [encryptedContainer];
|
|
2277
|
-
const data = file.data;
|
|
2278
|
-
data.encrypted = true;
|
|
2279
|
-
data.text = "";
|
|
2280
|
-
data.description = "";
|
|
2281
|
-
const frontmatterUnlisted = frontmatter.unlisted;
|
|
2282
|
-
if (typeof frontmatterUnlisted === "boolean") {
|
|
2283
|
-
data.unlisted = frontmatterUnlisted;
|
|
2284
|
-
} else if (options.unlistWhenEncrypted) {
|
|
2285
|
-
data.unlisted = true;
|
|
2286
|
-
}
|
|
2287
|
-
if (frontmatter.stealth === true) {
|
|
2288
|
-
data.stealth = true;
|
|
2289
|
-
data.unlisted = true;
|
|
2290
|
-
}
|
|
2311
|
+
const relativePath = file.data?.relativePath;
|
|
2312
|
+
if (typeof relativePath !== "string" || relativePath.toLowerCase().endsWith(".md")) return;
|
|
2313
|
+
const referencedBy = file.data.referencedBy;
|
|
2314
|
+
if (!Array.isArray(referencedBy)) return;
|
|
2315
|
+
const inherited = [...referencedBy].filter((slug) => typeof slug === "string").sort().map((slug) => passwordBySlug.get(slug)).find((password) => password !== void 0);
|
|
2316
|
+
if (!inherited) return;
|
|
2317
|
+
encryptTree(tree, file, inherited, options);
|
|
2318
|
+
inheritedPasswords.set(file.data, inherited);
|
|
2291
2319
|
};
|
|
2292
2320
|
};
|
|
2293
2321
|
var EncryptedPages = (userOptions) => {
|
|
2294
2322
|
const options = { ...defaultOptions, ...userOptions };
|
|
2295
|
-
|
|
2323
|
+
const instance = {
|
|
2296
2324
|
name: "EncryptedPages",
|
|
2297
2325
|
htmlPlugins() {
|
|
2298
2326
|
return [rehypeEncryptedPages(options)];
|
|
2327
|
+
},
|
|
2328
|
+
virtualPageHtmlPlugins(_ctx, content) {
|
|
2329
|
+
return [rehypeEncryptedVirtualPages(options, content)];
|
|
2299
2330
|
}
|
|
2300
2331
|
};
|
|
2332
|
+
return instance;
|
|
2301
2333
|
};
|
|
2302
2334
|
|
|
2303
2335
|
// node_modules/@quartz-community/types/dist/index.js
|
|
@@ -2343,7 +2375,7 @@ var EncryptedContentIndex = (userOptions) => {
|
|
|
2343
2375
|
if (data.unlisted !== true) continue;
|
|
2344
2376
|
if (data.stealth === true) continue;
|
|
2345
2377
|
const frontmatter = data.frontmatter ?? {};
|
|
2346
|
-
const password = frontmatter[passwordField];
|
|
2378
|
+
const password = frontmatter[passwordField] ?? inheritedPasswords.get(file.data);
|
|
2347
2379
|
if (typeof password !== "string" || password.length === 0) continue;
|
|
2348
2380
|
const iterations = extractIterationsFromTree(tree);
|
|
2349
2381
|
const shadowEntry = buildShadowEntry(data);
|