Moved static files to static/ folder
This commit is contained in:
parent
0965ffea41
commit
d1f29825d3
5 changed files with 2 additions and 2 deletions
79
static/app.js
Normal file
79
static/app.js
Normal file
|
@ -0,0 +1,79 @@
|
|||
import config from './config.js';
|
||||
import { generateKey, encryptData, decryptData, exportKey, importKey } from './crypto.js';
|
||||
|
||||
const textarea = document.getElementById('secretInput');
|
||||
const shareButton = document.getElementById('shareButton');
|
||||
const spinner = document.getElementById('spinner');
|
||||
|
||||
async function handleShare() {
|
||||
if (!textarea.value.trim()) return;
|
||||
|
||||
try {
|
||||
spinner.classList.remove('hidden');
|
||||
|
||||
// Generate encryption key and encrypt data
|
||||
const key = await generateKey();
|
||||
const encryptedData = await encryptData(textarea.value, key);
|
||||
|
||||
// Send encrypted data to server
|
||||
const response = await fetch(`${config.API_BASE_URL}/secret`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ data: encryptedData })
|
||||
});
|
||||
|
||||
const { key: secretKey } = await response.json();
|
||||
const exportedKey = await exportKey(key);
|
||||
|
||||
// Create sharing URL
|
||||
const combinedData = `${secretKey}::${exportedKey}`;
|
||||
const encodedData = btoa(combinedData);
|
||||
const shareUrl = `${window.location.origin}/${encodedData}`;
|
||||
|
||||
textarea.value = shareUrl;
|
||||
} catch (error) {
|
||||
console.error('Sharing failed:', error);
|
||||
} finally {
|
||||
spinner.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
async function handlePageLoad() {
|
||||
const path = window.location.pathname.substring(1);
|
||||
if (!path) return;
|
||||
|
||||
try {
|
||||
// Try to decode the URL
|
||||
const decodedData = atob(path);
|
||||
const [secretKey, encryptionKeyData] = decodedData.split('::');
|
||||
|
||||
if (!secretKey || !encryptionKeyData) return;
|
||||
|
||||
spinner.classList.remove('hidden');
|
||||
|
||||
// Fetch the secret
|
||||
const response = await fetch(`${config.API_BASE_URL}/secret/${secretKey}`);
|
||||
|
||||
if (response.status === 404) {
|
||||
textarea.value = "Oops, your secret could not be found. Have you already retrieved it?";
|
||||
return;
|
||||
}
|
||||
|
||||
const { data: encryptedData } = await response.json();
|
||||
|
||||
// Decrypt the data
|
||||
const key = await importKey(encryptionKeyData);
|
||||
const decryptedData = await decryptData(encryptedData, key);
|
||||
|
||||
textarea.value = decryptedData;
|
||||
} catch (error) {
|
||||
console.error('Loading secret failed:', error);
|
||||
} finally {
|
||||
spinner.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
shareButton.addEventListener('click', handleShare);
|
||||
window.addEventListener('load', handlePageLoad);
|
5
static/config.js
Normal file
5
static/config.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
const config = {
|
||||
API_BASE_URL: 'http://localhost:8000'
|
||||
};
|
||||
|
||||
export default config;
|
69
static/crypto.js
Normal file
69
static/crypto.js
Normal file
|
@ -0,0 +1,69 @@
|
|||
export async function generateKey() {
|
||||
return await window.crypto.subtle.generateKey(
|
||||
{
|
||||
name: "AES-GCM",
|
||||
length: 256
|
||||
},
|
||||
true,
|
||||
["encrypt", "decrypt"]
|
||||
);
|
||||
}
|
||||
|
||||
export async function encryptData(data, key) {
|
||||
const iv = window.crypto.getRandomValues(new Uint8Array(12));
|
||||
const encodedData = new TextEncoder().encode(data);
|
||||
|
||||
const encryptedData = await window.crypto.subtle.encrypt(
|
||||
{
|
||||
name: "AES-GCM",
|
||||
iv: iv
|
||||
},
|
||||
key,
|
||||
encodedData
|
||||
);
|
||||
|
||||
const encryptedArray = new Uint8Array(encryptedData);
|
||||
const resultArray = new Uint8Array(iv.length + encryptedArray.length);
|
||||
resultArray.set(iv);
|
||||
resultArray.set(encryptedArray, iv.length);
|
||||
|
||||
return btoa(String.fromCharCode(...resultArray));
|
||||
}
|
||||
|
||||
export async function decryptData(encryptedData, key) {
|
||||
try {
|
||||
const data = Uint8Array.from(atob(encryptedData), c => c.charCodeAt(0));
|
||||
const iv = data.slice(0, 12);
|
||||
const ciphertext = data.slice(12);
|
||||
|
||||
const decrypted = await window.crypto.subtle.decrypt(
|
||||
{
|
||||
name: "AES-GCM",
|
||||
iv: iv
|
||||
},
|
||||
key,
|
||||
ciphertext
|
||||
);
|
||||
|
||||
return new TextDecoder().decode(decrypted);
|
||||
} catch (error) {
|
||||
console.error('Decryption failed:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function exportKey(key) {
|
||||
const exported = await window.crypto.subtle.exportKey("raw", key);
|
||||
return btoa(String.fromCharCode(...new Uint8Array(exported)));
|
||||
}
|
||||
|
||||
export async function importKey(keyData) {
|
||||
const keyBytes = Uint8Array.from(atob(keyData), c => c.charCodeAt(0));
|
||||
return await window.crypto.subtle.importKey(
|
||||
"raw",
|
||||
keyBytes,
|
||||
"AES-GCM",
|
||||
true,
|
||||
["encrypt", "decrypt"]
|
||||
);
|
||||
}
|
80
static/styles.css
Normal file
80
static/styles.css
Normal file
|
@ -0,0 +1,80 @@
|
|||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #2f2d3c;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
min-height: 150px;
|
||||
padding: 15px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
resize: vertical;
|
||||
margin-bottom: 15px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
textarea::placeholder {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
button {
|
||||
float: right;
|
||||
padding: 10px 30px;
|
||||
background-color: #00e5c7;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #00c4aa;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 5px solid rgba(255, 255, 255, 0.1);
|
||||
border-top: 5px solid #00e5c7;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: translate(-50%, -50%) rotate(0deg); }
|
||||
100% { transform: translate(-50%, -50%) rotate(360deg); }
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue