mirror of
https://github.com/pengzhanbo/vuepress-theme-plume.git
synced 2026-04-23 10:58:13 +08:00
208 lines
5.5 KiB
TypeScript
208 lines
5.5 KiB
TypeScript
import type { InjectionKey, Ref } from 'vue'
|
|
import type { EncryptDataRule } from './encrypt-data.js'
|
|
import { computedAsync, useSessionStorage } from '@vueuse/core'
|
|
import { bcryptVerify, md5 } from 'hash-wasm'
|
|
import { computed, inject, provide } from 'vue'
|
|
import { useRoute } from 'vuepress/client'
|
|
import { removeLeadingSlash } from 'vuepress/shared'
|
|
import { useData } from './data.js'
|
|
import { useEncryptData } from './encrypt-data.js'
|
|
|
|
export interface Encrypt {
|
|
hasPageEncrypt: Ref<boolean>
|
|
isGlobalDecrypted: Ref<boolean>
|
|
isPageDecrypted: Ref<boolean>
|
|
hashList: Ref<EncryptDataRule[]>
|
|
}
|
|
|
|
export const EncryptSymbol: InjectionKey<Encrypt> = Symbol(
|
|
__VUEPRESS_DEV__ ? 'Encrypt' : '',
|
|
)
|
|
|
|
const storage = useSessionStorage('2a0a3d6afb2fdf1f', () => {
|
|
if (__VUEPRESS_SSR__) {
|
|
return { g: '', p: [] as string[] }
|
|
}
|
|
return {
|
|
g: '',
|
|
p: [] as string[],
|
|
}
|
|
})
|
|
|
|
const compareCache = new Map<string, boolean>()
|
|
async function compareDecrypt(content: string, hash: string, separator = ':'): Promise<boolean> {
|
|
const key = [content, hash].join(separator)
|
|
if (compareCache.has(key))
|
|
return compareCache.get(key)!
|
|
|
|
try {
|
|
const result = await bcryptVerify({ password: content, hash })
|
|
compareCache.set(key, result)
|
|
return result
|
|
}
|
|
catch {
|
|
compareCache.set(key, false)
|
|
return false
|
|
}
|
|
}
|
|
|
|
const matchCache = new Map<string, RegExp>()
|
|
function createMatchRegex(match: string) {
|
|
if (matchCache.has(match))
|
|
return matchCache.get(match)!
|
|
|
|
const regex = new RegExp(match)
|
|
matchCache.set(match, regex)
|
|
return regex
|
|
}
|
|
|
|
function toMatch(match: string, pagePath: string, filePathRelative: string | null) {
|
|
const relativePath = filePathRelative || ''
|
|
if (match[0] === '^') {
|
|
const regex = createMatchRegex(match)
|
|
return regex.test(pagePath) || regex.test(relativePath)
|
|
}
|
|
if (match.endsWith('.md'))
|
|
return relativePath && relativePath.endsWith(match)
|
|
|
|
return pagePath.startsWith(match) || relativePath.startsWith(removeLeadingSlash(match))
|
|
}
|
|
|
|
export function setupEncrypt(): void {
|
|
const { page } = useData()
|
|
const route = useRoute()
|
|
const encrypt = useEncryptData()
|
|
|
|
const hasPageEncrypt = computed(() => {
|
|
const pagePath = route.path
|
|
const filePathRelative = page.value.filePathRelative
|
|
if (page.value._e)
|
|
return true
|
|
|
|
return encrypt.value.ruleList.length
|
|
? encrypt.value.matches.some(match => toMatch(match, pagePath, filePathRelative))
|
|
: false
|
|
})
|
|
|
|
const isGlobalDecrypted = computedAsync(async () => {
|
|
const hash = storage.value.g
|
|
if (!encrypt.value.global)
|
|
return true
|
|
|
|
for (const admin of encrypt.value.admins) {
|
|
if (hash && hash === await md5(admin))
|
|
return true
|
|
}
|
|
return false
|
|
}, !encrypt.value.global)
|
|
|
|
const hashList = computed(() => {
|
|
const pagePath = route.path
|
|
const filePathRelative = page.value.filePathRelative
|
|
const passwords = typeof page.value._e === 'string' ? page.value._e.split(':') : []
|
|
const pageRule: EncryptDataRule | undefined = passwords.length
|
|
? { key: pagePath.replace(/\//g, '').replace(/\.html$/, ''), match: pagePath, rules: passwords }
|
|
: undefined
|
|
const rules = encrypt.value.ruleList.length
|
|
? encrypt.value.ruleList
|
|
.filter(item => toMatch(item.match, pagePath, filePathRelative))
|
|
: []
|
|
|
|
return [pageRule, ...rules].filter(Boolean) as EncryptDataRule[]
|
|
})
|
|
|
|
const isPageDecrypted = computedAsync(async () => {
|
|
if (!hasPageEncrypt.value)
|
|
return true
|
|
|
|
const hash = storage.value.g
|
|
|
|
for (const admin of encrypt.value.admins) {
|
|
if (hash && hash === await md5(admin))
|
|
return true
|
|
}
|
|
|
|
for (const { key, rules } of hashList.value) {
|
|
const hash = storage.value.p[key]
|
|
for (const rule of rules) {
|
|
if (hash && hash === await md5(rule))
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}, !hasPageEncrypt.value)
|
|
|
|
provide(EncryptSymbol, {
|
|
hasPageEncrypt,
|
|
isGlobalDecrypted,
|
|
isPageDecrypted,
|
|
hashList,
|
|
})
|
|
}
|
|
|
|
export function useEncrypt(): Encrypt {
|
|
const result = inject(EncryptSymbol)
|
|
|
|
if (!result)
|
|
throw new Error('useEncrypt() is called without setup')
|
|
|
|
return result
|
|
}
|
|
|
|
export function useEncryptCompare(): {
|
|
compareGlobal: (password: string) => Promise<boolean>
|
|
comparePage: (password: string) => Promise<boolean>
|
|
} {
|
|
const encrypt = useEncryptData()
|
|
const { page } = useData()
|
|
const route = useRoute()
|
|
const { hashList } = useEncrypt()
|
|
|
|
async function compareGlobal(password: string): Promise<boolean> {
|
|
if (!password)
|
|
return false
|
|
|
|
for (const admin of encrypt.value.admins) {
|
|
if (await compareDecrypt(password, admin, encrypt.value.separator)) {
|
|
storage.value.g = await md5(admin)
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
async function comparePage(password: string): Promise<boolean> {
|
|
if (!password)
|
|
return false
|
|
|
|
const pagePath = route.path
|
|
const filePathRelative = page.value.filePathRelative
|
|
|
|
let decrypted = false
|
|
|
|
for (const { match, key, rules } of hashList.value) {
|
|
if (toMatch(match, pagePath, filePathRelative)) {
|
|
for (const rule of rules) {
|
|
if (await compareDecrypt(password, rule, encrypt.value.separator)) {
|
|
decrypted = true
|
|
storage.value.p[key] = await md5(rule)
|
|
break
|
|
}
|
|
}
|
|
if (decrypted)
|
|
break
|
|
}
|
|
}
|
|
|
|
if (!decrypted) {
|
|
decrypted = await compareGlobal(password)
|
|
}
|
|
|
|
return decrypted
|
|
}
|
|
|
|
return { compareGlobal, comparePage }
|
|
}
|