Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added encrypt plugin #75

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 { 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 @@
{ 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 @@
{ 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 @@
},
},
},
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
Loading