cert.html 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. <div class="standardContainer">
  2. <div class="ui basic segment">
  3. <h2>TLS / SSL Certificates</h2>
  4. <p>Setup TLS cert for different domains of your reverse proxy server names</p>
  5. </div>
  6. <div class="ui divider"></div>
  7. <h4>Default Certificates</h4>
  8. <small>When there are no matching certificate for the requested server name, reverse proxy router will always fallback to this one.<br>Note that you need both of them uploaded for it to fallback properly</small></p>
  9. <table class="ui very basic unstackable celled table">
  10. <thead>
  11. <tr><th class="no-sort">Key Type</th>
  12. <th class="no-sort">Exists</th>
  13. </tr></thead>
  14. <tbody>
  15. <tr>
  16. <td><i class="globe icon"></i> Default Public Key</td>
  17. <td id="pubkeyExists"></td>
  18. </tr>
  19. <tr>
  20. <td><i class="lock icon"></i> Default Private Key</td>
  21. <td id="prikeyExists"></td>
  22. </tr>
  23. </tbody>
  24. </table>
  25. <p style="margin-bottom: 0.4em;"><i class="ui upload icon"></i> Upload Default Keypairs</p>
  26. <div class="ui buttons">
  27. <button class="ui basic grey button" onclick="uploadPublicKey();"><i class="globe icon"></i> Public Key</button>
  28. <button class="ui basic black button" onclick="uploadPrivateKey();"><i class="black lock icon"></i> Private Key</button>
  29. </div>
  30. <div class="ui divider"></div>
  31. <h4>Sub-domain Certificates</h4>
  32. <p>Provide certificates for multiple domains reverse proxy</p>
  33. <div class="ui fluid form">
  34. <div class="three fields">
  35. <div class="field">
  36. <label>Server Name (Domain)</label>
  37. <input type="text" id="certdomain" placeholder="example.com / blog.example.com">
  38. </div>
  39. <div class="field">
  40. <label>Public Key</label>
  41. <input type="file" id="pubkeySelector" onchange="handleFileSelect(event, 'pub')">
  42. </div>
  43. <div class="field">
  44. <label>Private Key</label>
  45. <input type="file" id="prikeySelector" onchange="handleFileSelect(event, 'pri')">
  46. </div>
  47. </div>
  48. <button class="ui basic button" onclick="handleDomainUploadByKeypress();"><i class="ui teal upload icon"></i> Upload</button>
  49. </div>
  50. <div id="certUploadSuccMsg" class="ui green message" style="display:none;">
  51. <i class="ui checkmark icon"></i> Certificate for domain <span id="certUploadingDomain"></span> uploaded.
  52. </div>
  53. <br>
  54. <div>
  55. <table class="ui sortable unstackable celled table">
  56. <thead>
  57. <tr><th>Domain</th>
  58. <th>Last Update</th>
  59. <th>Expire At</th>
  60. <th class="no-sort">Remove</th>
  61. </tr></thead>
  62. <tbody id="certifiedDomainList">
  63. </tbody>
  64. </table>
  65. <button class="ui basic button" onclick="initManagedDomainCertificateList();"><i class="green refresh icon"></i> Refresh List</button>
  66. </div>
  67. <div class="ui message">
  68. <h4><i class="info circle icon"></i> Sub-domain Certificates</h4>
  69. If you have 3rd or even 4th level subdomains like <code>blog.example.com</code> or <code>en.blog.example.com</code> ,
  70. depending on your certificates coverage, you might need to setup them one by one (i.e. having two seperate certificate for <code>a.example.com</code> and <code>b.example.com</code>).<br>
  71. If you have a wildcard certificate that covers <code>*.example.com</code>, you can just enter <code>example.com</code> as server name in the form below to add a certificate.
  72. </div>
  73. </div>
  74. <script>
  75. var uploadPendingPublicKey = undefined;
  76. var uploadPendingPrivateKey = undefined;
  77. //Delete the certificate by its domain
  78. function deleteCertificate(domain){
  79. if (confirm("Confirm delete certificate for " + domain + " ?")){
  80. $.ajax({
  81. url: "/api/cert/delete",
  82. method: "POST",
  83. data: {domain: domain},
  84. success: function(data){
  85. if (data.error != undefined){
  86. msgbox(data.error, false, 5000);
  87. }else{
  88. initManagedDomainCertificateList();
  89. initDefaultKeypairCheck();
  90. }
  91. }
  92. });
  93. }
  94. }
  95. //List the stored certificates
  96. function initManagedDomainCertificateList(){
  97. $("#certifiedDomainList").html("");
  98. $.get("/api/cert/list?date=true", function(data){
  99. if (data.error != undefined){
  100. msgbox(data.error, false, 5000);
  101. }else{
  102. data.forEach(entry => {
  103. $("#certifiedDomainList").append(`<tr>
  104. <td>${entry.Domain}</td>
  105. <td>${entry.LastModifiedDate}</td>
  106. <td>${entry.ExpireDate}</td>
  107. <td><button title="Delete key-pair" class="ui mini basic red icon button" onclick="deleteCertificate('${entry.Domain}');"><i class="ui red trash icon"></i></button></td>
  108. </tr>`);
  109. });
  110. if (data.length == 0){
  111. $("#certifiedDomainList").append(`<tr>
  112. <td colspan="4"><i class="ui times circle icon"></i> No valid keypairs found</td>
  113. </tr>`);
  114. }
  115. }
  116. })
  117. }
  118. initManagedDomainCertificateList();
  119. function handleDomainUploadByKeypress(){
  120. handleDomainKeysUpload(function(){
  121. $("#certUploadingDomain").text($("#certdomain").val().trim());
  122. //After uploaded, reset the file selector
  123. document.getElementById('pubkeySelector').value = '';
  124. document.getElementById('prikeySelector').value = '';
  125. document.getElementById('certdomain').value = '';
  126. uploadPendingPublicKey = undefined;
  127. uploadPendingPrivateKey = undefined;
  128. //Show succ
  129. $("#certUploadSuccMsg").stop().finish().slideDown("fast").delay(3000).slideUp("fast");
  130. initManagedDomainCertificateList();
  131. });
  132. }
  133. //Handle domain keys upload
  134. function handleDomainKeysUpload(callback=undefined){
  135. let domain = $("#certdomain").val();
  136. if (domain.trim() == ""){
  137. msgbox("Missing domain", false, 5000);
  138. return;
  139. }
  140. if (uploadPendingPublicKey && uploadPendingPrivateKey && typeof uploadPendingPublicKey === 'object' && typeof uploadPendingPrivateKey === 'object') {
  141. const publicKeyForm = new FormData();
  142. publicKeyForm.append('file', uploadPendingPublicKey, 'publicKey');
  143. const privateKeyForm = new FormData();
  144. privateKeyForm.append('file', uploadPendingPrivateKey, 'privateKey');
  145. const publicKeyRequest = new XMLHttpRequest();
  146. publicKeyRequest.open('POST', '/api/cert/upload?ktype=pub&domain=' + domain);
  147. publicKeyRequest.onreadystatechange = function() {
  148. if (publicKeyRequest.readyState === XMLHttpRequest.DONE) {
  149. if (publicKeyRequest.status !== 200) {
  150. msgbox('Error uploading public key: ' + publicKeyRequest.statusText, false, 5000);
  151. }
  152. if (callback != undefined){
  153. callback();
  154. }
  155. }
  156. };
  157. publicKeyRequest.send(publicKeyForm);
  158. const privateKeyRequest = new XMLHttpRequest();
  159. privateKeyRequest.open('POST', '/api/cert/upload?ktype=pri&domain=' + domain);
  160. privateKeyRequest.onreadystatechange = function() {
  161. if (privateKeyRequest.readyState === XMLHttpRequest.DONE) {
  162. if (privateKeyRequest.status !== 200) {
  163. msgbox('Error uploading private key: ' + privateKeyRequest.statusText, false, 5000);
  164. }
  165. if (callback != undefined){
  166. callback();
  167. }
  168. }
  169. };
  170. privateKeyRequest.send(privateKeyForm);
  171. } else {
  172. msgbox('One or both of the files is missing or not a file object');
  173. }
  174. }
  175. //Handlers for selecting domain based key pairs
  176. //ktype = {"pub" / "pri"}
  177. function handleFileSelect(event, ktype="pub") {
  178. const file = event.target.files[0];
  179. //const fileNameInput = document.getElementById('selected-file-name');
  180. if (ktype == "pub"){
  181. uploadPendingPublicKey = file;
  182. }else if (ktype == "pri"){
  183. uploadPendingPrivateKey = file;
  184. }
  185. //fileNameInput.value = file.name;
  186. }
  187. //Check if the default keypairs exists
  188. function initDefaultKeypairCheck(){
  189. $.get("/api/cert/checkDefault", function(data){
  190. let tick = `<i class="ui green checkmark icon"></i>`;
  191. let cross = `<i class="ui red times icon"></i>`;
  192. $("#pubkeyExists").html(data.DefaultPubExists?tick:cross);
  193. $("#prikeyExists").html(data.DefaultPriExists?tick:cross);
  194. });
  195. }
  196. initDefaultKeypairCheck();
  197. function uploadPrivateKey(){
  198. // create file input element
  199. const input = document.createElement('input');
  200. input.type = 'file';
  201. // add change listener to file input
  202. input.addEventListener('change', () => {
  203. // create form data object
  204. const formData = new FormData();
  205. // add selected file to form data
  206. formData.append('file', input.files[0]);
  207. // send form data to server
  208. fetch('/api/cert/upload?ktype=pri', {
  209. method: 'POST',
  210. body: formData
  211. })
  212. .then(response => {
  213. initDefaultKeypairCheck();
  214. if (response.ok) {
  215. msgbox('File upload successful!');
  216. } else {
  217. response.text().then(text => {
  218. msgbox(text, false, 5000);
  219. });
  220. //console.log(response.text());
  221. //alert('File upload failed!');
  222. }
  223. })
  224. .catch(error => {
  225. msgbox('An error occurred while uploading the file.', false, 5000);
  226. console.error(error);
  227. });
  228. });
  229. // click file input to open file selector
  230. input.click();
  231. }
  232. function uploadPublicKey() {
  233. // create file input element
  234. const input = document.createElement('input');
  235. input.type = 'file';
  236. // add change listener to file input
  237. input.addEventListener('change', () => {
  238. // create form data object
  239. const formData = new FormData();
  240. // add selected file to form data
  241. formData.append('file', input.files[0]);
  242. // send form data to server
  243. fetch('/api/cert/upload?ktype=pub', {
  244. method: 'POST',
  245. body: formData
  246. })
  247. .then(response => {
  248. if (response.ok) {
  249. msgbox('File upload successful!');
  250. initDefaultKeypairCheck();
  251. } else {
  252. response.text().then(text => {
  253. msgbox(text, false, 5000);
  254. });
  255. //console.log(response.text());
  256. //alert('File upload failed!');
  257. }
  258. })
  259. .catch(error => {
  260. msgbox('An error occurred while uploading the file.', false, 5000);
  261. console.error(error);
  262. });
  263. });
  264. // click file input to open file selector
  265. input.click();
  266. }
  267. </script>