WebPass / webpass /templates /share_view.html
ag235772's picture
Initial Release: WebPass V2 with Steganography, Crypto Vault, and Cloud Toggle
136c0f7
{% extends "base.html" %}
{% block title %}Secure Message{% endblock %}
{% block page_title %}Dead Drop Protocol{% endblock %}
{% block content %}
<div class="row justify-content-center mt-5">
<div class="col-lg-6 fade-in-up">
<div class="cyber-card p-5 text-center">
<div id="locked-state">
<div class="mb-4 position-relative d-inline-block">
<i class="bi bi-file-earmark-lock2-fill text-warning" style="font-size: 4rem;"></i>
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
1 View Left
</span>
</div>
<h3 class="text-white mb-3">Encrypted Transmission</h3>
<p class="text-muted mb-4">
This message is stored in a Zero-Knowledge Vault.
Once you reveal it, it will be <strong>permanently deleted</strong> from the server and a self-destruct timer will begin.
</p>
<button id="reveal-btn" class="btn btn-warning w-100 py-3 fw-bold">
<i class="bi bi-eye-fill me-2"></i>Reveal Secret Message
</button>
</div>
<div id="unlocked-state" class="d-none text-start">
<div class="d-flex align-items-center mb-3">
<i class="bi bi-unlock-fill text-success fs-2 me-3"></i>
<h4 class="m-0 text-success">Decryption Successful</h4>
</div>
<div class="alert alert-warning border-warning bg-warning bg-opacity-10 d-flex justify-content-between align-items-center mb-4">
<div class="text-warning">
<i class="bi bi-stopwatch fs-4 me-2"></i> <strong>Self-Destructing in:</strong>
</div>
<div id="countdown-timer" class="fs-1 fw-bold text-danger font-monospace">--</div>
</div>
<label class="small text-muted text-uppercase fw-bold">Message Contents:</label>
<div class="p-4 bg-dark border border-success rounded mt-2 position-relative">
<code id="secret-content" class="text-white fs-5" style="word-break: break-all;"></code>
<button class="btn btn-sm btn-outline-success position-absolute top-0 end-0 m-2" onclick="copySecret()">
Copy
</button>
</div>
</div>
<div id="error-state" class="d-none">
<i class="bi bi-x-octagon-fill text-danger mb-3" style="font-size: 4rem;"></i>
<h3 class="text-danger">Access Denied</h3>
<p class="text-muted" id="error-msg"></p>
<a href="{{ url_for('share.share_ui') }}" class="btn btn-outline-light mt-3">Create New Drop</a>
</div>
</div>
</div>
</div>
<script src="{{ url_for('static', filename='js/zk_crypto.js') }}"></script>
<script>
function copySecret() {
const text = document.getElementById("secret-content").innerText;
navigator.clipboard.writeText(text);
alert("Copied to clipboard!");
}
document.getElementById("reveal-btn").addEventListener("click", async () => {
const btn = document.getElementById("reveal-btn");
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Decrypting...';
try {
const keyFromHash = window.location.hash.substring(1);
if (!keyFromHash) throw new Error("Decryption key missing. The link is incomplete.");
const dropId = "{{ drop_id }}";
const res = await fetch(`/api/share/${dropId}`, { method: "POST" });
if (res.status === 410 || res.status === 404) {
throw new Error("This message has expired or has already been viewed.");
}
if (!res.ok) throw new Error("Server communication error.");
const data = await res.json();
const key = await ZKCrypto.deriveKey(keyFromHash, Uint8Array.from(atob(data.salt), c => c.charCodeAt(0)));
const iv = Uint8Array.from(atob(data.iv), c => c.charCodeAt(0));
const encrypted = Uint8Array.from(atob(data.ciphertext), c => c.charCodeAt(0));
const decryptedBuf = await window.crypto.subtle.decrypt(
{ name: "AES-GCM", iv: iv },
key,
encrypted
);
const dec = new TextDecoder();
const plainText = dec.decode(decryptedBuf);
document.getElementById("locked-state").classList.add("d-none");
document.getElementById("secret-content").textContent = plainText;
document.getElementById("unlocked-state").classList.remove("d-none");
// --- START COUNTDOWN TIMER ---
let timeLeft = data.view_time || 30; // Default fallback
const timerDisplay = document.getElementById("countdown-timer");
timerDisplay.textContent = timeLeft + "s";
const timerInterval = setInterval(() => {
timeLeft--;
timerDisplay.textContent = timeLeft + "s";
if (timeLeft <= 0) {
clearInterval(timerInterval);
// Timer hit 0. Force page reload.
// Because data is burned on server, reload will show 'Link Terminated'.
window.location.reload(true);
}
}, 1000);
} catch (err) {
document.getElementById("locked-state").classList.add("d-none");
document.getElementById("error-msg").textContent = err.message;
document.getElementById("error-state").classList.remove("d-none");
}
});
</script>
{% endblock %}