Skip to content

Commit

Permalink
feat: added encrypt plugin
Browse files Browse the repository at this point in the history
Signed-off-by: Neko Ayaka <neko@ayaka.moe>
  • Loading branch information
nekomeowww committed Mar 28, 2024
1 parent 47ab3a5 commit 8df6e1f
Show file tree
Hide file tree
Showing 12 changed files with 356 additions and 35 deletions.
46 changes: 46 additions & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import type { Options as ElementTransformOptions } from '@nolebase/markdown-it-e
import { ElementTransform } from '@nolebase/markdown-it-element-transform'
import { buildEndGenerateOpenGraphImages } from '@nolebase/vitepress-plugin-og-image'

import { rehype } from 'rehype'
import RehypeStringgify from 'rehype-stringify'
import RehypeRewrite from 'rehype-rewrite'

export const sidebars: Record<string, DefaultTheme.Sidebar> = {
'en': {
'/': [
Expand Down Expand Up @@ -60,6 +64,7 @@ export const sidebars: Record<string, DefaultTheme.Sidebar> = {
{ text: 'Changelog & File history', link: '/pages/en/integrations/vitepress-plugin-git-changelog/' },
{ text: 'Page properties', link: '/pages/en/integrations/vitepress-plugin-page-properties/' },
{ text: 'Previewing image (social media card) generation', link: '/pages/en/integrations/vitepress-plugin-og-image/' },
{ text: 'Encrypt', link: '/pages/en/integrations/vitepress-plugin-encrypt/' },
],
},
],
Expand Down Expand Up @@ -134,6 +139,7 @@ export const sidebars: Record<string, DefaultTheme.Sidebar> = {
{ text: '变更日志 及 文件历史', link: '/pages/zh-CN/integrations/vitepress-plugin-git-changelog/' },
{ text: '页面属性', link: '/pages/zh-CN/integrations/vitepress-plugin-page-properties/' },
{ text: '预览图片(社交媒体卡片)生成', link: '/pages/zh-CN/integrations/vitepress-plugin-og-image/' },
{ text: '保密', link: '/pages/zh-CN/integrations/vitepress-plugin-encrypt/' },
],
},
],
Expand Down Expand Up @@ -239,6 +245,46 @@ export default defineConfig({
},
},
},
transformHtml: async (code, id) => {
if (id.includes('vitepress-plugin-encrypt')) {
const rawHTML = ''

Check failure on line 250 in docs/.vitepress/config.ts

View workflow job for this annotation

GitHub Actions / Lint - 18.x

'rawHTML' is assigned a value but never used. Allowed unused vars must match /^_/u

Check failure on line 250 in docs/.vitepress/config.ts

View workflow job for this annotation

GitHub Actions / Lint - 20.x

'rawHTML' is assigned a value but never used. Allowed unused vars must match /^_/u

const processed = await rehype()
.data('settings', { fragment: true })
.use(RehypeRewrite, {
rewrite: (node) => {
if (node.type === 'element' && node.properties.id === 'vp-nolebase-encrypt-protected-content') {
node.children = [
{
type: 'element',
tagName: 'div',
properties: {
id: 'vp-nolebase-encrypt-protected-content-placeholder',
},
children: [
{
type: 'text',
value: 'This content is protected. Please input the password to view it.',
},
],
},
]
}
},
})
.use(RehypeStringgify)
.use(() => {
return (tree) => {

Check failure on line 277 in docs/.vitepress/config.ts

View workflow job for this annotation

GitHub Actions / Lint - 18.x

'tree' is defined but never used. Allowed unused args must match /^_/u

Check failure on line 277 in docs/.vitepress/config.ts

View workflow job for this annotation

GitHub Actions / Lint - 20.x

'tree' is defined but never used. Allowed unused args must match /^_/u
const scriptNode = {

Check failure on line 278 in docs/.vitepress/config.ts

View workflow job for this annotation

GitHub Actions / Lint - 18.x

'scriptNode' is assigned a value but never used. Allowed unused vars must match /^_/u

Check failure on line 278 in docs/.vitepress/config.ts

View workflow job for this annotation

GitHub Actions / Lint - 20.x

'scriptNode' is assigned a value but never used. Allowed unused vars must match /^_/u

}
}
})
.process(code)

return processed.toString()
}
},
markdown: {
config(md) {
md.use(MarkdownItFootnote)
Expand Down
126 changes: 126 additions & 0 deletions docs/.vitepress/theme/components/Protected.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { LazyHydrationWrapper } from 'vue3-lazy-hydration'
import { decryptText, encryptText } from '../composables/crypto'
const password = ref('')
const iv = ref('')
const errored = ref(false)
const rawContent = '<div>a</div>'
const encryptedHtml = ref('')
const decryptedHtml = ref('')
const trigger = ref(false)
onMounted(async () => {
try {
const key = 'password'
const encrypted = await encryptText(rawContent, key)
encryptedHtml.value = encrypted.encryptedText
iv.value = encrypted.ivBase64
}
catch (err) {
console.error('Failed to encrypt the content.')
}
})
async function onPasswordInput() {
try {
decryptedHtml.value = await decryptText(encryptedHtml.value, password.value, iv.value)
trigger.value = true
}
catch (err) {
console.error('Invalid password.')
errored.value = true
return
}
errored.value = false
}
</script>

<template>
<div id="vp-nolebase-protected">
<div>
<div relative>
<div flex="~ col" absolute left-0 top-0 z-10 h-full w-full items-center justify-center>
<div mb-4>
<p mt="0!" mb="4!" text-center>
<span font-semibold>You don't have permissions to access this page.</span>
</p>
<p my="0!" text-left>
<span>You could either</span>
<ul my="0!">
<li my="0!">
Request an access permission from the owner.
</li>
<li my="0!">
Prompt a valid password for it.
</li>
</ul>
</p>
</div>
<div w-full flex="~ col" items-center justify-center max-w="80">
<button
min-w-35 w-full rounded-lg
px-3 py-2
bg="zinc-700 hover:zinc-600 active:zinc-700 dark:zinc-200 dark:hover:zinc-300 dark:active:zinc-400"
text="zinc-100 dark:zinc-900 " font-semibold
transition="all ease" duration-750
>
Request Access
</button>
<span>or</span>
<form min-w-35 w-full flex="~ row" @submit.prevent="() => {}">
<input
v-model="password"
type="password"
mr-2
w-full rounded-lg
px-3 py-2
bg="$vp-c-bg"
text="$vp-c-text-1" font-semibold
transition="all ease" duration-750
:class="[
errored ? 'outline-offset-1 outline-2 outline-red-400' : 'outline-offset-1 outline-2 outline-zinc-100',
]"
placeholder="Enter the valid password..."
>
<button
rounded-lg
px-3 py-2
font-semibold
transition="all ease" duration-750
:class="[
password !== '' ? 'bg-zinc-700 hover:bg-zinc-600 active:bg-zinc-700 dark:bg-zinc-200 dark:hover:bg-zinc-300 dark:active:bg-zinc-400 text-zinc-100 dark:text-zinc-900' : 'bg-$vp-c-bg cursor-not-allowed! text-$vp-c-text-1',
]"
@click="onPasswordInput"
>
Unlock
</button>
</form>
</div>
</div>
<div blur-md space-y-5>
<div h="[1lh]" bg="[var(--vp-c-text-1)]" w-full rounded-lg opacity-5 />
<div h="[1lh]" bg="[var(--vp-c-text-1)]" w-full rounded-lg opacity-5 />
<div h="[1lh]" bg="[var(--vp-c-text-1)]" w-full rounded-lg opacity-5 />
<div h="[1lh]" bg="[var(--vp-c-text-1)]" w-full rounded-lg opacity-5 />
<div h="[1lh]" bg="[var(--vp-c-text-1)]" w-full rounded-lg opacity-5 />
<div h="[1lh]" bg="[var(--vp-c-text-1)]" w-full rounded-lg opacity-5 />
<div h="[1lh]" bg="[var(--vp-c-text-1)]" w-full rounded-lg opacity-5 />
<div h="[1lh]" bg="[var(--vp-c-text-1)]" w-full rounded-lg opacity-5 />
<div h="[1lh]" bg="[var(--vp-c-text-1)]" w-full rounded-lg opacity-5 />
<div h="[1lh]" bg="[var(--vp-c-text-1)]" w-full rounded-lg opacity-5 />
<div h="[1lh]" bg="[var(--vp-c-text-1)]" w-full rounded-lg opacity-5 />
</div>
</div>
<LazyHydrationWrapper :when-triggered="trigger">
<div id="vp-nolebase-protected-content">
<slot />
</div>
<div v-html="decryptedHtml" />
</LazyHydrationWrapper>
</div>
</div>
</template>
105 changes: 105 additions & 0 deletions docs/.vitepress/theme/composables/crypto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
function useCrypto() {
if (crypto)
return crypto

if (window.crypto)
return window.crypto

throw new Error('Crypto not supported')
}

