178 lines
6.1 KiB
Plaintext
178 lines
6.1 KiB
Plaintext
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<%- include('../layout/head.ejs', { title: 'Intended Link' }) %>
|
|
|
|
<body>
|
|
<%- include('../layout/header.ejs') %>
|
|
<main class="f-col">
|
|
<div class="page-wrapper">
|
|
<div class="text-center">
|
|
<h1 class="page-header">Securely Share Your Secrets</h1>
|
|
<p>Only the person with the account you specify will be able to decrypt your message.</p>
|
|
</div>
|
|
<form id="secretForm" class="table rows">
|
|
<p>
|
|
<label for="text">Secret Message</label>
|
|
<textarea id="text" name="text" placeholder="Enter your secret here." required></textarea>
|
|
</p>
|
|
<p>
|
|
<label for="file">Or select your secret file</label>
|
|
<input type="file" id="file" name="file">
|
|
</p>
|
|
<p>
|
|
<label for="serviceIdentifier">Their Username / Email</label>
|
|
<input type="text" id="serviceIdentifier" name="serviceIdentifier" required>
|
|
</p>
|
|
<p>
|
|
<label for="github">Type of account:</label>
|
|
<select id="service" name="service">
|
|
<option value="github">Github</option>
|
|
<option value="google">Google</option>
|
|
</select>
|
|
</p>
|
|
<p>
|
|
<button type="submit">Generate URL</button>
|
|
</p>
|
|
</form>
|
|
|
|
<div class="result-section">
|
|
<div class="f-row">
|
|
<label for="result">Intended Link:</label>
|
|
<input type="text" id="result" readonly>
|
|
<button id="copyButton" class="iconbutton" type="button">⎘</button>
|
|
</div>
|
|
<div>
|
|
<label for="encryptedData">Encrypted data sent to the server:</label>
|
|
<textarea id="encryptedData" readonly></textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<script>
|
|
const HexMix = {
|
|
uint8ToHex(data) {
|
|
return Array.from(data, byte => ('00' + byte.toString(16)).slice(-2)).join('');
|
|
},
|
|
hexToUint8(data) {
|
|
const hexArray = data.match(/.{1,2}/g);
|
|
return hexArray ? new Uint8Array(hexArray.map(char => parseInt(char, 16))) : new Uint8Array(0);
|
|
},
|
|
arrayBufferToString(buf, callback) {
|
|
const reader = new FileReader();
|
|
reader.onload = event => callback(event.target.result);
|
|
reader.onerror = event => alert(event.target.error);
|
|
reader.readAsBinaryString(new Blob([buf], { type: 'application/octet-stream' }));
|
|
},
|
|
stringToArrayBuffer(str) {
|
|
const array = new Uint8Array(str.length);
|
|
for (let i = 0; i < str.length; i++) {
|
|
array[i] = str.charCodeAt(i);
|
|
}
|
|
return array.buffer;
|
|
}
|
|
};
|
|
|
|
const copyButton = document.getElementById('copyButton');
|
|
const fileInput = document.getElementById('file');
|
|
|
|
copyButton.addEventListener('click', () => {
|
|
resultInput.select();
|
|
document.execCommand('copy');
|
|
alert('URL copied to clipboard!');
|
|
});
|
|
|
|
fileInput.addEventListener('change', (e) => {
|
|
const file = e.target.files[0];
|
|
if (file) {
|
|
document.getElementById('text').disabled = true;
|
|
} else {
|
|
document.getElementById('text').disabled = false;
|
|
}
|
|
});
|
|
|
|
const form = document.getElementById('secretForm');
|
|
const resultInput = document.getElementById('result');
|
|
const encryptedDataTextarea = document.getElementById('encryptedData');
|
|
|
|
|
|
form.addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
|
|
const formData = new FormData(form);
|
|
const text = formData.get('text');
|
|
const file = formData.get('file');
|
|
const serviceIdentifier = formData.get('serviceIdentifier');
|
|
const service = formData.get('service');
|
|
|
|
let plaintext = text;
|
|
let filetype = 'text/plain';
|
|
let filename = 'secret.txt';
|
|
|
|
if (file.size > 0) {
|
|
if (file.size > 2097152) {
|
|
alert('Error: Max file size is 2mb.');
|
|
return;
|
|
}
|
|
plaintext = await file.arrayBuffer();
|
|
filetype = file.type;
|
|
filename = file.name;
|
|
}
|
|
|
|
const key = await window.crypto.subtle.generateKey(
|
|
{ name: 'AES-GCM', length: 256 },
|
|
true,
|
|
['encrypt', 'decrypt']
|
|
);
|
|
|
|
const iv = window.crypto.getRandomValues(new Uint8Array(16));
|
|
const exported = await window.crypto.subtle.exportKey('raw', key);
|
|
const encrypted = await window.crypto.subtle.encrypt(
|
|
{ name: 'AES-GCM', iv },
|
|
key,
|
|
typeof plaintext === 'string' ? HexMix.stringToArrayBuffer(plaintext) : plaintext
|
|
);
|
|
|
|
HexMix.arrayBufferToString(encrypted, async (encryptedText) => {
|
|
encryptedDataTextarea.value = encryptedText;
|
|
|
|
|
|
const keyHex = HexMix.uint8ToHex(new Uint8Array(exported));
|
|
const ivHex = HexMix.uint8ToHex(iv);
|
|
const keyParams = { key: keyHex, iv: ivHex };
|
|
|
|
const params = JSON.stringify({
|
|
service: service,
|
|
serviceIdentifier: serviceIdentifier,
|
|
text: encryptedText,
|
|
iv: ivHex,
|
|
filetype: filetype,
|
|
filename: filename
|
|
});
|
|
|
|
try {
|
|
const response = await fetch('/rest/links', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: params
|
|
});
|
|
const data = await response.json();
|
|
const url = `http://${window.location.host}/rest/links/${data.id}#${btoa(JSON.stringify(keyParams))}`;
|
|
resultInput.value = url;
|
|
} catch (err) {
|
|
alert(`Error: ${err.message}`);
|
|
}
|
|
})
|
|
|
|
});
|
|
|
|
|
|
</script>
|
|
|
|
<%- include('../layout/footer.ejs') %>
|
|
</body>
|
|
|
|
</html> |