form.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. const _ = require('underscore');
  2. const Mn = require('backbone.marionette');
  3. const App = require('../../main');
  4. const CertificateModel = require('../../../models/certificate');
  5. const template = require('./form.ejs');
  6. const i18n = require('../../i18n');
  7. const dns_providers = sortProvidersAlphabetically(require('../../../../../global/certbot-dns-plugins'));
  8. require('jquery-serializejson');
  9. require('selectize');
  10. function sortProvidersAlphabetically(obj) {
  11. return Object.entries(obj)
  12. .sort((a,b) => a[1].name.toLowerCase() > b[1].name.toLowerCase())
  13. .reduce((result, entry) => {
  14. result[entry[0]] = entry[1];
  15. return result;
  16. }, {});
  17. }
  18. module.exports = Mn.View.extend({
  19. template: template,
  20. className: 'modal-dialog',
  21. max_file_size: 102400,
  22. ui: {
  23. form: 'form',
  24. loader_content: '.loader-content',
  25. non_loader_content: '.non-loader-content',
  26. le_error_info: '#le-error-info',
  27. domain_names: 'input[name="domain_names"]',
  28. test_domains_container: '.test-domains-container',
  29. test_domains_button: '.test-domains',
  30. buttons: '.modal-footer button',
  31. cancel: 'button.cancel',
  32. save: 'button.save',
  33. other_certificate: '#other_certificate',
  34. other_certificate_label: '#other_certificate_label',
  35. other_certificate_key: '#other_certificate_key',
  36. dns_challenge_switch: 'input[name="meta[dns_challenge]"]',
  37. dns_challenge_content: '.dns-challenge',
  38. dns_provider: 'select[name="meta[dns_provider]"]',
  39. credentials_file_content: '.credentials-file-content',
  40. dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]',
  41. propagation_seconds: 'input[name="meta[propagation_seconds]"]',
  42. other_certificate_key_label: '#other_certificate_key_label',
  43. other_intermediate_certificate: '#other_intermediate_certificate',
  44. other_intermediate_certificate_label: '#other_intermediate_certificate_label'
  45. },
  46. events: {
  47. 'change @ui.dns_challenge_switch': function () {
  48. const checked = this.ui.dns_challenge_switch.prop('checked');
  49. if (checked) {
  50. this.ui.dns_provider.prop('required', 'required');
  51. const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value;
  52. if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){
  53. this.ui.dns_provider_credentials.prop('required', 'required');
  54. }
  55. this.ui.dns_challenge_content.show();
  56. this.ui.test_domains_container.hide();
  57. } else {
  58. this.ui.dns_provider.prop('required', false);
  59. this.ui.dns_provider_credentials.prop('required', false);
  60. this.ui.dns_challenge_content.hide();
  61. this.ui.test_domains_container.show();
  62. }
  63. },
  64. 'change @ui.dns_provider': function () {
  65. const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value;
  66. if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) {
  67. this.ui.dns_provider_credentials.prop('required', 'required');
  68. this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials;
  69. this.ui.credentials_file_content.show();
  70. } else {
  71. this.ui.dns_provider_credentials.prop('required', false);
  72. this.ui.credentials_file_content.hide();
  73. }
  74. },
  75. 'click @ui.save': function (e) {
  76. e.preventDefault();
  77. this.ui.le_error_info.hide();
  78. if (!this.ui.form[0].checkValidity()) {
  79. $('<input type="submit">').hide().appendTo(this.ui.form).click().remove();
  80. $(this).removeClass('btn-loading');
  81. return;
  82. }
  83. let data = this.ui.form.serializeJSON();
  84. data.provider = this.model.get('provider');
  85. let ssl_files = [];
  86. if (data.provider === 'letsencrypt') {
  87. if (typeof data.meta === 'undefined') data.meta = {};
  88. let domain_err = false;
  89. if (!data.meta.dns_challenge) {
  90. data.domain_names.split(',').map(function (name) {
  91. if (name.match(/\*/im)) {
  92. domain_err = true;
  93. }
  94. });
  95. }
  96. if (domain_err) {
  97. alert(i18n('ssl', 'no-wildcard-without-dns'));
  98. return;
  99. }
  100. // Manipulate
  101. data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1;
  102. data.meta.dns_challenge = data.meta.dns_challenge == 1;
  103. if(!data.meta.dns_challenge){
  104. data.meta.dns_provider = undefined;
  105. data.meta.dns_provider_credentials = undefined;
  106. data.meta.propagation_seconds = undefined;
  107. } else {
  108. if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined;
  109. }
  110. if (typeof data.domain_names === 'string' && data.domain_names) {
  111. data.domain_names = data.domain_names.split(',');
  112. }
  113. } else if (data.provider === 'other' && !this.model.hasSslFiles()) {
  114. // check files are attached
  115. if (!this.ui.other_certificate[0].files.length || !this.ui.other_certificate[0].files[0].size) {
  116. alert('Certificate file is not attached');
  117. return;
  118. } else {
  119. if (this.ui.other_certificate[0].files[0].size > this.max_file_size) {
  120. alert('Certificate file is too large (> 100kb)');
  121. return;
  122. }
  123. ssl_files.push({name: 'certificate', file: this.ui.other_certificate[0].files[0]});
  124. }
  125. if (!this.ui.other_certificate_key[0].files.length || !this.ui.other_certificate_key[0].files[0].size) {
  126. alert('Certificate key file is not attached');
  127. return;
  128. } else {
  129. if (this.ui.other_certificate_key[0].files[0].size > this.max_file_size) {
  130. alert('Certificate key file is too large (> 100kb)');
  131. return;
  132. }
  133. ssl_files.push({name: 'certificate_key', file: this.ui.other_certificate_key[0].files[0]});
  134. }
  135. if (this.ui.other_intermediate_certificate[0].files.length && this.ui.other_intermediate_certificate[0].files[0].size) {
  136. if (this.ui.other_intermediate_certificate[0].files[0].size > this.max_file_size) {
  137. alert('Intermediate Certificate file is too large (> 100kb)');
  138. return;
  139. }
  140. ssl_files.push({name: 'intermediate_certificate', file: this.ui.other_intermediate_certificate[0].files[0]});
  141. }
  142. }
  143. this.ui.loader_content.show();
  144. this.ui.non_loader_content.hide();
  145. // compile file data
  146. let form_data = new FormData();
  147. if (data.provider === 'other' && ssl_files.length) {
  148. ssl_files.map(function (file) {
  149. form_data.append(file.name, file.file);
  150. });
  151. }
  152. new Promise(resolve => {
  153. if (data.provider === 'other') {
  154. resolve(App.Api.Nginx.Certificates.validate(form_data));
  155. } else {
  156. resolve();
  157. }
  158. })
  159. .then(() => {
  160. return App.Api.Nginx.Certificates.create(data);
  161. })
  162. .then(result => {
  163. this.model.set(result);
  164. // Now upload the certs if we need to
  165. if (data.provider === 'other') {
  166. return App.Api.Nginx.Certificates.upload(this.model.get('id'), form_data)
  167. .then(result => {
  168. this.model.set('meta', _.assign({}, this.model.get('meta'), result));
  169. });
  170. }
  171. })
  172. .then(() => {
  173. App.UI.closeModal(function () {
  174. App.Controller.showNginxCertificates();
  175. });
  176. })
  177. .catch(err => {
  178. let more_info = '';
  179. if (err.code === 500 && err.debug) {
  180. try{
  181. more_info = JSON.parse(err.debug).debug.stack.join("\n");
  182. } catch(e) {}
  183. }
  184. this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `<pre class="mt-3">${more_info}</pre>`:''}`;
  185. this.ui.le_error_info.show();
  186. this.ui.le_error_info[0].scrollIntoView();
  187. this.ui.loader_content.hide();
  188. this.ui.non_loader_content.show();
  189. });
  190. },
  191. 'click @ui.test_domains_button': function (e) {
  192. e.preventDefault();
  193. const domainNames = this.ui.domain_names[0].value.split(',');
  194. if (domainNames && domainNames.length > 0) {
  195. this.model.set('domain_names', domainNames);
  196. this.model.set('back_to_add', true);
  197. App.Controller.showNginxCertificateTestReachability(this.model);
  198. }
  199. },
  200. 'change @ui.domain_names': function(e){
  201. const domainNames = e.target.value.split(',');
  202. if (domainNames && domainNames.length > 0) {
  203. this.ui.test_domains_button.prop('disabled', false);
  204. } else {
  205. this.ui.test_domains_button.prop('disabled', true);
  206. }
  207. },
  208. 'change @ui.other_certificate_key': function(e){
  209. this.setFileName("other_certificate_key_label", e)
  210. },
  211. 'change @ui.other_certificate': function(e){
  212. this.setFileName("other_certificate_label", e)
  213. },
  214. 'change @ui.other_intermediate_certificate': function(e){
  215. this.setFileName("other_intermediate_certificate_label", e)
  216. }
  217. },
  218. setFileName(ui, e){
  219. this.getUI(ui).text(e.target.files[0].name)
  220. },
  221. templateContext: {
  222. getLetsencryptEmail: function () {
  223. return typeof this.meta.letsencrypt_email !== 'undefined' ? this.meta.letsencrypt_email : App.Cache.User.get('email');
  224. },
  225. getLetsencryptAgree: function () {
  226. return typeof this.meta.letsencrypt_agree !== 'undefined' ? this.meta.letsencrypt_agree : false;
  227. },
  228. getUseDnsChallenge: function () {
  229. return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false;
  230. },
  231. getDnsProvider: function () {
  232. return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null;
  233. },
  234. getDnsProviderCredentials: function () {
  235. return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : '';
  236. },
  237. getPropagationSeconds: function () {
  238. return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : '';
  239. },
  240. dns_plugins: dns_providers,
  241. },
  242. onRender: function () {
  243. this.ui.domain_names.selectize({
  244. delimiter: ',',
  245. persist: false,
  246. maxOptions: 100,
  247. create: function (input) {
  248. return {
  249. value: input,
  250. text: input
  251. };
  252. },
  253. createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/
  254. });
  255. this.ui.dns_challenge_content.hide();
  256. this.ui.credentials_file_content.hide();
  257. this.ui.loader_content.hide();
  258. this.ui.le_error_info.hide();
  259. if (this.ui.domain_names[0]) {
  260. const domainNames = this.ui.domain_names[0].value.split(',');
  261. if (!domainNames || domainNames.length === 0 || (domainNames.length === 1 && domainNames[0] === "")) {
  262. this.ui.test_domains_button.prop('disabled', true);
  263. }
  264. }
  265. },
  266. initialize: function (options) {
  267. if (typeof options.model === 'undefined' || !options.model) {
  268. this.model = new CertificateModel.Model({provider: 'letsencrypt'});
  269. }
  270. }
  271. });