export async function encryptText(plainText: string, plainTextKey: string) {
const crypto = useCrypto()

// Encode the key and hash it using SHA-256
const keyMaterial = await getKeyMaterial(plainTextKey)
const key = await crypto.subtle.importKey(
'raw',
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt'],
)

// Encode the text to be encrypted
const encoder = new TextEncoder()
const encodedText = encoder.encode(plainText)

// Generate an IV
const iv = window.crypto.getRandomValues(new Uint8Array(12))

// Encrypt the text
const encryptedData = await window.crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv,
},
key,
encodedText,
)

// Convert the encrypted data to Base64
const encryptedText = bufferToBase64(encryptedData)

// Convert the IV to Base64
const ivBase64 = bufferToBase64(iv)

return { encryptedText, ivBase64 }
}

export async function decryptText(encryptedTextBase64: string, plainTextKey: string, ivBase64: string) {
// Decode the Base64 encrypted text and IV
const encryptedData = base64ToBuffer(encryptedTextBase64)
const iv = base64ToBuffer(ivBase64)

// Encode the key and hash it using SHA-256
const keyMaterial = await getKeyMaterial(plainTextKey)

const key = await window.crypto.subtle.importKey(
'raw',
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['decrypt'],
)

// Decrypt the text
const decryptedData = await window.crypto.subtle.decrypt(
{
name: 'AES-GCM',
iv,
},
key,
encryptedData,
)

// Decode the decrypted data
const decoder = new TextDecoder()
return decoder.decode(decryptedData)
}

