Compare commits

..

No commits in common. "93fb60724ca6ea6bc455e0fd49ed016aade4a04c" and "a2d594b393982b2506f292e314c3451441b82b06" have entirely different histories.

5 changed files with 0 additions and 211 deletions

View file

@ -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>

View file

@ -1,6 +0,0 @@
---
const { text, ruby } = Astro.props;
---
<ruby>
{text} <rp>(</rp><rt>{ruby}</rt><rp>)</rp>
</ruby>

View file

@ -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>',

View file

@ -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.

View file

@ -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')
};
}