get creation of links working
This commit is contained in:
178
views/app/send.ejs
Normal file
178
views/app/send.ejs
Normal file
@@ -0,0 +1,178 @@
|
||||
<!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>
|
8
views/layout/footer.ejs
Normal file
8
views/layout/footer.ejs
Normal file
@@ -0,0 +1,8 @@
|
||||
<footer>
|
||||
<p>© 2023 Intended Link. All rights reserved.</p>
|
||||
<p>
|
||||
<a href="https://git.silentsilas.com/silentsilas/intended-server" target="_blank" rel="noopener noreferrer">
|
||||
Intended Link
|
||||
</a>, by <a href="https://silentsilas.com" target="_blank" rel="noopener noreferrer">Silent Silas</a>
|
||||
</p>
|
||||
</footer>
|
25
views/layout/head.ejs
Normal file
25
views/layout/head.ejs
Normal file
@@ -0,0 +1,25 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title>
|
||||
<%= title %>
|
||||
</title>
|
||||
<style>
|
||||
html {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
background-color: #121212;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<link href="/static/missing/missing.min.css" rel="stylesheet" />
|
||||
<link href="/static/victormono/index.css" rel="stylesheet" />
|
||||
<link href="/static/app.css" rel="stylesheet" />
|
||||
</head>
|
9
views/layout/header.ejs
Normal file
9
views/layout/header.ejs
Normal file
@@ -0,0 +1,9 @@
|
||||
<header class="navbar">
|
||||
<nav aria-label="Site sections">
|
||||
<ul role="list">
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/doc">Docs</a></li>
|
||||
<li><a href="/app">App</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
@@ -1,100 +1,32 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title>client</title>
|
||||
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700" rel="stylesheet" />
|
||||
<style>
|
||||
body, h1 {
|
||||
font-family: Source Sans Pro,sans-serif;
|
||||
}
|
||||
body:after {
|
||||
content: "";
|
||||
background-image: radial-gradient(#eef2f5 0,#f4f7f8 40%,transparent 75%);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 60%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
.container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.container-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
.container-logo img {
|
||||
max-width: 150px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
ul li a {
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
padding-top: .25rem;
|
||||
padding-bottom: .25rem;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
border: 2px solid #504747;
|
||||
min-width: 110px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
display: block;
|
||||
border-radius: 1rem;
|
||||
color: #504747;
|
||||
text-decoration: none;
|
||||
transition: all ease-in-out 0.5s;
|
||||
}
|
||||
ul li a:hover {
|
||||
color: #14a5c2;
|
||||
border-color: #14a5c2;
|
||||
}
|
||||
ul li a span {
|
||||
margin: .25rem;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div>
|
||||
<div class="container-logo">
|
||||
<img src="https://tsed.io/tsed-og.png" alt="Ts.ED">
|
||||
</div>
|
||||
<%- include('layout/head.ejs', { title: 'Intended Link' }) %>
|
||||
|
||||
<body>
|
||||
<%- include('layout/header.ejs') %>
|
||||
<main class="container">
|
||||
<h1>Intended Link</h1>
|
||||
<p>Securely share messages with verified recipients via OAuth.</p>
|
||||
|
||||
<h2>How it works:</h2>
|
||||
<ol>
|
||||
<li> Enter your secret message.</li>
|
||||
<li> Choose the service and username of the recipient.</li>
|
||||
<li> We generate a secure link that only the intended recipient can access.</li>
|
||||
<li> The recipient must authenticate with the specified account for the service in order to
|
||||
access the message.</li>
|
||||
</ol>
|
||||
|
||||
<h2>Supported services</h2>
|
||||
<ul>
|
||||
<% docs.forEach((doc) => { %>
|
||||
|
||||
<li><a href="<%= doc.path %>"><span>OpenSpec <%= doc.specVersion %></span></a></li>
|
||||
|
||||
<% }) %>
|
||||
<li> Github</li>
|
||||
<li> Google</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
</main>
|
||||
|
||||
<%- include('layout/footer.ejs') %>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
|
||||
</html>
|
Reference in New Issue
Block a user