// Helper function to convert a Base64 string to an ArrayBuffer
function base64ToBuffer(base64: string): ArrayBuffer {
const binaryString = atob(base64)
const bytes = new Uint8Array(binaryString.length)
for (let i = 0; i < binaryString.length; i++)
bytes[i] = binaryString.charCodeAt(i)

return bytes.buffer
}

// Helper function to convert a plaintext string to a SHA-256 hash
async function getKeyMaterial(plainTextKey: string): Promise<ArrayBuffer> {
const crypto = useCrypto()

const encoder = new TextEncoder()
const keyData = encoder.encode(plainTextKey)
return crypto.subtle.digest('SHA-256', keyData)
}

// Helper function to convert an ArrayBuffer to a Base64 string
function bufferToBase64(buffer: ArrayBuffer): string {
const bytes = new Uint8Array(buffer)
const binary = bytes.reduce((acc, byte) => acc + String.fromCharCode(byte), '')
return btoa(binary)
}
2 changes: 2 additions & 0 deletions docs/.vitepress/theme/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import TwoslashFloatingVue from '@shikijs/vitepress-twoslash/client'
import { NuLazyTeleportRiveCanvas } from '@nolebase/ui'
import { NolebasePluginSet, defineThemeUnconfig } from '@nolebase/unconfig-vitepress'

import Protected from './components/Protected.vue'
import IntegrationCard from './components/IntegrationCard.vue'
import HomeContent from './components/HomeContent.vue'

Expand All @@ -28,6 +29,7 @@ export default defineThemeUnconfig({
app.component('IntegrationCard', IntegrationCard)
app.component('HomeContent', HomeContent)
app.use(TwoslashFloatingVue as Plugin)
app.component('Protected', Protected)
},
pluginSets: [
NolebasePluginSet({
Expand Down
7 changes: 6 additions & 1 deletion docs/pages/en/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ Nólëbase Integrations project provides a variety of integrations, plugins, com
</template>
</IntegrationCard>

<IntegrationCard type="vitepress" title="Encrypt" package="vitepress-plugin-encrypt">
<template v-slot:badge>
<Badge type="danger" text="Alpha" />
</template>
</IntegrationCard>

<IntegrationCard type="obsidian" title="UnoCSS" package="obsidian-plugin-unocss">
<template v-slot:badge>
<Badge type="warning" text="Beta" />
Expand All @@ -94,4 +100,3 @@ Nólëbase Integrations project provides a variety of integrations, plugins, com
</div>

</HomeContent>

8 changes: 8 additions & 0 deletions docs/pages/en/integrations/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ Nólëbase Integrations project provides a variety of integrations, plugins, com

<br />

<IntegrationCard type="vitepress" title="Encrypt" package="vitepress-plugin-encrypt">
<template v-slot:badge>
<Badge type="danger" text="Alpha" />
</template>
</IntegrationCard>

<br />

<IntegrationCard type="obsidian" title="UnoCSS" package="obsidian-plugin-unocss">
<template v-slot:badge>
<Badge type="warning" text="Beta" />
Expand Down
7 changes: 7 additions & 0 deletions docs/pages/en/integrations/vitepress-plugin-encrypt/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<Protected>

# Share

<IntegrationCard type="markdown-it" title="Elements Transformation" package="markdown-it-element-transform" />

</Protected>
6 changes: 6 additions & 0 deletions docs/pages/zh-CN/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ Nólëbase 集成项目提供多种不同的集成、插件、组件和库来方
</template>
</IntegrationCard>

<IntegrationCard type="vitepress" title="保密" package="vitepress-plugin-encrypt">
<template v-slot:badge>
<Badge type="danger" text="Alpha 测试" />
</template>
</IntegrationCard>

<IntegrationCard type="obsidian" title="UnoCSS" package="obsidian-plugin-unocss">
<template v-slot:badge>
<Badge type="warning" text="Beta 测试" />
Expand Down
8 changes: 8 additions & 0 deletions docs/pages/zh-CN/integrations/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ Nólëbase 集成项目提供多种不同的集成、插件、组件和库来方

<br />

<IntegrationCard type="vitepress" title="保密" package="vitepress-plugin-encrypt">
<template v-slot:badge>
<Badge type="danger" text="Alpha 测试" />
</template>
</IntegrationCard>

<br />

<IntegrationCard type="obsidian" title="UnoCSS" package="obsidian-plugin-unocss">
<template v-slot:badge>
<Badge type="warning" text="Beta 测试" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<Protected>

# 分享

<IntegrationCard type="markdown-it" title="元素变换" package="markdown-it-element-transform" />

</Protected>
Loading

0 comments on commit 8df6e1f

Please sign in to comment.