Files
Secure-Pastebin-Self-Hosted/script.js
T
M.M.Azizi fa09eed556 Add encryption and decryption features in script.js
Implement encryption and decryption functionality with password support, including UI updates for error handling and content display.
2026-02-19 14:30:48 +03:30

256 lines
8.4 KiB
JavaScript

let pendingKey = null;
let pendingData = null;
function showError(msg) {
document.getElementById('errorDisplay').style.display = 'flex';
document.getElementById('errorMessage').textContent = msg;
}
function clearError() {
document.getElementById('errorDisplay').style.display = 'none';
}
function isRTL(text) {
const rtlRegex = /[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\u0590-\u05FF\uFB50-\uFDFF\uFE70-\uFEFF]/;
return rtlRegex.test(text);
}
const contentTextarea = document.getElementById('content');
if (contentTextarea) {
contentTextarea.addEventListener('input', function(e) {
const text = e.target.value;
if (isRTL(text)) {
e.target.style.direction = 'rtl';
e.target.style.fontFamily = "'Vazirmatn', sans-serif";
} else {
e.target.style.direction = 'ltr';
e.target.style.fontFamily = "'JetBrains Mono', monospace";
}
});
}
function togglePassword() {
const enabled = document.getElementById('enablePassword').checked;
const wrapper = document.getElementById('passwordWrapper');
wrapper.classList.toggle('show', enabled);
}
const Base64 = {
encode(buf) {
const bytes = new Uint8Array(buf);
let bin = '';
for (let b of bytes) bin += String.fromCharCode(b);
return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
},
decode(str) {
str = str.replace(/-/g, '+').replace(/_/g, '/');
while (str.length % 4) str += '=';
const bin = atob(str);
const bytes = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
return bytes;
}
};
async function deriveKeyFromPassword(password, salt) {
const encoder = new TextEncoder();
const keyMaterial = await crypto.subtle.importKey(
'raw', encoder.encode(password), { name: 'PBKDF2' }, false, ['deriveKey']
);
return crypto.subtle.deriveKey(
{ name: 'PBKDF2', salt, iterations: 100000, hash: 'SHA-256' },
keyMaterial,
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
);
}
function genId() {
const arr = new Uint8Array(16);
crypto.getRandomValues(arr);
return Array.from(arr, b => b.toString(16).padStart(2, '0')).join('');
}
async function createPaste() {
clearError();
const content = document.getElementById('content').value.trim();
if (!content) return showError('Please enter content to encrypt');
const hasPassword = document.getElementById('enablePassword').checked;
const password = document.getElementById('passwordInput').value;
if (hasPassword && password.length < 4) {
return showError('Password must be at least 4 characters');
}
const btn = document.getElementById('createBtn');
const btnText = document.getElementById('btnText');
btn.disabled = true;
btnText.innerHTML = '<span class="loading"></span> Encrypting...';
try {
let key, keyToExport;
if (hasPassword) {
const salt = crypto.getRandomValues(new Uint8Array(16));
key = await deriveKeyFromPassword(password, salt);
keyToExport = salt;
} else {
key = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt']);
keyToExport = await crypto.subtle.exportKey('raw', key);
}
const iv = crypto.getRandomValues(new Uint8Array(12));
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
new TextEncoder().encode(content)
);
const id = genId();
const payload = {
iv: Array.from(iv),
data: Array.from(new Uint8Array(encrypted))
};
const res = await fetch('/api/create', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id,
encryptedData: payload,
expiresIn: parseInt(document.getElementById('expiresIn').value),
burnAfterRead: document.getElementById('burnAfterRead').checked,
hasPassword: hasPassword
})
});
const result = await res.json();
if (!res.ok) throw new Error(result.error || 'Server error');
let url;
if (hasPassword) {
url = `${location.origin}/#${id}:${Base64.encode(keyToExport)}:pwd`;
} else {
url = `${location.origin}/#${id}:${Base64.encode(keyToExport)}`;
}
document.getElementById('shareUrl').value = url;
document.getElementById('expiryTime').textContent = new Date(Date.now() + parseInt(document.getElementById('expiresIn').value) * 1000).toLocaleString();
document.getElementById('passwordNotice').style.display = hasPassword ? 'flex' : 'none';
document.getElementById('resultBox').classList.add('show');
document.getElementById('content').value = '';
} catch (err) {
showError(err.message);
} finally {
btn.disabled = false;
btnText.textContent = '🔐 Encrypt & Save';
}
}
async function decryptPaste() {
const hash = location.hash.slice(1);
if (!hash.includes(':')) return;
const parts = hash.split(':');
const id = parts[0];
const keyData = parts[1];
const isPasswordProtected = parts[2] === 'pwd';
try {
const res = await fetch(`/api/get/${id}`);
const data = await res.json();
if (!res.ok) throw new Error(data.error);
if (data.hasPassword || isPasswordProtected) {
pendingKey = keyData;
pendingData = data;
document.getElementById('createView').style.display = 'none';
document.getElementById('passwordPrompt').classList.add('show');
return;
}
await performDecryption(keyData, data);
} catch (err) {
showError('Failed: ' + err.message);
setTimeout(() => location.href = '/', 3000);
}
}
async function decryptWithPassword() {
const password = document.getElementById('decryptPassword').value;
if (!password) return;
const btn = document.querySelector('#passwordPrompt .btn');
const btnText = document.getElementById('decryptBtnText');
btn.disabled = true;
btnText.innerHTML = '<span class="loading"></span> Decrypting...';
try {
const salt = Base64.decode(pendingKey);
const key = await deriveKeyFromPassword(password, salt);
await performDecryption(key, pendingData, true);
} catch (err) {
document.getElementById('passwordError').style.display = 'block';
btn.disabled = false;
btnText.textContent = '🔓 Decrypt';
}
}
async function performDecryption(keyOrData, data, isKeyObject = false) {
let key;
if (isKeyObject) {
key = keyOrData;
} else {
const keyRaw = Base64.decode(keyOrData);
key = await crypto.subtle.importKey(
'raw', keyRaw, { name: 'AES-GCM', length: 256 }, false, ['decrypt']
);
}
const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: new Uint8Array(data.data.iv) },
key,
new Uint8Array(data.data.data)
);
const text = new TextDecoder().decode(decrypted);
document.getElementById('passwordPrompt').classList.remove('show');
document.getElementById('decryptView').classList.add('show');
if (data.burnAfterRead) {
document.getElementById('burnNotice').style.display = 'flex';
}
const contentBox = document.getElementById('decryptedContent');
contentBox.textContent = text;
if (isRTL(text)) {
contentBox.style.direction = 'rtl';
contentBox.style.fontFamily = "'Vazirmatn', sans-serif";
} else {
contentBox.style.direction = 'ltr';
contentBox.style.fontFamily = "'JetBrains Mono', monospace";
}
history.replaceState(null, null, ' ');
}
function copyUrl() {
const inp = document.getElementById('shareUrl');
inp.select();
document.execCommand('copy');
const btn = document.querySelector('.btn-copy');
btn.textContent = '✅ Copied!';
setTimeout(() => btn.textContent = '📋 Copy', 2000);
}
window.addEventListener('load', () => {
if (location.hash.length > 1) decryptPaste();
});