Add APIs
This commit is contained in:
@@ -8,14 +8,14 @@ import hashlib
|
|||||||
import secrets
|
import secrets
|
||||||
import subprocess
|
import subprocess
|
||||||
import platform
|
import platform
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime
|
||||||
import sys
|
import sys
|
||||||
import psutil
|
import psutil
|
||||||
|
|
||||||
# ===== Third-Party Imports =====
|
# ===== Third-Party Imports =====
|
||||||
from flask import (
|
from flask import (
|
||||||
Flask, render_template, request, jsonify, session,
|
Flask, render_template, request, jsonify, session,
|
||||||
redirect, url_for, flash, send_file
|
redirect, url_for, flash, send_file, make_response
|
||||||
)
|
)
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
||||||
@@ -83,20 +83,21 @@ def simple_decode(text: str) -> str:
|
|||||||
return ''.join(ALPHABET[(ALPHABET.index(c) - 3) % 26] if c in ALPHABET else c for c in text.lower())
|
return ''.join(ALPHABET[(ALPHABET.index(c) - 3) % 26] if c in ALPHABET else c for c in text.lower())
|
||||||
|
|
||||||
def advanced_encrypt(plaintext: str, password: str) -> str:
|
def advanced_encrypt(plaintext: str, password: str) -> str:
|
||||||
"""Encrypt text using AES-GCM with password-derived key."""
|
"""Encrypt plaintext with AES-GCM and return base64-encoded result."""
|
||||||
salt = os.urandom(16)
|
salt = os.urandom(16)
|
||||||
key = derive_key(password, salt)
|
|
||||||
nonce = os.urandom(12)
|
nonce = os.urandom(12)
|
||||||
ct = AESGCM(key).encrypt(nonce, plaintext.encode(), None)
|
key = derive_key(password, salt)
|
||||||
return base64.urlsafe_b64encode(salt + nonce + ct).decode()
|
ciphertext = AESGCM(key).encrypt(nonce, plaintext.encode(), None)
|
||||||
|
return base64.b64encode(salt + nonce + ciphertext).decode()
|
||||||
|
|
||||||
def advanced_decrypt(token_b64: str, password: str) -> str:
|
def advanced_decrypt(data_b64: str, password: str) -> str:
|
||||||
"""Decrypt text using AES-GCM with password-derived key."""
|
"""Decrypt base64-encoded AES-GCM encrypted data."""
|
||||||
try:
|
try:
|
||||||
data = base64.urlsafe_b64decode(token_b64.encode())
|
data = base64.b64decode(data_b64)
|
||||||
salt, nonce, ct = data[:16], data[16:28], data[28:]
|
salt, nonce, ciphertext = data[:16], data[16:28], data[28:]
|
||||||
key = derive_key(password, salt)
|
key = derive_key(password, salt)
|
||||||
return AESGCM(key).decrypt(nonce, ct, None).decode()
|
plaintext = AESGCM(key).decrypt(nonce, ciphertext, None)
|
||||||
|
return plaintext.decode()
|
||||||
except Exception:
|
except Exception:
|
||||||
return "[Error] Invalid password or corrupted data!"
|
return "[Error] Invalid password or corrupted data!"
|
||||||
|
|
||||||
@@ -207,7 +208,7 @@ def handle_file_upload(request):
|
|||||||
|
|
||||||
meta = {
|
meta = {
|
||||||
'pickup_password': base64.urlsafe_b64encode(hashlib.sha256(pickup_password.encode()).digest()).decode(),
|
'pickup_password': base64.urlsafe_b64encode(hashlib.sha256(pickup_password.encode()).digest()).decode(),
|
||||||
'original_name': filename,
|
'original_name': encrypt_filename(filename, enc_password),
|
||||||
'timestamp': datetime.now().isoformat()
|
'timestamp': datetime.now().isoformat()
|
||||||
}
|
}
|
||||||
with open(os.path.join(UPLOAD_FOLDER, f"{random_id}.json"), 'w') as f:
|
with open(os.path.join(UPLOAD_FOLDER, f"{random_id}.json"), 'w') as f:
|
||||||
@@ -217,7 +218,6 @@ def handle_file_upload(request):
|
|||||||
return jsonify({"success": True, "pickup_url": pickup_url})
|
return jsonify({"success": True, "pickup_url": pickup_url})
|
||||||
|
|
||||||
def handle_text_operation(request):
|
def handle_text_operation(request):
|
||||||
"""Process text encryption/decryption operations."""
|
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
encryption_type = data.get("encryption-type", "basic")
|
encryption_type = data.get("encryption-type", "basic")
|
||||||
operation = data.get("operation", "")
|
operation = data.get("operation", "")
|
||||||
@@ -226,10 +226,27 @@ def handle_text_operation(request):
|
|||||||
|
|
||||||
if encryption_type == "basic":
|
if encryption_type == "basic":
|
||||||
result = simple_encode(message) if operation == "encrypt" else simple_decode(message)
|
result = simple_encode(message) if operation == "encrypt" else simple_decode(message)
|
||||||
else:
|
return jsonify(result=html.escape(result))
|
||||||
result = advanced_encrypt(message, password) if operation == "encrypt" else advanced_decrypt(message, password)
|
|
||||||
|
|
||||||
return jsonify(result=html.escape(result))
|
if operation == "encrypt":
|
||||||
|
encrypted = advanced_encrypt(message, password)
|
||||||
|
return jsonify(result=encrypted)
|
||||||
|
else:
|
||||||
|
decrypted = advanced_decrypt(message, password)
|
||||||
|
return jsonify(result=html.escape(decrypted))
|
||||||
|
|
||||||
|
def encrypt_filename(filename: str, password: str) -> str:
|
||||||
|
salt = os.urandom(16)
|
||||||
|
key = derive_key(password, salt)
|
||||||
|
nonce = os.urandom(12)
|
||||||
|
ct = AESGCM(key).encrypt(nonce, filename.encode(), None)
|
||||||
|
return base64.urlsafe_b64encode(salt + nonce + ct).decode()
|
||||||
|
|
||||||
|
def decrypt_filename(enc_filename_b64: str, password: str) -> str:
|
||||||
|
raw = base64.urlsafe_b64decode(enc_filename_b64)
|
||||||
|
salt, nonce, ct = raw[:16], raw[16:28], raw[28:]
|
||||||
|
key = derive_key(password, salt)
|
||||||
|
return AESGCM(key).decrypt(nonce, ct, None).decode()
|
||||||
|
|
||||||
# ===== File Pickup Route =====
|
# ===== File Pickup Route =====
|
||||||
@app.route("/pickup/<file_id>", methods=["GET", "POST"])
|
@app.route("/pickup/<file_id>", methods=["GET", "POST"])
|
||||||
@@ -278,15 +295,22 @@ def handle_file_pickup(request, meta_path, enc_path, file_id):
|
|||||||
os.remove(enc_path)
|
os.remove(enc_path)
|
||||||
log_admin_event(f"File {file_id} downloaded and deleted.")
|
log_admin_event(f"File {file_id} downloaded and deleted.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
original_name = decrypt_filename(meta['original_name'], enc_password)
|
||||||
|
except Exception:
|
||||||
|
original_name = "retrieved_file"
|
||||||
|
|
||||||
response = send_file(
|
response = send_file(
|
||||||
io.BytesIO(decrypted),
|
io.BytesIO(decrypted),
|
||||||
as_attachment=True,
|
as_attachment=True,
|
||||||
download_name=meta['original_name'],
|
|
||||||
|
download_name=original_name,
|
||||||
mimetype='application/octet-stream'
|
mimetype='application/octet-stream'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add headers for better mobile compatibility
|
# Add headers for better mobile compatibility
|
||||||
response.headers['Content-Disposition'] = f'attachment; filename="{meta["original_name"]}"'
|
|
||||||
|
response.headers['Content-Disposition'] = f'attachment; filename="{original_name}"'
|
||||||
response.headers['Content-Type'] = 'application/octet-stream'
|
response.headers['Content-Type'] = 'application/octet-stream'
|
||||||
response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
|
response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
|
||||||
response.headers['Pragma'] = 'no-cache'
|
response.headers['Pragma'] = 'no-cache'
|
||||||
@@ -661,6 +685,96 @@ def robots_txt():
|
|||||||
]
|
]
|
||||||
return "\n".join(lines), 200, {"Content-Type": "text/plain"}
|
return "\n".join(lines), 200, {"Content-Type": "text/plain"}
|
||||||
|
|
||||||
|
# ===== API Endpoints =====
|
||||||
|
@app.route("/api/encrypt", methods=["POST"])
|
||||||
|
def api_encrypt():
|
||||||
|
try:
|
||||||
|
req = request.get_json()
|
||||||
|
password = req.get("password")
|
||||||
|
data_b64 = req.get("data")
|
||||||
|
|
||||||
|
if not password or not data_b64:
|
||||||
|
return jsonify({"error": "Missing data or password"}), 400
|
||||||
|
|
||||||
|
file_data = base64.b64decode(data_b64)
|
||||||
|
salt = os.urandom(16)
|
||||||
|
key = derive_key(password, salt)
|
||||||
|
nonce = os.urandom(12)
|
||||||
|
ct = AESGCM(key).encrypt(nonce, file_data, None)
|
||||||
|
encrypted_binary = salt + nonce + ct
|
||||||
|
|
||||||
|
# Return base64 string of the binary
|
||||||
|
encrypted_b64 = base64.b64encode(encrypted_binary).decode()
|
||||||
|
return jsonify({"result": encrypted_b64}), 200
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
|
@app.route("/api/decrypt", methods=["POST"])
|
||||||
|
def api_decrypt():
|
||||||
|
try:
|
||||||
|
req = request.get_json()
|
||||||
|
password = req.get("password")
|
||||||
|
encrypted_b64 = req.get("data")
|
||||||
|
|
||||||
|
if not password or not encrypted_b64:
|
||||||
|
return jsonify({"error": "Missing data or password"}), 400
|
||||||
|
|
||||||
|
encrypted_binary = base64.b64decode(encrypted_b64)
|
||||||
|
salt, nonce, ct = encrypted_binary[:16], encrypted_binary[16:28], encrypted_binary[28:]
|
||||||
|
key = derive_key(password, salt)
|
||||||
|
decrypted = AESGCM(key).decrypt(nonce, ct, None)
|
||||||
|
|
||||||
|
return jsonify({"result": base64.b64encode(decrypted).decode()}), 200
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
|
@app.route("/api/pacshare", methods=["POST"])
|
||||||
|
def api_pacshare():
|
||||||
|
try:
|
||||||
|
enc_password = request.form.get("enc_password")
|
||||||
|
pickup_password = request.form.get("pickup_password")
|
||||||
|
file = request.files.get("file")
|
||||||
|
|
||||||
|
if not file or not enc_password or not pickup_password:
|
||||||
|
return jsonify({"error": "Missing file or fields"}), 400
|
||||||
|
|
||||||
|
file_data = file.read()
|
||||||
|
filename = secure_filename(file.filename)
|
||||||
|
|
||||||
|
salt = os.urandom(16)
|
||||||
|
key = derive_key(enc_password, salt)
|
||||||
|
nonce = os.urandom(12)
|
||||||
|
ct = AESGCM(key).encrypt(nonce, file_data, None)
|
||||||
|
encrypted = salt + nonce + ct
|
||||||
|
|
||||||
|
file_id = secrets.token_urlsafe(24)
|
||||||
|
enc_path = os.path.join(UPLOAD_FOLDER, f"{file_id}.enc")
|
||||||
|
meta_path = os.path.join(UPLOAD_FOLDER, f"{file_id}.json")
|
||||||
|
|
||||||
|
with open(enc_path, "wb") as f:
|
||||||
|
f.write(encrypted)
|
||||||
|
|
||||||
|
encrypted_filename = encrypt_filename(filename, enc_password)
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
'pickup_password': base64.urlsafe_b64encode(
|
||||||
|
hashlib.sha256(pickup_password.encode()).digest()
|
||||||
|
).decode(),
|
||||||
|
'original_name': encrypted_filename,
|
||||||
|
'timestamp': datetime.now().isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
|
with open(meta_path, "w") as f:
|
||||||
|
json.dump(meta, f)
|
||||||
|
|
||||||
|
pickup_url = request.host_url.rstrip('/') + url_for('pickup_file', file_id=file_id)
|
||||||
|
return jsonify({"pickup_url": pickup_url})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
# ===== Error Handlers =====
|
# ===== Error Handlers =====
|
||||||
@app.errorhandler(404)
|
@app.errorhandler(404)
|
||||||
def page_not_found(e):
|
def page_not_found(e):
|
||||||
@@ -695,3 +809,4 @@ if __name__ == "__main__":
|
|||||||
else:
|
else:
|
||||||
print("[INFO] Running in DEVELOPMENT mode with Flask server.")
|
print("[INFO] Running in DEVELOPMENT mode with Flask server.")
|
||||||
app.run(debug=True, host="0.0.0.0", port=5000)
|
app.run(debug=True, host="0.0.0.0", port=5000)
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
+10
-31
@@ -7,16 +7,13 @@ import { encryptFile, decryptFile } from './fileops.js';
|
|||||||
|
|
||||||
// ===== UI Initialization =====
|
// ===== UI Initialization =====
|
||||||
export function setupUI() {
|
export function setupUI() {
|
||||||
// Set initial state of remove button to hidden
|
|
||||||
const removeBtn = document.getElementById("remove-file-btn");
|
const removeBtn = document.getElementById("remove-file-btn");
|
||||||
if (removeBtn) {
|
if (removeBtn) {
|
||||||
removeBtn.style.display = "none";
|
removeBtn.style.display = "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeEventListeners();
|
initializeEventListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== Event Listeners =====
|
|
||||||
function initializeEventListeners() {
|
function initializeEventListeners() {
|
||||||
const elements = {
|
const elements = {
|
||||||
encryptionType: document.getElementById("encryption-type"),
|
encryptionType: document.getElementById("encryption-type"),
|
||||||
@@ -56,9 +53,6 @@ function setupElementListeners(elements) {
|
|||||||
console.log("Mode:", elements.toggleSwitch.checked ? "Decrypt" : "Encrypt");
|
console.log("Mode:", elements.toggleSwitch.checked ? "Decrypt" : "Encrypt");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Add file input change listener
|
|
||||||
const fileInput = document.getElementById("file-input");
|
const fileInput = document.getElementById("file-input");
|
||||||
if (fileInput) {
|
if (fileInput) {
|
||||||
fileInput.addEventListener("change", () => {
|
fileInput.addEventListener("change", () => {
|
||||||
@@ -93,7 +87,6 @@ function setupShareLinkListeners(elements) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== UI State Management =====
|
|
||||||
function toggleEncryptionOptions() {
|
function toggleEncryptionOptions() {
|
||||||
const type = document.getElementById("encryption-type").value.trim().toLowerCase();
|
const type = document.getElementById("encryption-type").value.trim().toLowerCase();
|
||||||
const passwordInputWrapper = document.getElementById("password-input");
|
const passwordInputWrapper = document.getElementById("password-input");
|
||||||
@@ -101,19 +94,11 @@ function toggleEncryptionOptions() {
|
|||||||
const isAdvanced = type.includes("advanced");
|
const isAdvanced = type.includes("advanced");
|
||||||
|
|
||||||
if (passwordInputWrapper) {
|
if (passwordInputWrapper) {
|
||||||
if (isAdvanced) {
|
passwordInputWrapper.classList.toggle("hidden", !isAdvanced);
|
||||||
passwordInputWrapper.classList.remove("hidden");
|
|
||||||
} else {
|
|
||||||
passwordInputWrapper.classList.add("hidden");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileSection) {
|
if (fileSection) {
|
||||||
if (isAdvanced) {
|
fileSection.classList.toggle("hidden", !isAdvanced);
|
||||||
fileSection.classList.remove("hidden");
|
|
||||||
} else {
|
|
||||||
fileSection.classList.add("hidden");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateToggleLabels();
|
updateToggleLabels();
|
||||||
@@ -150,7 +135,6 @@ function toggleInputMode() {
|
|||||||
removeBtn.style.display = fileSelected ? "inline-block" : "none";
|
removeBtn.style.display = fileSelected ? "inline-block" : "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== Form Handling =====
|
|
||||||
async function handleSubmit(event) {
|
async function handleSubmit(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
@@ -189,14 +173,18 @@ async function handleTextOperation(encryptionType, operation, password) {
|
|||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify(payload)
|
body: JSON.stringify(payload)
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
document.getElementById("output-text").value = data.result;
|
|
||||||
|
const outputField = document.getElementById("output-text");
|
||||||
|
if (outputField) {
|
||||||
|
outputField.value = data.result || "[Error] No response received.";
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert("Error processing request: " + err.message);
|
alert("Error processing request: " + err.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== Utility Functions =====
|
|
||||||
function removeFile() {
|
function removeFile() {
|
||||||
const fileInput = document.getElementById("file-input");
|
const fileInput = document.getElementById("file-input");
|
||||||
if (fileInput) fileInput.value = "";
|
if (fileInput) fileInput.value = "";
|
||||||
@@ -214,7 +202,6 @@ function generateRandomPassword() {
|
|||||||
const passwordField = document.getElementById("generated-password");
|
const passwordField = document.getElementById("generated-password");
|
||||||
if (passwordField) {
|
if (passwordField) {
|
||||||
passwordField.value = password;
|
passwordField.value = password;
|
||||||
// Check if we should start Pacman
|
|
||||||
checkForPacman();
|
checkForPacman();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -225,33 +212,27 @@ function copyToClipboard(elementId, feedbackId) {
|
|||||||
|
|
||||||
if (!el || !el.value) return;
|
if (!el || !el.value) return;
|
||||||
|
|
||||||
// Create a temporary textarea element
|
|
||||||
const textarea = document.createElement('textarea');
|
const textarea = document.createElement('textarea');
|
||||||
textarea.value = el.value;
|
textarea.value = el.value;
|
||||||
textarea.style.position = 'fixed';
|
textarea.style.position = 'fixed';
|
||||||
textarea.style.opacity = '0';
|
textarea.style.opacity = '0';
|
||||||
document.body.appendChild(textarea);
|
document.body.appendChild(textarea);
|
||||||
|
|
||||||
// Select and copy the text
|
|
||||||
textarea.select();
|
textarea.select();
|
||||||
textarea.setSelectionRange(0, 99999); // For mobile devices
|
textarea.setSelectionRange(0, 99999);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Try using the modern clipboard API first
|
|
||||||
navigator.clipboard.writeText(el.value).then(() => {
|
navigator.clipboard.writeText(el.value).then(() => {
|
||||||
showFeedback(feedback);
|
showFeedback(feedback);
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
// Fallback to execCommand for older browsers
|
|
||||||
document.execCommand('copy');
|
document.execCommand('copy');
|
||||||
showFeedback(feedback);
|
showFeedback(feedback);
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Final fallback
|
|
||||||
document.execCommand('copy');
|
document.execCommand('copy');
|
||||||
showFeedback(feedback);
|
showFeedback(feedback);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up
|
|
||||||
document.body.removeChild(textarea);
|
document.body.removeChild(textarea);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,6 +279,7 @@ function checkForPacman() {
|
|||||||
window.exitGame();
|
window.exitGame();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyShareLink() {
|
function copyShareLink() {
|
||||||
const linkEl = document.getElementById("share-link");
|
const linkEl = document.getElementById("share-link");
|
||||||
const feedback = document.getElementById("shared-link-feedback");
|
const feedback = document.getElementById("shared-link-feedback");
|
||||||
@@ -346,8 +328,5 @@ function showCopyFeedback(feedbackEl) {
|
|||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function startPacman() { }
|
function startPacman() { }
|
||||||
function exitGame() { }
|
function exitGame() { }
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user