forked from jstrieb/link-lock
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
118 lines (103 loc) · 3.97 KB
/
index.js
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
function error(text) {
document.querySelector(".form").style.display = "none";
document.querySelector(".error").style.display = "inherit";
document.querySelector("#errortext").innerText = `Error: ${text}`;
}
// Run when the <body> loads
function main() {
if (window.location.hash) {
document.querySelector(".form").style.display = "inherit";
document.querySelector("#password").value = "";
document.querySelector("#password").focus();
document.querySelector(".error").style.display = "none";
document.querySelector("#errortext").innerText = "";
// Fail if the b64 library or API was not loaded
if (!("b64" in window)) {
error("Base64 library not loaded.");
return;
}
if (!("apiVersions" in window)) {
error("API library not loaded.");
return;
}
// Try to get page data from the URL if possible
const hash = window.location.hash.slice(1);
let params;
try {
params = JSON.parse(b64.decode(hash));
} catch {
error("The link appears corrupted.");
return;
}
// Check that all required parameters encoded in the URL are present
if (!("v" in params && "e" in params)) {
error("The link appears corrupted. The encoded URL is missing necessary parameters.");
return;
}
// Check that the version in the parameters is valid
if (!(params["v"] in apiVersions)) {
error("Unsupported API version. The link may be corrupted.");
return;
}
const api = apiVersions[params["v"]];
// Get values for decryption
const encrypted = b64.base64ToBinary(params["e"]);
const salt = "s" in params ? b64.base64ToBinary(params["s"]) : null;
const iv = "i" in params ? b64.base64ToBinary(params["i"]) : null;
let hint, password;
if ("h" in params) {
hint = params["h"];
document.querySelector("#hint").innerText = "Hint: " + hint;
}
const unlockButton = document.querySelector("#unlockbutton");
const passwordPrompt = document.querySelector("#password");
passwordPrompt.addEventListener("keypress", (e) => {
if (e.key === "Enter") {
unlockButton.click();
}
});
unlockButton.addEventListener("click", async () => {
password = passwordPrompt.value;
// Decrypt and redirect if possible
let url;
try {
url = await api.decrypt(encrypted, password, salt, iv);
} catch {
// Password is incorrect.
error("Password is incorrect.");
// Set the "decrypt without redirect" URL appropriately
document.querySelector("#no-redirect").href =
`https://jstrieb.github.io/link-lock/decrypt/#${hash}`;
// Set the "create hidden bookmark" URL appropriately
document.querySelector("#hidden").href =
`https://jstrieb.github.io/link-lock/hidden/#${hash}`;
return;
}
try {
// Extra check to make sure the URL is valid. Probably shouldn't fail.
let urlObj = new URL(url);
// Prevent XSS by making sure only HTTP URLs are used. Also allow magnet
// links for password-protected torrents.
if (!(urlObj.protocol == "http:"
|| urlObj.protocol == "https:"
|| urlObj.protocol == "magnet:")) {
error(`The link uses a non-hypertext protocol, which is not allowed. `
+ `The URL begins with "${urlObj.protocol}" and may be malicious.`);
return;
}
// IMPORTANT NOTE: must use window.location.href instead of the (in my
// opinion more proper) window.location.replace. If you use replace, it
// causes Chrome to change the icon of a bookmarked link to update it to
// the unlocked destination. This is dangerous information leakage.
window.location.href = url;
} catch {
error("A corrupted URL was encrypted. Cannot redirect.");
console.log(url);
return;
}
});
} else {
// Otherwise redirect to the creator
window.location.replace("./create");
}
}