Compare commits
No commits in common. "93fb60724ca6ea6bc455e0fd49ed016aade4a04c" and "a2d594b393982b2506f292e314c3451441b82b06" have entirely different histories.
93fb60724c
...
a2d594b393
5 changed files with 0 additions and 211 deletions
|
@ -1,131 +0,0 @@
|
||||||
---
|
|
||||||
import { encrypt } from '../../plugins/encrypt';
|
|
||||||
import {siteConfig} from "../../config";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
password?: string;
|
|
||||||
pwEnv?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { password: propPassword, pwEnv } = Astro.props;
|
|
||||||
// Get password from props, environment variable, or site config
|
|
||||||
const password = (pwEnv ? import.meta.env[pwEnv] : propPassword) || siteConfig.contentPassword || import.meta.env.CONTENT_PASSWORD || Math.random().toString();
|
|
||||||
|
|
||||||
// Get the slot content
|
|
||||||
const content = await Astro.slots.render('default');
|
|
||||||
|
|
||||||
// Encrypt content at build time
|
|
||||||
const { encryptedData, iv } = encrypt(content, password);
|
|
||||||
---
|
|
||||||
|
|
||||||
<div class="encrypted-content" data-encrypted={encryptedData} data-iv={iv}>
|
|
||||||
<div class="password-form">
|
|
||||||
<p>This content is protected. Enter the password to view it:</p>
|
|
||||||
<form>
|
|
||||||
<input type="password" class="decrypt-password" title="password" />
|
|
||||||
<button class="decrypt-button">Decrypt</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="content-container hidden"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
div.encrypted-content {
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
}
|
|
||||||
div.password-form {
|
|
||||||
margin: 0.5rem auto;
|
|
||||||
}
|
|
||||||
input[type="password"] {
|
|
||||||
background: var(--accent-color);
|
|
||||||
border: none;
|
|
||||||
padding: 0.25rem;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
background-color: var(--accent-color);
|
|
||||||
border: none;
|
|
||||||
padding: 0.25rem;
|
|
||||||
}
|
|
||||||
div.hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// Client-side decryption logic
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
const containers = document.querySelectorAll('.encrypted-content');
|
|
||||||
|
|
||||||
containers.forEach(container => {
|
|
||||||
const encryptedData = container.getAttribute('data-encrypted');
|
|
||||||
const iv = container.getAttribute('data-iv');
|
|
||||||
const passwordInput = container.querySelector('.decrypt-password');
|
|
||||||
const decryptButton = container.querySelector('.decrypt-button');
|
|
||||||
const contentContainer = container.querySelector('.content-container');
|
|
||||||
const passwordForm = container.querySelector('.password-form');
|
|
||||||
|
|
||||||
decryptButton.addEventListener('click', async () => {
|
|
||||||
try {
|
|
||||||
const password = passwordInput.value;
|
|
||||||
if (!password) return;
|
|
||||||
|
|
||||||
const content = await decrypt(encryptedData, iv, password);
|
|
||||||
contentContainer.innerHTML = content;
|
|
||||||
contentContainer.classList.remove('hidden');
|
|
||||||
passwordForm.classList.add('hidden');
|
|
||||||
} catch (error) {
|
|
||||||
alert('Incorrect password');
|
|
||||||
console.error('Decryption failed:', error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Allow pressing Enter to decrypt
|
|
||||||
passwordInput.addEventListener('keydown', (e) => {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
decryptButton.click();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
async function decrypt(encryptedData, iv, password) {
|
|
||||||
// Convert base64 to array buffer
|
|
||||||
const encryptedBytes = Uint8Array.from(atob(encryptedData), c => c.charCodeAt(0));
|
|
||||||
const ivBytes = Uint8Array.from(atob(iv), c => c.charCodeAt(0));
|
|
||||||
|
|
||||||
// Derive key from password
|
|
||||||
const encoder = new TextEncoder();
|
|
||||||
const passwordBytes = encoder.encode(password);
|
|
||||||
const keyMaterial = await window.crypto.subtle.importKey(
|
|
||||||
'raw',
|
|
||||||
passwordBytes,
|
|
||||||
{ name: 'PBKDF2' },
|
|
||||||
false,
|
|
||||||
['deriveBits', 'deriveKey']
|
|
||||||
);
|
|
||||||
|
|
||||||
const key = await window.crypto.subtle.deriveKey(
|
|
||||||
{
|
|
||||||
name: 'PBKDF2',
|
|
||||||
salt: ivBytes,
|
|
||||||
iterations: 100000,
|
|
||||||
hash: 'SHA-256'
|
|
||||||
},
|
|
||||||
keyMaterial,
|
|
||||||
{ name: 'AES-GCM', length: 256 },
|
|
||||||
false,
|
|
||||||
['decrypt']
|
|
||||||
);
|
|
||||||
|
|
||||||
// Decrypt
|
|
||||||
const decryptedBytes = await window.crypto.subtle.decrypt(
|
|
||||||
{ name: 'AES-GCM', iv: ivBytes },
|
|
||||||
key,
|
|
||||||
encryptedBytes
|
|
||||||
);
|
|
||||||
|
|
||||||
// Convert array buffer to text
|
|
||||||
const decoder = new TextDecoder();
|
|
||||||
return decoder.decode(decryptedBytes);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -1,6 +0,0 @@
|
||||||
---
|
|
||||||
const { text, ruby } = Astro.props;
|
|
||||||
---
|
|
||||||
<ruby>
|
|
||||||
{text} <rp>(</rp><rt>{ruby}</rt><rp>)</rp>
|
|
||||||
</ruby>
|
|
|
@ -28,10 +28,6 @@ export const siteConfig = {
|
||||||
searchEngine: 'bing', // 'google', 'duckduckgo', 'bing'(broken until M1cr0$0ft get support for it), defaults to 'google'
|
searchEngine: 'bing', // 'google', 'duckduckgo', 'bing'(broken until M1cr0$0ft get support for it), defaults to 'google'
|
||||||
// content
|
// content
|
||||||
displayAvatar: true, // display author avatar in the article list and info line of article page
|
displayAvatar: true, // display author avatar in the article list and info line of article page
|
||||||
// encryption
|
|
||||||
// the global password to encrypt/decrypt the content, if set, all <ProtectedContent/> without specifying a password will be encrypted with this password
|
|
||||||
// you can use a different environment variable to set the password.
|
|
||||||
contentPassword: import.meta.env.CONTENT_PASSWORD,
|
|
||||||
// footer
|
// footer
|
||||||
// yes you can write html safely here
|
// yes you can write html safely here
|
||||||
customFooter: '<i>I have no mouth, and I must SCREAM</i>',
|
customFooter: '<i>I have no mouth, and I must SCREAM</i>',
|
||||||
|
|
|
@ -7,8 +7,6 @@ tags: ["markdown", "css", "html", "sample"]
|
||||||
import Callout from '/src/components/shortcodes/Callout.astro';
|
import Callout from '/src/components/shortcodes/Callout.astro';
|
||||||
import LinkCard from '/src/components/shortcodes/LinkCard.astro';
|
import LinkCard from '/src/components/shortcodes/LinkCard.astro';
|
||||||
import Spoiler from '/src/components/shortcodes/Spoiler.astro';
|
import Spoiler from '/src/components/shortcodes/Spoiler.astro';
|
||||||
import Ruby from '/src/components/shortcodes/Ruby.astro';
|
|
||||||
import ProtectedContent from '/src/components/shortcodes/ProtectedContent.astro';
|
|
||||||
|
|
||||||
This article offers a sample of basic and extended Markdown formatting that can be used, also it shows how some basic HTML elements are decorated.
|
This article offers a sample of basic and extended Markdown formatting that can be used, also it shows how some basic HTML elements are decorated.
|
||||||
## Markdown in Astro
|
## Markdown in Astro
|
||||||
|
@ -54,17 +52,6 @@ You can also display a 'tip' for the reader when hovered:
|
||||||
</Spoiler>
|
</Spoiler>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Ruby
|
|
||||||
The `Ruby` component allows you to quickly add ruby annotations to your text, which is useful for providing pronunciation or additional information about certain words, especially in East Asian languages.
|
|
||||||
|
|
||||||
<Ruby text="汉" ruby="han" />
|
|
||||||
<Ruby text="字" ruby="zi" />
|
|
||||||
|
|
||||||
```mdx
|
|
||||||
<Ruby text="汉" ruby="han" />
|
|
||||||
<Ruby text="字" ruby="zi" />
|
|
||||||
```
|
|
||||||
|
|
||||||
### Callouts
|
### Callouts
|
||||||
You can use callouts to highlight important information or warnings in your content. Callouts are styled boxes that draw attention to specific parts of the text.
|
You can use callouts to highlight important information or warnings in your content. Callouts are styled boxes that draw attention to specific parts of the text.
|
||||||
<Callout icon='💁' type="info">
|
<Callout icon='💁' type="info">
|
||||||
|
@ -101,35 +88,6 @@ You can use the `LinkCard` component to create cards that link to external resou
|
||||||
Or to customize the card further with a title and description:
|
Or to customize the card further with a title and description:
|
||||||
<LinkCard url="https://www.bilibili.com/video/BV1PC4y1L7mq/" title="Don't check the description" description="Don't click on the link" />
|
<LinkCard url="https://www.bilibili.com/video/BV1PC4y1L7mq/" title="Don't check the description" description="Don't click on the link" />
|
||||||
|
|
||||||
### Protected Content
|
|
||||||
You can use the `ProtectedContent` component to protect certain parts of your content with a password. This is useful for sharing exclusive content or information that should only be accessible to certain users.
|
|
||||||
<Callout type="warning">
|
|
||||||
About security:
|
|
||||||
|
|
||||||
Although the encrypt process is happened at build and encrypted content is not visible in the rendered HTML. But it's still not 100% secure, as the password can be stored in the source code of a document (which could be public). Use it only for not really sensitive content.
|
|
||||||
Using a environment variable to store the password is recommended.
|
|
||||||
</Callout>
|
|
||||||
|
|
||||||
<ProtectedContent password="42">
|
|
||||||
Yes, what you input is the ultimate answer to *life*, *the universe*, and **everything**.
|
|
||||||
</ProtectedContent>
|
|
||||||
|
|
||||||
```mdx
|
|
||||||
<ProtectedContent password="42">
|
|
||||||
Yes, what you input is the ultimate answer to *life*, *the universe*, and **everything**.
|
|
||||||
</ProtectedContent>
|
|
||||||
```
|
|
||||||
|
|
||||||
<ProtectedContent>
|
|
||||||
And this will be encrypted with a global password, which is set in the environment variable `CONTENT_PASSWORD` or defined by `config.ts`. (config.ts takes precedence over the environment variable)
|
|
||||||
</ProtectedContent>
|
|
||||||
|
|
||||||
```mdx
|
|
||||||
<ProtectedContent>
|
|
||||||
And this will be encrypted with a global password, which is set in the environment variable `CONTENT_PASSWORD` or defined by `config.ts`. (config.ts takes precedence over the environment variable)
|
|
||||||
</ProtectedContent>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Headings
|
## Headings
|
||||||
|
|
||||||
The following HTML `<h1>`—`<h6>` elements represent six levels of section headings. `<h1>` is the highest section level while `<h6>` is the lowest.
|
The following HTML `<h1>`—`<h6>` elements represent six levels of section headings. `<h1>` is the highest section level while `<h6>` is the lowest.
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
import crypto from 'crypto';
|
|
||||||
|
|
||||||
export function encrypt(content: string, password: string) {
|
|
||||||
// Generate a random initialization vector
|
|
||||||
const iv = crypto.randomBytes(16);
|
|
||||||
|
|
||||||
// Derive key from password using PBKDF2
|
|
||||||
const key = crypto.pbkdf2Sync(password, iv, 100000, 32, 'sha256');
|
|
||||||
|
|
||||||
// Create cipher
|
|
||||||
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
|
|
||||||
|
|
||||||
// Encrypt content
|
|
||||||
let encryptedData = cipher.update(content, 'utf8', 'base64');
|
|
||||||
encryptedData += cipher.final('base64');
|
|
||||||
|
|
||||||
// Get auth tag and append to encrypted data
|
|
||||||
const authTag = cipher.getAuthTag();
|
|
||||||
const encryptedBuffer = Buffer.concat([
|
|
||||||
Buffer.from(encryptedData, 'base64'),
|
|
||||||
authTag
|
|
||||||
]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
encryptedData: encryptedBuffer.toString('base64'),
|
|
||||||
iv: iv.toString('base64')
|
|
||||||
};
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue