Rev 865 | Rev 982 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
5 | daniel-mar | 1 | /* |
2 | * OIDplus 2.0 |
||
511 | daniel-mar | 3 | * Copyright 2019 - 2021 Daniel Marschall, ViaThinkSoft |
5 | daniel-mar | 4 | * |
5 | * Licensed under the Apache License, Version 2.0 (the "License"); |
||
6 | * you may not use this file except in compliance with the License. |
||
7 | * You may obtain a copy of the License at |
||
8 | * |
||
9 | * http://www.apache.org/licenses/LICENSE-2.0 |
||
10 | * |
||
11 | * Unless required by applicable law or agreed to in writing, software |
||
12 | * distributed under the License is distributed on an "AS IS" BASIS, |
||
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||
14 | * See the License for the specific language governing permissions and |
||
15 | * limitations under the License. |
||
16 | */ |
||
17 | |||
362 | daniel-mar | 18 | // DEFAULT_LANGUAGE will be set by setup.js.php |
19 | // language_messages will be set by setup.js.php |
||
20 | // language_tblprefix will be set by setup.js.php |
||
21 | |||
695 | daniel-mar | 22 | // TODO: Put these settings in a "setup configuration file" (hardcoded) |
635 | daniel-mar | 23 | min_password_length = 10; // see also plugins/viathinksoft/publicPages/092_forgot_password_admin/script.js |
421 | daniel-mar | 24 | password_salt_length = 10; |
695 | daniel-mar | 25 | bcrypt_rounds = 10; |
80 | daniel-mar | 26 | |
2 | daniel-mar | 27 | function btoa(bin) { |
28 | var tableStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; |
||
29 | var table = tableStr.split(""); |
||
30 | for (var i = 0, j = 0, len = bin.length / 3, base64 = []; i < len; ++i) { |
||
31 | var a = bin.charCodeAt(j++), b = bin.charCodeAt(j++), c = bin.charCodeAt(j++); |
||
362 | daniel-mar | 32 | if ((a | b | c) > 255) throw new Error(_L('String contains an invalid character')); |
2 | daniel-mar | 33 | base64[base64.length] = table[a >> 2] + table[((a << 4) & 63) | (b >> 4)] + |
34 | (isNaN(b) ? "=" : table[((b << 2) & 63) | (c >> 6)]) + |
||
35 | (isNaN(b + c) ? "=" : table[c & 63]); |
||
36 | } |
||
37 | return base64.join(""); |
||
38 | }; |
||
39 | |||
40 | function hexToBase64(str) { |
||
41 | return btoa(String.fromCharCode.apply(null, |
||
42 | str.replace(/\r|\n/g, "").replace(/([\da-fA-F]{2}) ?/g, "0x$1 ").replace(/ +$/, "").split(" "))); |
||
43 | } |
||
44 | |||
865 | daniel-mar | 45 | function _b64EncodeUnicode(str) { |
46 | if (str == "") { |
||
47 | return "''"; |
||
48 | } else { |
||
49 | return "base64_decode('"+b64EncodeUnicode(str)+"')"; |
||
50 | } |
||
51 | } |
||
52 | |||
2 | daniel-mar | 53 | function b64EncodeUnicode(str) { |
54 | // first we use encodeURIComponent to get percent-encoded UTF-8, |
||
55 | // then we convert the percent encodings into raw bytes which |
||
56 | // can be fed into btoa. |
||
57 | return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, |
||
58 | function toSolidBytes(match, p1) { |
||
59 | return String.fromCharCode('0x' + p1); |
||
60 | })); |
||
61 | } |
||
62 | |||
63 | function generateRandomString(length) { |
||
64 | var charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", |
||
65 | retVal = ""; |
||
66 | for (var i = 0, n = charset.length; i < length; ++i) { |
||
67 | retVal += charset.charAt(Math.floor(Math.random() * n)); |
||
68 | } |
||
69 | return retVal; |
||
70 | } |
||
71 | |||
72 | String.prototype.replaceAll = function(search, replacement) { |
||
73 | var target = this; |
||
74 | return target.replace(new RegExp(search, 'g'), replacement); |
||
75 | }; |
||
76 | |||
421 | daniel-mar | 77 | function adminGeneratePassword(password) { |
78 | var salt = generateRandomString(password_salt_length); |
||
79 | return salt+'$'+hexToBase64(sha3_512(salt+password)); |
||
80 | } |
||
81 | |||
456 | daniel-mar | 82 | var bCryptWorker = null; |
83 | var g_prevBcryptPw = null; |
||
84 | var g_last_admPwdHash = null; |
||
85 | var g_last_pwComment = null; |
||
86 | |||
2 | daniel-mar | 87 | function rebuild() { |
561 | daniel-mar | 88 | var pw = $("#admin_password")[0].value; |
456 | daniel-mar | 89 | |
90 | if (pw != g_prevBcryptPw) { |
||
91 | // sync call to calculate SHA3 |
||
92 | var admPwdHash = adminGeneratePassword(pw); |
||
93 | var pwComment = 'salted, base64 encoded SHA3-512 hash'; |
||
94 | doRebuild(admPwdHash, pwComment); |
||
95 | |||
96 | // "async" call to calculate bcrypt (via web-worker) |
||
97 | if (bCryptWorker != null) { |
||
98 | g_prevBcryptPw = null; |
||
99 | bCryptWorker.terminate(); |
||
100 | } |
||
599 | daniel-mar | 101 | bCryptWorker = new Worker('../bcrypt_worker.js'); |
695 | daniel-mar | 102 | bCryptWorker.postMessage([pw, bcrypt_rounds]); |
456 | daniel-mar | 103 | bCryptWorker.onmessage = function (event) { |
104 | var admPwdHash = event.data; |
||
105 | var pwComment = 'bcrypt encoded hash'; |
||
106 | doRebuild(admPwdHash, pwComment); |
||
107 | g_prevBcryptPw = pw; |
||
108 | }; |
||
109 | } else { |
||
110 | doRebuild(g_last_admPwdHash, g_last_pwComment); |
||
111 | } |
||
112 | } |
||
113 | |||
114 | function doRebuild(admPwdHash, pwComment) { |
||
115 | g_last_admPwdHash = admPwdHash; |
||
116 | g_last_pwComment = pwComment; |
||
117 | |||
2 | daniel-mar | 118 | var error = false; |
119 | |||
561 | daniel-mar | 120 | if ($("#config")[0] == null) return; |
150 | daniel-mar | 121 | |
81 | daniel-mar | 122 | // Check 1: Has the password the correct length? |
561 | daniel-mar | 123 | if ($("#admin_password")[0].value.length < min_password_length) |
81 | daniel-mar | 124 | { |
561 | daniel-mar | 125 | $("#password_warn")[0].innerHTML = '<font color="red">'+_L('Password must be at least %1 characters long',min_password_length)+'</font>'; |
126 | $("#config")[0].innerHTML = '<b><?php</b><br><br><i>// ERROR: Password must be at least '+min_password_length+' characters long</i>'; // do not translate |
||
81 | daniel-mar | 127 | error = true; |
128 | } else { |
||
561 | daniel-mar | 129 | $("#password_warn")[0].innerHTML = ''; |
81 | daniel-mar | 130 | } |
131 | |||
132 | // Check 2: Do the passwords match? |
||
561 | daniel-mar | 133 | if ($("#admin_password")[0].value != $("#admin_password2")[0].value) { |
134 | $("#password_warn2")[0].innerHTML = '<font color="red">'+_L('The passwords do not match!')+'</font>'; |
||
2 | daniel-mar | 135 | error = true; |
136 | } else { |
||
561 | daniel-mar | 137 | $("#password_warn2")[0].innerHTML = ''; |
2 | daniel-mar | 138 | } |
139 | |||
702 | daniel-mar | 140 | // Check 3: Ask the database or captcha plugins for verification of their data |
150 | daniel-mar | 141 | for (var i = 0; i < rebuild_callbacks.length; i++) { |
142 | var f = rebuild_callbacks[i]; |
||
143 | if (!f()) { |
||
144 | error = true; |
||
145 | } |
||
2 | daniel-mar | 146 | } |
81 | daniel-mar | 147 | |
149 | daniel-mar | 148 | // Continue |
81 | daniel-mar | 149 | if (!error) |
150 | { |
||
561 | daniel-mar | 151 | var e = $("#db_plugin")[0]; |
702 | daniel-mar | 152 | var strDatabasePlugin = e.options[e.selectedIndex].value; |
153 | var e = $("#captcha_plugin")[0]; |
||
154 | var strCaptchaPlugin = e.options[e.selectedIndex].value; |
||
150 | daniel-mar | 155 | |
561 | daniel-mar | 156 | $("#config")[0].innerHTML = '<b><?php</b><br><br>' + |
362 | daniel-mar | 157 | '<i>// To renew this file, please run setup/ in your browser.</i><br>' + // do not translate |
158 | '<i>// If you don\'t want to run setup again, you can also change most of the settings directly in this file.</i><br>' + // do not translate |
||
2 | daniel-mar | 159 | '<br>' + |
261 | daniel-mar | 160 | 'OIDplus::baseConfig()->setValue(\'CONFIG_VERSION\', 2.1);<br>' + |
2 | daniel-mar | 161 | '<br>' + |
150 | daniel-mar | 162 | // Passwords are Base64 encoded to avoid that passwords can be read upon first sight, |
294 | daniel-mar | 163 | // e.g. if collegues are looking over your shoulder while you accidently open (and quickly close) userdata/baseconfig/config.inc.php |
456 | daniel-mar | 164 | 'OIDplus::baseConfig()->setValue(\'ADMIN_PASSWORD\', \'' + admPwdHash + '\'); // '+pwComment+'<br>' + |
2 | daniel-mar | 165 | '<br>' + |
702 | daniel-mar | 166 | 'OIDplus::baseConfig()->setValue(\'DATABASE_PLUGIN\', \''+strDatabasePlugin+'\');<br>'; |
150 | daniel-mar | 167 | for (var i = 0; i < rebuild_config_callbacks.length; i++) { |
168 | var f = rebuild_config_callbacks[i]; |
||
169 | var cont = f(); |
||
170 | if (cont) { |
||
561 | daniel-mar | 171 | $("#config")[0].innerHTML = $("#config")[0].innerHTML + cont; |
150 | daniel-mar | 172 | } |
173 | } |
||
561 | daniel-mar | 174 | $("#config")[0].innerHTML = $("#config")[0].innerHTML + |
2 | daniel-mar | 175 | '<br>' + |
561 | daniel-mar | 176 | 'OIDplus::baseConfig()->setValue(\'TABLENAME_PREFIX\', \''+$("#tablename_prefix")[0].value+'\');<br>' + |
2 | daniel-mar | 177 | '<br>' + |
261 | daniel-mar | 178 | 'OIDplus::baseConfig()->setValue(\'SERVER_SECRET\', \''+generateRandomString(32)+'\');<br>' + |
2 | daniel-mar | 179 | '<br>' + |
702 | daniel-mar | 180 | 'OIDplus::baseConfig()->setValue(\'CAPTCHA_PLUGIN\', \''+strCaptchaPlugin+'\');<br>'; |
181 | for (var i = 0; i < captcha_rebuild_config_callbacks.length; i++) { |
||
182 | var f = captcha_rebuild_config_callbacks[i]; |
||
183 | var cont = f(); |
||
184 | if (cont) { |
||
185 | $("#config")[0].innerHTML = $("#config")[0].innerHTML + cont; |
||
186 | } |
||
187 | } |
||
188 | |||
189 | $("#config")[0].innerHTML = $("#config")[0].innerHTML + |
||
80 | daniel-mar | 190 | '<br>' + |
561 | daniel-mar | 191 | 'OIDplus::baseConfig()->setValue(\'ENFORCE_SSL\', '+$("#enforce_ssl")[0].value+');<br>'; |
2 | daniel-mar | 192 | |
561 | daniel-mar | 193 | $("#config")[0].innerHTML = $("#config")[0].innerHTML.replaceAll(' ', ' '); |
2 | daniel-mar | 194 | } |
195 | |||
196 | // In case something is not good, do not allow the user to continue with the other configuration steps: |
||
197 | if (error) { |
||
561 | daniel-mar | 198 | $("#step2")[0].style.display = "None"; |
199 | $("#step3")[0].style.display = "None"; |
||
200 | $("#step4")[0].style.display = "None"; |
||
2 | daniel-mar | 201 | } else { |
561 | daniel-mar | 202 | $("#step2")[0].style.display = "Block"; |
203 | $("#step3")[0].style.display = "Block"; |
||
204 | $("#step4")[0].style.display = "Block"; |
||
2 | daniel-mar | 205 | } |
206 | } |
||
362 | daniel-mar | 207 | |
208 | function RemoveLastDirectoryPartOf(the_url) { |
||
209 | var the_arr = the_url.split('/'); |
||
210 | if (the_arr.pop() == '') the_arr.pop(); |
||
211 | return( the_arr.join('/') ); |
||
212 | } |
||
213 | |||
214 | function checkAccess(dir) { |
||
706 | daniel-mar | 215 | if (!dir.toLowerCase().startsWith('https:') && !dir.toLowerCase().startsWith('http:')) { |
216 | var url = '../' + dir; |
||
217 | var visibleUrl = RemoveLastDirectoryPartOf(window.location.href) + '/' + dir; // xhr.responseURL not available in IE |
||
218 | } else { |
||
219 | var url = dir; |
||
220 | var visibleUrl = dir; // xhr.responseURL not available in IE |
||
221 | } |
||
362 | daniel-mar | 222 | |
223 | var xhr = new XMLHttpRequest(); |
||
224 | xhr.onreadystatechange = function() { |
||
225 | if (xhr.readyState === 4) { |
||
226 | if (xhr.status === 200) { |
||
561 | daniel-mar | 227 | $("#systemCheckCaption")[0].style.display = 'block'; |
861 | daniel-mar | 228 | // TODO: At my Ubuntu VM, after fixing an issue with Apache2, the messages still appeared. I had to click the links and press F5 for each one |
229 | // THERE MUST BE NO CACHE!!! |
||
561 | daniel-mar | 230 | $("#dirAccessWarning")[0].innerHTML = $("#dirAccessWarning")[0].innerHTML + _L('Attention: The following directory is world-readable: %1 ! You need to configure your web server to restrict access to this directory! (For Apache see <i>.htaccess</i>, for Microsoft IIS see <i>web.config</i>, for Nginx see <i>nginx.conf</i>).','<a target="_blank" href="'+url+'">'+visibleUrl+'</a>') + '<br>'; |
362 | daniel-mar | 231 | } |
232 | } |
||
233 | }; |
||
234 | |||
235 | xhr.open('GET', url); |
||
236 | xhr.send(); |
||
237 | } |
||
238 | |||
239 | function dbplugin_changed() { |
||
561 | daniel-mar | 240 | var e = $("#db_plugin")[0]; |
702 | daniel-mar | 241 | var strDatabasePlugin = e.options[e.selectedIndex].value; |
362 | daniel-mar | 242 | |
243 | for (var i = 0; i < plugin_combobox_change_callbacks.length; i++) { |
||
244 | var f = plugin_combobox_change_callbacks[i]; |
||
702 | daniel-mar | 245 | f(strDatabasePlugin); |
362 | daniel-mar | 246 | } |
247 | |||
248 | rebuild(); |
||
249 | } |
||
250 | |||
702 | daniel-mar | 251 | function captchaplugin_changed() { |
252 | var e = $("#captcha_plugin")[0]; |
||
253 | var strCaptchaPlugin = e.options[e.selectedIndex].value; |
||
254 | |||
255 | for (var i = 0; i < captcha_plugin_combobox_change_callbacks.length; i++) { |
||
256 | var f = captcha_plugin_combobox_change_callbacks[i]; |
||
257 | f(strCaptchaPlugin); |
||
258 | } |
||
259 | |||
260 | rebuild(); |
||
261 | } |
||
262 | |||
362 | daniel-mar | 263 | function performAccessCheck() { |
561 | daniel-mar | 264 | $("#dirAccessWarning")[0].innerHTML = ""; |
362 | daniel-mar | 265 | checkAccess("userdata/index.html"); |
476 | daniel-mar | 266 | checkAccess("res/ATTENTION.TXT"); |
362 | daniel-mar | 267 | checkAccess("dev/index.html"); |
268 | checkAccess("includes/index.html"); |
||
448 | daniel-mar | 269 | checkAccess("setup/includes/index.html"); |
635 | daniel-mar | 270 | //checkAccess("plugins/viathinksoft/publicPages/100_whois/whois/cli/index.html"); |
974 | daniel-mar | 271 | |
706 | daniel-mar | 272 | if (window.location.href.toLowerCase().startsWith('https://')) { |
974 | daniel-mar | 273 | $("#enforce_ssl").val('OIDplus::ENFORCE_SSL_YES'); // enforce SSL (because we are already SSL) |
706 | daniel-mar | 274 | } else { |
275 | // Do a SSL detection now. |
||
276 | // This is important because on XAMPP the SSL cert is invalid (self signed) and the user might |
||
974 | daniel-mar | 277 | // be very confused if the PHP detection (OIDplus::ENFORCE_SSL_AUTO) notices the open 443 port and redirects the user to a |
706 | daniel-mar | 278 | // big warning page of the browser! |
279 | var xhr = new XMLHttpRequest(); |
||
280 | xhr.onreadystatechange = function() { |
||
281 | if (xhr.readyState === 4) { |
||
282 | if (xhr.status === 200) { |
||
974 | daniel-mar | 283 | $("#enforce_ssl").val('OIDplus::ENFORCE_SSL_YES'); // enforce SSL (we checked that it loads correctly) |
706 | daniel-mar | 284 | } else { |
285 | console.log("JS SSL detection result: "+xhr.status); |
||
974 | daniel-mar | 286 | $("#enforce_ssl").val('OIDplus::ENFORCE_SSL_NO'); // disable SSL (because it failed, e.g. because of invalid cert or closed port) |
706 | daniel-mar | 287 | } |
288 | } |
||
289 | }; |
||
290 | var https_url = window.location.href.replace(/^http:/i, "https:"); |
||
291 | xhr.open('GET', https_url); |
||
292 | xhr.send(); |
||
293 | } |
||
362 | daniel-mar | 294 | } |
295 | |||
296 | function setupOnLoad() { |
||
297 | rebuild(); |
||
298 | dbplugin_changed(); |
||
702 | daniel-mar | 299 | captchaplugin_changed(); |
362 | daniel-mar | 300 | performAccessCheck(); |
301 | } |
||
302 | |||
303 | function getCookie(cname) { |
||
304 | // Source: https://www.w3schools.com/js/js_cookies.asp |
||
305 | var name = cname + "="; |
||
306 | var decodedCookie = decodeURIComponent(document.cookie); |
||
307 | var ca = decodedCookie.split(';'); |
||
308 | for(var i = 0; i <ca.length; i++) { |
||
309 | var c = ca[i]; |
||
310 | while (c.charAt(0) == ' ') { |
||
311 | c = c.substring(1); |
||
312 | } |
||
313 | if (c.indexOf(name) == 0) { |
||
314 | return c.substring(name.length, c.length); |
||
315 | } |
||
316 | } |
||
317 | return undefined; |
||
318 | } |
||
319 | |||
320 | function getCurrentLang() { |
||
321 | // Note: If the argument "?lang=" is used, PHP will automatically set a Cookie, so it is OK when we only check for the cookie |
||
322 | var lang = getCookie('LANGUAGE'); |
||
323 | return (typeof lang != "undefined") ? lang : DEFAULT_LANGUAGE; |
||
324 | } |
||
325 | |||
326 | function _L() { |
||
327 | var args = Array.prototype.slice.call(arguments); |
||
506 | daniel-mar | 328 | var str = args.shift().trim(); |
362 | daniel-mar | 329 | |
330 | var tmp = ""; |
||
331 | if (typeof language_messages[getCurrentLang()] == "undefined") { |
||
332 | tmp = str; |
||
333 | } else { |
||
334 | var msg = language_messages[getCurrentLang()][str]; |
||
335 | if (typeof msg != "undefined") { |
||
336 | tmp = msg; |
||
337 | } else { |
||
338 | tmp = str; |
||
339 | } |
||
340 | } |
||
341 | |||
342 | tmp = tmp.replace('###', language_tblprefix); |
||
343 | |||
344 | var n = 1; |
||
345 | while (args.length > 0) { |
||
346 | var val = args.shift(); |
||
347 | tmp = tmp.replace("%"+n, val); |
||
348 | n++; |
||
349 | } |
||
350 | |||
370 | daniel-mar | 351 | tmp = tmp.replace("%%", "%"); |
352 | |||
362 | daniel-mar | 353 | return tmp; |
354 | } |
||
355 | |||
356 | window.onload = setupOnLoad; |