diff --git a/theme/src/node/prepare/prepareEncrypt.ts b/theme/src/node/prepare/prepareEncrypt.ts new file mode 100644 index 00000000..05f04df4 --- /dev/null +++ b/theme/src/node/prepare/prepareEncrypt.ts @@ -0,0 +1,76 @@ +import { genSaltSync, hashSync } from 'bcrypt-ts' +import type { App } from 'vuepress' +import { isNumber, isString, random, toArray } from '@pengzhanbo/utils' +import type { Page } from 'vuepress/core' +import type { PlumeThemeEncrypt, PlumeThemePageData } from '../../shared/index.js' +import { hash, resolveContent, writeTemp } from '../utils/index.js' + +export type EncryptConfig = readonly [ + boolean, // global + string, // separator + string, // admin + string[], // keys + Record, // rules +] + +const isStringLike = (value: unknown): boolean => isString(value) || isNumber(value) +const separator = ':' +let contentHash = '' + +export async function prepareEncrypt(app: App, encrypt?: PlumeThemeEncrypt) { + const currentHash = encrypt ? hash(JSON.stringify(encrypt)) : '' + + if (!contentHash || contentHash !== currentHash) { + contentHash = currentHash + const content = resolveContent(app, { + name: 'encrypt', + content: resolveEncrypt(encrypt), + }) + await writeTemp(app, 'internal/encrypt.js', content) + } +} + +function resolveEncrypt(encrypt?: PlumeThemeEncrypt): EncryptConfig { + const salt = () => genSaltSync(random(8, 16)) + const admin = encrypt?.admin + ? toArray(encrypt.admin) + .filter(isStringLike) + .map(item => hashSync(String(item), salt())) + .join(separator) + : '' + + const rules: Record = {} + const keys = Object.keys(encrypt?.rules ?? {}) + + if (encrypt?.rules) { + Object.keys(encrypt.rules).forEach((key) => { + const index = keys.indexOf(key) + + rules[String(index)] = toArray(encrypt.rules![key]) + .filter(isStringLike) + .map(item => hashSync(String(item), salt())) + .join(separator) + }) + } + + return [encrypt?.global ?? false, separator, admin, keys, rules] +} + +export function isEncryptPage(page: Page, encrypt?: PlumeThemeEncrypt) { + if (!encrypt) + return false + + const rules = encrypt.rules ?? {} + + return Object.keys(rules).some((match) => { + const relativePath = page.data.filePathRelative || '' + if (match[0] === '^') { + const regex = new RegExp(match) + return regex.test(page.path) || (relativePath && regex.test(relativePath)) + } + if (match.endsWith('.md')) + return relativePath && relativePath.endsWith(match) + + return page.path.startsWith(match) || relativePath.startsWith(match) + }) +}