https.html 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta name="apple-mobile-web-app-capable" content="yes" />
  5. <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
  6. <meta charset="UTF-8">
  7. <meta name="theme-color" content="#4b75ff">
  8. <link rel="icon" type="image/png" href="./favicon.png" />
  9. <title>HTTPS Setup Wizard | Zoraxy</title>
  10. <link rel="stylesheet" href="../script/semantic/semantic.min.css">
  11. <script src="../script/jquery-3.6.0.min.js"></script>
  12. <script src="../../script/ao_module.js"></script>
  13. <script src="../script/semantic/semantic.min.js"></script>
  14. <script src="../script/tablesort.js"></script>
  15. <link rel="stylesheet" href="shepherd.js/dist/css/shepherd.css"/>
  16. <script src="shepherd.js/dist/js/shepherd.min.js"></script>
  17. <link rel="stylesheet" href="../main.css">
  18. </head>
  19. <body>
  20. <link rel="stylesheet" href="../darktheme.css">
  21. <script src="../script/darktheme.js"></script>
  22. <br>
  23. <div class="ui container">
  24. <div class="ui yellow message">
  25. This Wizard require both client and server connected to the internet.
  26. <br><b>
  27. As different deployment methods might involve different network environment,
  28. this wizard is only provided for assistant and the correctness of the setup is not guaranteed.
  29. If you need to verify your TLS/SSL certificate installation is valid, please seek help
  30. from IT professionals.</b>
  31. </div>
  32. <div class="ui segment">
  33. <h3 class="ui header">
  34. HTTPS (TLS/SSL Certificate) Setup Wizard
  35. <div class="sub header">This tool help you setup https with your domain / subdomain on your Zoraxy host. <br>
  36. Follow the steps below to get started</div>
  37. </h3>
  38. </div>
  39. <div class="ui segment stepContainer" step="1">
  40. <h4 class="ui header">
  41. 1. Setup Zoraxy to listen to port 80 or 443 and start listening
  42. <div class="sub header">ACME can only works on port 80 (or 80 redirected 443). Please make sure Zoarxy is listening to either one of the ports.</div>
  43. </h4>
  44. <button class="ui basic green button" onclick="checkStep(1, step1Callback, this);">Check Port Setup</button>
  45. <div class="checkResult" style="margin-top: 1em;">
  46. </div>
  47. </div>
  48. <div class="ui segment stepContainer" step="2">
  49. <h4 class="ui header">
  50. 2. If you are under NAT, setup Port Forward and forward external port 80 (<b style="color: rgb(206, 29, 29); font-weight:bolder;">and</b> 443, if you are using 443) to your Zoraxy's LAN IP address port 80 (and 443)
  51. <div class="sub header">If your Zoraxy server IP address starts with 192.168., you are mostly under a NAT router.</div>
  52. </h4>
  53. <small>The check function below will use public ip to check if port is opened. Make sure your host is reachable from the internet!<br>
  54. <b style="color: rgb(206, 29, 29); font-weight:bolder;">If you are using 443, you still need to forward port 80 for performing 80 to 443 redirect.</b></small><br>
  55. <button style="margin-top: 0.6em;" class="ui basic green button" onclick="checkStep(2, step2Callback, this);">Check Internet Reachable</button>
  56. <div class="checkResult" style="margin-top: 1em;">
  57. </div>
  58. </div>
  59. <div class="ui segment stepContainer" step="3">
  60. <h4 class="ui header">
  61. 3. Point your domain (or sub-domain) to your Zoraxy server public IP address
  62. <div class="sub header">DNS records might takes 5 - 10 minutes to take effect. If checking did not poss the first time, wait for a few minutes and retry.</div>
  63. </h4>
  64. <div class="ui fluid input">
  65. <input type="text" name="domain" placeholder="Your Domain / DNS name (e.g. dev.example.com)">
  66. </div>
  67. <br>
  68. <button class="ui basic green button" onclick="checkStep(3, step3Callback, this);">Check Domain Reachable</button>
  69. <div class="checkResult" style="margin-top: 1em;">
  70. </div>
  71. </div>
  72. <div class="ui segment stepContainer" step="4">
  73. <h4 class="ui header">
  74. 4. Request a public CA to assign you a certificate
  75. <div class="sub header">This process might take a few minutes and usually fully automated. If there are any error, you can see Zoraxy STDOUT / log for more information.</div>
  76. </h4>
  77. <div class="ui form">
  78. <div class="field">
  79. <label>Renewer Email</label>
  80. <div class="ui fluid input">
  81. <input id="caRegisterEmail" type="text" placeholder="[email protected]">
  82. </div>
  83. <small>Your CA might send expire notification to you via this email.</small>
  84. </div>
  85. <div class="field">
  86. <label>Domain(s)</label>
  87. <input id="domainsInput" type="text" placeholder="example.com" onkeyup="checkIfInputDomainIsMultiple();">
  88. <small>If you have more than one domain in a single certificate, enter the domains separated by commas (e.g. s1.dev.example.com,s2.dev.example.com)</small>
  89. </div>
  90. <div class="field multiDomainOnly" style="display:none;">
  91. <label>Matching Rule</label>
  92. <input id="filenameInput" type="text" placeholder="Enter filename (no file extension)">
  93. <small>Matching rule to let Zoraxy pick which certificate to use (Also be used as filename). Usually is the longest common suffix of the entered addresses. (e.g. dev.example.com)</small>
  94. </div>
  95. <div class="field multiDomainOnly" style="display:none;">
  96. <button class="ui basic fluid button" onclick="autoDetectMatchingRules();">Auto Detect Matching Rule</button>
  97. </div>
  98. <div class="field">
  99. <label>Certificate Authority (CA)</label>
  100. <div class="ui selection dropdown" id="ca">
  101. <input type="hidden" name="ca">
  102. <i class="dropdown icon"></i>
  103. <div class="default text">Let's Encrypt</div>
  104. <div class="menu">
  105. <div class="item" data-value="Let's Encrypt">Let's Encrypt</div>
  106. <div class="item" data-value="Buypass">Buypass</div>
  107. <div class="item" data-value="ZeroSSL">ZeroSSL</div>
  108. <!-- <div class="item" data-value="Google">Google</div> -->
  109. </div>
  110. </div>
  111. </div>
  112. <button id="obtainButton" class="ui green basic button" type="submit"><i class="green download icon"></i> Get Certificate</button>
  113. </div>
  114. <div class="ui green message" id="installSucc" style="display:none;">
  115. <i class="ui check icon"></i> Certificate for this domain has been installed. Visit the TLS/SSL tab for advance operations.
  116. </div>
  117. </div>
  118. <script>
  119. $(".dropdown").dropdown();
  120. function checkIfInputDomainIsMultiple(){
  121. var inputDomains = $("#domainsInput").val();
  122. if (inputDomains.includes(",")){
  123. $(".multiDomainOnly").show();
  124. }else{
  125. $(".multiDomainOnly").hide();
  126. }
  127. }
  128. //Grab the longest common suffix of all domains
  129. //not that smart technically
  130. function autoDetectMatchingRules(){
  131. var domainsString = $("#domainsInput").val();
  132. if (!domainsString.includes(",")){
  133. return domainsString;
  134. }
  135. let domains = domainsString.split(",");
  136. //Clean out any spacing between commas
  137. for (var i = 0; i < domains.length; i++){
  138. domains[i] = domains[i].trim();
  139. }
  140. function getLongestCommonSuffix(strings) {
  141. if (strings.length === 0) {
  142. return ''; // Return an empty string if the array is empty
  143. }
  144. var sortedStrings = strings.slice().sort(); // Create a sorted copy of the array
  145. var firstString = sortedStrings[0];
  146. var lastString = sortedStrings[sortedStrings.length - 1];
  147. var suffix = '';
  148. var minLength = Math.min(firstString.length, lastString.length);
  149. for (var i = 0; i < minLength; i++) {
  150. if (firstString[firstString.length - 1 - i] !== lastString[lastString.length - 1 - i]) {
  151. break; // Stop iterating if characters don't match
  152. }
  153. suffix = firstString[firstString.length - 1 - i] + suffix;
  154. }
  155. return suffix;
  156. }
  157. let longestSuffix = getLongestCommonSuffix(domains);
  158. //Check if the suffix is a valid domain
  159. if (longestSuffix.substr(0,1) == "."){
  160. //Trim off the first dot
  161. longestSuffix = longestSuffix.substr(1);
  162. }
  163. if (!longestSuffix.includes(".")){
  164. alert("Auto Detect failed: Multiple Domains");
  165. return;
  166. }
  167. $("#filenameInput").val(longestSuffix);
  168. }
  169. $("#obtainButton").click(function() {
  170. $("#obtainButton").addClass("loading").addClass("disabled");
  171. obtainCertificate();
  172. });
  173. // Obtain certificate from API
  174. function obtainCertificate() {
  175. var domains = $("#domainsInput").val();
  176. var filename = $("#filenameInput").val();
  177. var email = $("#caRegisterEmail").val();
  178. if (email == ""){
  179. alert("ACME renew email is not set")
  180. return;
  181. }
  182. if (filename.trim() == "" && !domains.includes(",")){
  183. //Zoraxy filename are the matching name for domains.
  184. //Use the same as domains
  185. filename = domains;
  186. }else if (filename != "" && !domains.includes(",")){
  187. //Invalid settings. Force the filename to be same as domain
  188. //if there are only 1 domain
  189. filename = domains;
  190. }else{
  191. alert("Filename cannot be empty for certs containing multiple domains.")
  192. return;
  193. }
  194. var ca = $("#ca").dropdown("get value");
  195. $.ajax({
  196. url: "/api/acme/obtainCert",
  197. method: "GET",
  198. data: {
  199. domains: domains,
  200. filename: filename,
  201. email: email,
  202. ca: ca,
  203. },
  204. success: function(response) {
  205. $("#obtainButton").removeClass("loading").removeClass("disabled");
  206. if (response.error) {
  207. console.log("Error:", response.error);
  208. // Show error message
  209. alert(response.error);
  210. $("#installSucc").hide();
  211. } else {
  212. console.log("Certificate installed successfully");
  213. // Show success message
  214. //alert("Certificate installed successfully");
  215. $("#installSucc").show();
  216. }
  217. },
  218. error: function(error) {
  219. $("#obtainButton").removeClass("loading").removeClass("disabled");
  220. console.log("Failed to renewed certificate:", error);
  221. }
  222. });
  223. }
  224. function step3Callback(resultContainer, data){
  225. if (data == true){
  226. $(resultContainer).html(`<div class="ui green message">
  227. <i class="ui check icon"></i> Domain is reachable and seems there is a HTTP server listening. Please move on to the next step.
  228. </div>`);
  229. }else{
  230. $(resultContainer).html(`<div class="ui red message">
  231. <i class="ui remove icon"></i> Domain is reachable but there are no HTTP server listening<br>
  232. Make sure you have point to the correct IP address and there are not another proxy server above Zoraxy.
  233. </div>`);
  234. }
  235. }
  236. function step2Callback(resultContainer, data){
  237. if (data == true){
  238. $(resultContainer).html(`<div class="ui green message">
  239. <i class="ui check icon"></i> HTTP Server reachable from public IP address. Please move on to the next step.
  240. </div>`);
  241. }else{
  242. $(resultContainer).html(`<div class="ui red message">
  243. <i class="ui remove icon"></i> Server unreachable from public IP address<br>
  244. Check if you have correct NAT port forward setup in your home router, firewall and make sure network is reachable from the public internet.
  245. </div>`);
  246. }
  247. }
  248. function step1Callback(resultContainer, data){
  249. if (data == true){
  250. $(resultContainer).html(`<div class="ui green message">
  251. <i class="ui check icon"></i> Supported listening port. Please move on to the next step.
  252. </div>`);
  253. }else{
  254. $(resultContainer).html(`<div class="ui red message">
  255. <i class="ui remove icon"></i> Invalid listening port.<br>
  256. Go to Status tab and change the listening port to 80 or 443
  257. </div>`);
  258. }
  259. }
  260. function getStepContainerByNo(stepNo){
  261. let targetStepContainer = undefined;
  262. $(".stepContainer").each(function(){
  263. if ($(this).attr("step") == stepNo){
  264. let thisContainer = $(this);
  265. targetStepContainer = thisContainer;
  266. }
  267. });
  268. return targetStepContainer;
  269. }
  270. function checkStep(stepNo, callback, btn){
  271. let targetContainer = getStepContainerByNo(stepNo);
  272. $(btn).addClass("loading");
  273. //Load all the inputs
  274. data = {};
  275. $(targetContainer).find("input").each(function(){
  276. let key = $(this).attr("name")
  277. if (key != undefined){
  278. data[key] = $(this).val();
  279. }
  280. });
  281. $.ajax({
  282. url: "/api/acme/wizard?step=" + stepNo,
  283. data: data,
  284. success: function(data){
  285. $(btn).removeClass("loading");
  286. if (data.error != undefined){
  287. $(targetContainer).find(".checkResult").html(`
  288. <div class="ui red message">${data.error}</div>`);
  289. }else{
  290. callback($(targetContainer).find(".checkResult"), data);
  291. }
  292. },
  293. error: function(){
  294. $(btn).removeClass("loading");
  295. $(targetContainer).find(".checkResult").html(`
  296. <div class="ui red message">Server return an Unknown Error</div>`);
  297. }
  298. });
  299. }
  300. </script>
  301. </body>
  302. </html>