Subversion Repositories oidplus

Rev

Rev 865 | Rev 982 | Go to most recent revision | View as "text/javascript" | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. /*
  2.  * OIDplus 2.0
  3.  * Copyright 2019 - 2021 Daniel Marschall, ViaThinkSoft
  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.  
  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.  
  22. // TODO: Put these settings in a "setup configuration file" (hardcoded)
  23. min_password_length = 10; // see also plugins/viathinksoft/publicPages/092_forgot_password_admin/script.js
  24. password_salt_length = 10;
  25. bcrypt_rounds = 10;
  26.  
  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++);
  32.                 if ((a | b | c) > 255) throw new Error(_L('String contains an invalid character'));
  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.  
  45. function _b64EncodeUnicode(str) {
  46.         if (str == "") {
  47.                 return "''";
  48.         } else {
  49.                 return "base64_decode('"+b64EncodeUnicode(str)+"')";
  50.         }
  51. }
  52.  
  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.  
  77. function adminGeneratePassword(password) {
  78.         var salt = generateRandomString(password_salt_length);
  79.         return salt+'$'+hexToBase64(sha3_512(salt+password));
  80. }
  81.  
  82. var bCryptWorker = null;
  83. var g_prevBcryptPw = null;
  84. var g_last_admPwdHash = null;
  85. var g_last_pwComment = null;
  86.  
  87. function rebuild() {
  88.         var pw = $("#admin_password")[0].value;
  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.                 }
  101.                 bCryptWorker = new Worker('../bcrypt_worker.js');
  102.                 bCryptWorker.postMessage([pw, bcrypt_rounds]);
  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.  
  118.         var error = false;
  119.  
  120.         if ($("#config")[0] == null) return;
  121.  
  122.         // Check 1: Has the password the correct length?
  123.         if ($("#admin_password")[0].value.length < min_password_length)
  124.         {
  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>&lt?php</b><br><br><i>// ERROR: Password must be at least '+min_password_length+' characters long</i>'; // do not translate
  127.                 error = true;
  128.         } else {
  129.                 $("#password_warn")[0].innerHTML = '';
  130.         }
  131.  
  132.         // Check 2: Do the passwords match?
  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>';
  135.                 error = true;
  136.         } else {
  137.                 $("#password_warn2")[0].innerHTML = '';
  138.         }
  139.  
  140.         // Check 3: Ask the database or captcha plugins for verification of their data
  141.         for (var i = 0; i < rebuild_callbacks.length; i++) {
  142.                 var f = rebuild_callbacks[i];
  143.                 if (!f()) {
  144.                         error = true;
  145.                 }
  146.         }
  147.  
  148.         // Continue
  149.         if (!error)
  150.         {
  151.                 var e = $("#db_plugin")[0];
  152.                 var strDatabasePlugin = e.options[e.selectedIndex].value;
  153.                 var e = $("#captcha_plugin")[0];
  154.                 var strCaptchaPlugin = e.options[e.selectedIndex].value;
  155.  
  156.                 $("#config")[0].innerHTML = '<b>&lt?php</b><br><br>' +
  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
  159.                         '<br>' +
  160.                         'OIDplus::baseConfig()->setValue(\'CONFIG_VERSION\',    2.1);<br>' +
  161.                         '<br>' +
  162.                         // Passwords are Base64 encoded to avoid that passwords can be read upon first sight,
  163.                         // e.g. if collegues are looking over your shoulder while you accidently open (and quickly close) userdata/baseconfig/config.inc.php
  164.                         'OIDplus::baseConfig()->setValue(\'ADMIN_PASSWORD\',    \'' + admPwdHash + '\'); // '+pwComment+'<br>' +
  165.                         '<br>' +
  166.                         'OIDplus::baseConfig()->setValue(\'DATABASE_PLUGIN\',   \''+strDatabasePlugin+'\');<br>';
  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) {
  171.                                 $("#config")[0].innerHTML = $("#config")[0].innerHTML + cont;
  172.                         }
  173.                 }
  174.                 $("#config")[0].innerHTML = $("#config")[0].innerHTML +
  175.                         '<br>' +
  176.                         'OIDplus::baseConfig()->setValue(\'TABLENAME_PREFIX\',  \''+$("#tablename_prefix")[0].value+'\');<br>' +
  177.                         '<br>' +
  178.                         'OIDplus::baseConfig()->setValue(\'SERVER_SECRET\',     \''+generateRandomString(32)+'\');<br>' +
  179.                         '<br>' +
  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 +
  190.                         '<br>' +
  191.                         'OIDplus::baseConfig()->setValue(\'ENFORCE_SSL\',       '+$("#enforce_ssl")[0].value+');<br>';
  192.  
  193.                 $("#config")[0].innerHTML = $("#config")[0].innerHTML.replaceAll(' ', '&nbsp;');
  194.         }
  195.  
  196.         // In case something is not good, do not allow the user to continue with the other configuration steps:
  197.         if (error) {
  198.                 $("#step2")[0].style.display = "None";
  199.                 $("#step3")[0].style.display = "None";
  200.                 $("#step4")[0].style.display = "None";
  201.         } else {
  202.                 $("#step2")[0].style.display = "Block";
  203.                 $("#step3")[0].style.display = "Block";
  204.                 $("#step4")[0].style.display = "Block";
  205.         }
  206. }
  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) {
  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.         }
  222.  
  223.         var xhr = new XMLHttpRequest();
  224.         xhr.onreadystatechange = function() {
  225.                 if (xhr.readyState === 4) {
  226.                         if (xhr.status === 200) {
  227.                                 $("#systemCheckCaption")[0].style.display = 'block';
  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!!!
  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>';
  231.                         }
  232.                 }
  233.         };
  234.  
  235.         xhr.open('GET', url);
  236.         xhr.send();
  237. }
  238.  
  239. function dbplugin_changed() {
  240.         var e = $("#db_plugin")[0];
  241.         var strDatabasePlugin = e.options[e.selectedIndex].value;
  242.  
  243.         for (var i = 0; i < plugin_combobox_change_callbacks.length; i++) {
  244.                 var f = plugin_combobox_change_callbacks[i];
  245.                 f(strDatabasePlugin);
  246.         }
  247.  
  248.         rebuild();
  249. }
  250.  
  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.  
  263. function performAccessCheck() {
  264.         $("#dirAccessWarning")[0].innerHTML = "";
  265.         checkAccess("userdata/index.html");
  266.         checkAccess("res/ATTENTION.TXT");
  267.         checkAccess("dev/index.html");
  268.         checkAccess("includes/index.html");
  269.         checkAccess("setup/includes/index.html");
  270.         //checkAccess("plugins/viathinksoft/publicPages/100_whois/whois/cli/index.html");
  271.  
  272.         if (window.location.href.toLowerCase().startsWith('https://')) {
  273.                 $("#enforce_ssl").val('OIDplus::ENFORCE_SSL_YES'); // enforce SSL (because we are already SSL)
  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
  277.                 // be very confused if the PHP detection (OIDplus::ENFORCE_SSL_AUTO) notices the open 443 port and redirects the user to a
  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) {
  283.                                         $("#enforce_ssl").val('OIDplus::ENFORCE_SSL_YES'); // enforce SSL (we checked that it loads correctly)
  284.                                 } else {
  285.                                         console.log("JS SSL detection result: "+xhr.status);
  286.                                         $("#enforce_ssl").val('OIDplus::ENFORCE_SSL_NO'); // disable SSL (because it failed, e.g. because of invalid cert or closed port)
  287.                                 }
  288.                         }
  289.                 };
  290.                 var https_url = window.location.href.replace(/^http:/i, "https:");
  291.                 xhr.open('GET', https_url);
  292.                 xhr.send();
  293.         }
  294. }
  295.  
  296. function setupOnLoad() {
  297.         rebuild();
  298.         dbplugin_changed();
  299.         captchaplugin_changed();
  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);
  328.         var str = args.shift().trim();
  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.  
  351.         tmp = tmp.replace("%%", "%");
  352.  
  353.         return tmp;
  354. }
  355.  
  356. window.onload = setupOnLoad;
  357.