This repository has been archived by the owner on Apr 12, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
489 lines (434 loc) · 17.4 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="description" content="A secure tool to share sensitive information in encrypted form with third parties">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
<title>Share a Secret - Short lived encrypted secret sharing</title>
<link rel="stylesheet" href="static/custom.css" integrity="sha256-kh6gVNFTId2fxJriixeeDfo5X8rK0ZW9i81infWYch8=">
<link rel="stylesheet" href="static/semantic-2.7.5.css" integrity="sha256-3a0+mzIiHF2Wt5DvkjjoX0+M9otO3KTaMaqhaSwVZXg=">
<style>
#bodysegm,
#bodysegmresult {
display: none;
}
.slider {
width: 80%
}
#result,
#resultsecret,
#error,
#decryptionerror,
#error2,
#error3,
#extradetails + div,
#resultextradetails + div,
.red.segment,
#intro {
display: none;
}
#encryptedtext {
word-wrap: break-word;
}
#details p,
#details h4 {
color: #767676;
}
#details .ui.message {
clear: both;
}
#linkheader {
font-family: Verdana,serif;
}
#bottomseparator {
max-width: 100px;
margin: 0 auto 1em auto;
}
.styletag {
font-family: monospace;
}
</style>
</head>
<body>
<br><br>
<div id="bodysegmresult" class="ui text container">
<div id="decryptionerror">
<div class="ui error icon message">
<i class="robot icon"></i>
<div class="content">
<div id="error1">
<div class="header">Cannot decrypt secret</div>
<p>Your encrypted secret could not be decrypted. There was something wrong with the decryption key. Are you sure you
used the correct link?</p>
</div>
<div id="error2">
<div class="header">Secret not found</div>
<p>There is no secret with the given identifier. Did you use the correct link?</p>
</div>
<div id="error3">
<div class="header">Server error</div>
<p>Something bad happened with the server. Try again later or notify me</p>
</div>
</div>
</div>
</div>
<div id="resultsecret">
<div class="ui raised segment" style="padding-top: 3em">
<div class="ui top left attached black label">This is the content of your decrypted secret</div>
<span class="ui large text" style="white-space: pre-line"></span>
</div>
<span class="ui grey text"><i class="stopwatch icon"></i> This secret will expire on <span id="expiretime"></span></span><br>
<span class="ui grey text"><i class="eye icon"></i> <span id="totalViews">0</span> time(s) viewed</span>
<a id="resultextradetails" class="ui right floated tertiary button">+ Show extra details</a>
<div class="ui basic message">
<h4 class="ui header">Access records:</h4>
<p id="viewrecords"></p>
</div>
<br><br>
<div class="ui red segment">
<div class="ui red ribbon label"><i class="lightbulb outline large icon"></i></div>
<span class="ui red header">Are you planning to store this secret?</span>
<div class="ui divider"></div>
<span class="ui text">
If this secret is a password that you would like to store, it's <b>strongly</b> recommended to store it <b>securely</b>.
You can do so by using the open source <a href="https://keepass.info/" target="_blank">KeePass</a> password manager,
which allows you to store your passwords and other (related) data encrypted using strong and modern encryption algorithms.
A cross-platform alternative to it is <a href="https://keepassxc.org/" target="_blank">KeePassXC</a>.
On Android try out <a href="https://play.google.com/store/apps/details?id=keepass2android.keepass2android_nonet"
target="_blank">Keepass2Android Offline</a>. There are also many options for iPhone, but I can't
personally recommend one due to not having tested them.
<br><br>
For a video tutorial, check out these ones: <a href="https://www.youtube.com/watch?v=RvV698aztNs" target="_blank">first</a>,
<a href="https://www.youtube.com/watch?v=Dm3Xeo0hIQk" target="_blank">second</a>
</span>
</div>
</div>
</div>
<div id="bodysegm" class="ui text container">
<form class="ui form">
<div class="field">
<h1 class="ui centered header">
Share a Secret
<div class="sub header">Short lived encrypted secret sharing</div>
</h1>
<textarea rows="5"></textarea>
</div>
<div class="field">
<span>Link stays valid for <b id="duration">1 hour</b></span>
<div class="ui blue slider"></div>
</div>
<div class="field" style="text-align: center">
<div class="ui grey submit big button"><i class="lock icon"></i>Generate share link</div>
</div>
</form>
<br>
<div id="error">
<div class="ui error icon message">
<i class="robot icon"></i>
<div class="content">
<div class="header">Secret not created</div>
<p>Your encrypted secret could not be created right now due to an error that happened on the server.
Perhaps you could try again? If it keeps happening, please let me know so I can fix it for you.</p>
</div>
</div>
</div>
<div id="result">
<div class="ui raised blue segment">
<h2 id="linkheader" class="ui centered header">Your secure link:</h2>
<div class="ui action fluid input">
<input id="url" type="text">
<button id="copy" class="ui blue right labeled icon button"><i class="copy icon"></i>Copy</button>
</div>
</div>
<div id="details" class="ui basic segment">
<span class="ui grey header">Your secret will expire on: <span id="expirationTime"></span></span>
<br><br>
<a id="extradetails" class="ui right floated tertiary button">+ Show extra details</a>
<div class="ui basic message">
<h4 class="ui header">This is the encrypted version of your secret that is stored, unreadable to us:</h4>
<p id="encryptedtext"></p>
</div>
</div>
</div>
</div>
<div id="footer" class="ui text container">
<i class="copyright outline icon"></i>2019, https://wheredoi.click v1.3
{<a href="https://github.com/aardbol/secretshareweb" target="_blank"><i class="fitted github grey icon"></i></a>}<br>
<a href="index.html">home</a> | <a href="about.html">about this app</a> | <a href="security.html">how is this app secure?</a>
</div>
<div id="intro" class="ui text container">
<br>
<div id="bottomseparator" class="ui divider"></div>
<span class="ui grey text">The Share a Secret webapp allows you to share sensitive information <u>securely and privately</u>.
Enter the sensitive information in the form above, chose a lifespan for the secret and generate the share link. Only
that link contains the decryption key to decipher the secret. It's not stored and never sent to the server. The encrypted
secret will be completely removed when the expiration date has been reached. <a href="security.html">Read more</a></span>
<br><br>
You can style certain parts of text by surrounding them with supported tags:<br>
<span class="styletag">[hl][/hl]</span>: <code>highlight that text in monospace font</code><br>
<span class="styletag">[b][/b]</span>: <b>make text bold</b>
</div>
<script src="static/jquery-3.4.1.js" integrity="sha256-WpOohJOqMqqyKL9FccASB9O0KwACQJpFTUBLTYOVvVU="></script>
<script src="static/semantic-2.7.5.js" integrity="sha256-/fbdRVqLH/oOOtOg/Eb0A1ttDWTJn6L6yxhO2HC/+yg="></script>
<script src="static/xxtea.js" integrity="sha256-FeTvtpHz0E1sewBokHlUBIwlYAx0yA7f9m+KVHvEJCY=" async></script>
<script>
var durations = ["1 hour","3 hours", "5 hours", "1 day", "3 days", "7 days", "14 days", "30 days"],
durationMinutes = [60, 180, 300, 1440, 4320, 10080, 20160, 43200],
// URL of the app
baseURL = "file:///home/ik/WebstormProjects/secretshare/index.html",
// Holds the value of the text that needs to be encrypted, just to know when the data was changed so a new
// secure link can be created
data,
// Holds the duration of the secret, just to know when the data was changed so a new secure link can be created
duration,
// Will contain the hash until the response is received from the server. It is emptied as soon as it's not
// needed anymore, see submitElm.api()
hash,
// HTML element identifiers, cached here for quick usage
submitElm = $('.submit'),
textElm = $('textarea'),
resultElm = $('#result'),
errorElm = $('#error'),
decryptErrorElm = $('#decryptionerror'),
bodySegmResultElm = $('#bodysegmresult');
// API settings
$.fn.api.settings.contentType = "application/json";
$.fn.api.settings.base = "http://localhost:8080/v1";
$.fn.api.settings.api = {
'create secret' : "/",
'get secret' : "/{id}"
};
//--
// Functions
//--
/**
* Generate randomly secure ids
*
* Copyright 2017 Andrey Sitnik <[email protected]>
* MIT license
*
* @param size The desired number of characters
*/
function nanoid(size) {
size = size || 21;
var crypto = self.crypto || self.msCrypto,
id = '',
bytes = crypto.getRandomValues(new Uint8Array(size)),
url = 'Uint8ArdomValuesObj012345679BCDEFGHIJKLMNPQRSTWXYZ_cfghkpqvwxyz-';
while (0 < size--) {
id += url[bytes[size] & 63];
}
return id;
}
/**
* Copy the value to the clipboard
*
* @param elementId The id attribute of the HTML element
*/
function copyToClipboard(elementId) {
var id = document.getElementById(elementId);
id.select();
document.execCommand("copy");
console.log("Copied to clipboard: " + id.value);
}
/**
* Get the ID after the ? in the URL
*
* @returns {string} Secret ID
*/
function getIdFromUrl() {
return location.search.substr(1);
}
/**
* Get the decryption hash from the URL.
* This is only used when the secret is accessed with the share link!
*
* @return {string} Decryption key
*/
function getDecryptionHashFromUrl() {
return window.location.hash.substr(1);
}
/**
* Show an error on the result page
*/
function showError(id) {
decryptErrorElm.transition("fade in").find(id).show();
}
/**
* Convert HTML to text
*/
function stripHTML(dirtyString) {
var container = document.createElement('div'),
text = document.createTextNode(dirtyString);
container.appendChild(text);
return container.innerHTML; // innerHTML will be a xss safe string
}
//---
// DOM ready
//--
$(function(){
// Which page do we have to show?
if (getIdFromUrl() === "") {
// Always start with empty input
textElm.val("");
$('#bodysegm').show();
$('#intro').show();
} else {
bodySegmResultElm.transition("fly down", function(){
$(this).find('.red.segment').transition("fly left");
});
}
// Some eyecandy
$('h1').transition("zoom in");
$('.ui.slider').slider({
min: 0,
max: 7,
onMove: function(v) {
$('#duration').text(durations[v]);
if (duration !== durationMinutes[v]) {
duration = durationMinutes[v];
submitElm.removeClass("disabled");
}
}
});
// Detect a change in the secret so the user has the posibility to create another URL
textElm.keyup(function() {
var currentData = $(this).val().trim();
if (currentData.length !== 0) {
textElm.parent().removeClass("error");
if (data !== currentData) {
submitElm.removeClass("disabled");
} else {
submitElm.addClass("disabled");
}
}
});
$('#extradetails,#resultextradetails').click(function() {
$(this).next().transition("slide");
$(this).text($(this).text().replace(
$(this).text().indexOf("+") === 0 ? "+" : "-",
$(this).text().indexOf("+") === 0 ? "-" : "+")
);
});
// The API request
submitElm.api({
action: "create secret",
method: "post",
beforeSend: function(settings) {
if (textElm.val().trim().length === 0 ) {
textElm.parent().addClass("error");
textElm.transition({
animation: "shake",
queue: false
});
console.log("API [create secret]: request prevented");
return false;
}
// Stored to allow to detect a change, see textElm.keyup()
data = textElm.val().trim();
// Create the secret hash used for encryption and decryption
hash = nanoid(10);
// The secret is encrypted with XXTEA here before it's sent to the API
var encData = Tea.encrypt(data, hash);
// Now it's added to the request body, notice the encryption key is not sent to the server
settings.data = JSON.stringify({data: encData, expires: duration});
// Show the user what the encrypted data looks like
$('#encryptedtext').text(encData);
return settings;
},
onSuccess: function(json) {
console.log("API [create secret] success: Secret created with ID: " + json['id']);
// Disable the button because the same secret and duration shouldn't be submitted again
submitElm.addClass("disabled");
// Create the URL
$('#url').val(baseURL + "?" + json['id'] + "#" + hash);
$('#expirationTime').text((new Date(json['expires'])).toLocaleString());
// We don't need the hash to be stored anymore
hash = null;
if (resultElm.is(":hidden")) {
resultElm.transition("fade in");
} else {
resultElm.transition("pulse");
}
},
onError: function() {
console.log("API [create secret] error: Could not create secret due to server error");
// We don't need the hash to be stored anymore
hash = null;
// Enable the submit button again for retry
submitElm.removeClass("disabled");
if (errorElm.is(":hidden")) {
errorElm.transition("fade in");
} else {
errorElm.transition("pulse");
}
}
});
$.api({
action: "get secret",
on: "now",
beforeSend: function(settings) {
var secretId = getIdFromUrl();
if (secretId == null || secretId.length === 0 ) {
console.log("API [get secret]: request prevented because no secret ID is found");
return false;
}
settings.urlData.id = secretId;
return settings;
},
onSuccess: function(json) {
console.log("API [get secret] success: secret received");
var $resultsecret = $('#resultsecret');
if (getDecryptionHashFromUrl() !== "" && getDecryptionHashFromUrl().length === 10) {
$resultsecret.show().find('span:first').text(Tea.decrypt(json['data'], getDecryptionHashFromUrl()));
$resultsecret.find('span:first')
// styling formatting
.html(stripHTML($resultsecret.find('span:first').text())
.replace(/\[hl]/gi, "<code>")
.replace(/\[\/hl]/gi, "</code>")
.replace(/\[b]/gi, "<b>")
.replace(/\[\/b]/gi, "</b>")
);
$("#expiretime").html("<b>" + (new Date(json['expires'])).toLocaleString() + "</b>");
$('#totalViews').text(json['accessRecords'].length);
json['accessRecords'].forEach(function(item) {
$('#viewrecords').append(
"IP: <b>" + item['ip'] + "</b> viewed it on <b>" +
(new Date(item['time']).toLocaleString()) + "</b><br>"
);
});
} else {
showError("#error1");
}
},
onComplete: function(r, ctx, xhr) {
if (xhr.status === 404) {
console.log("API [get secret] error: 404 not found");
showError("#error2");
} else if (xhr.status !== 200) {
console.log("API [get secret] error: generic error: " + xhr.status);
showError("#error3");
}
}
});
// Copy button functionality
$('#copy').click(function() {
copyToClipboard('url');
$('body').toast({
message: "Copied URL to clipboard!",
class: "grey",
showIcon: "copy",
showProgress: "bottom",
progressUp: false,
transition: {
showMethod : "fly down",
showDuration : 500,
hideMethod : "fade",
hideDuration : 500
}
});
});
});
</script>
</body>
</html>