xref: /openbmc/webui-vue/src/views/SecurityAndAccess/Certificates/ModalGenerateCsr.vue (revision d36ac8a8be8636ddd0e64ce005d507b21bcdeb00)
1<template>
2  <div>
3    <b-modal
4      id="generate-csr"
5      ref="modal"
6      v-model="isVisible"
7      size="lg"
8      no-stacking
9      :title="$t('pageCertificates.modal.generateACertificateSigningRequest')"
10      @ok="onOkGenerateCsrModal"
11      @cancel="resetForm"
12      @hidden="v$.$reset()"
13    >
14      <b-form id="generate-csr-form" novalidate>
15        <b-container fluid>
16          <b-row>
17            <b-col lg="9">
18              <b-row>
19                <b-col lg="6">
20                  <b-form-group
21                    :label="$t('pageCertificates.modal.certificateType')"
22                    label-for="certificate-type"
23                  >
24                    <b-form-select
25                      id="certificate-type"
26                      v-model="form.certificateType"
27                      data-test-id="modalGenerateCsr-select-certificateType"
28                      :options="certificateOptions"
29                      :state="getValidationState(v$.form.certificateType)"
30                      @input="v$.form.certificateType.$touch()"
31                    >
32                      <template #first>
33                        <b-form-select-option :value="null" disabled>
34                          {{ $t('global.form.selectAnOption') }}
35                        </b-form-select-option>
36                      </template>
37                    </b-form-select>
38                    <b-form-invalid-feedback role="alert">
39                      {{ $t('global.form.fieldRequired') }}
40                    </b-form-invalid-feedback>
41                  </b-form-group>
42                </b-col>
43                <b-col lg="6">
44                  <b-form-group
45                    :label="$t('pageCertificates.modal.country')"
46                    label-for="country"
47                  >
48                    <b-form-select
49                      id="country"
50                      v-model="form.country"
51                      data-test-id="modalGenerateCsr-select-country"
52                      :options="countryOptions"
53                      :state="getValidationState(v$.form.country)"
54                      @input="v$.form.country.$touch()"
55                    >
56                      <template #first>
57                        <b-form-select-option :value="null" disabled>
58                          {{ $t('global.form.selectAnOption') }}
59                        </b-form-select-option>
60                      </template>
61                    </b-form-select>
62                    <b-form-invalid-feedback role="alert">
63                      {{ $t('global.form.fieldRequired') }}
64                    </b-form-invalid-feedback>
65                  </b-form-group>
66                </b-col>
67              </b-row>
68              <b-row>
69                <b-col lg="6">
70                  <b-form-group
71                    :label="$t('pageCertificates.modal.state')"
72                    label-for="state"
73                  >
74                    <b-form-input
75                      id="state"
76                      v-model="form.state"
77                      type="text"
78                      data-test-id="modalGenerateCsr-input-state"
79                      :state="getValidationState(v$.form.state)"
80                    />
81                    <b-form-invalid-feedback role="alert">
82                      {{ $t('global.form.fieldRequired') }}
83                    </b-form-invalid-feedback>
84                  </b-form-group>
85                </b-col>
86                <b-col lg="6">
87                  <b-form-group
88                    :label="$t('pageCertificates.modal.city')"
89                    label-for="city"
90                  >
91                    <b-form-input
92                      id="city"
93                      v-model="form.city"
94                      type="text"
95                      data-test-id="modalGenerateCsr-input-city"
96                      :state="getValidationState(v$.form.city)"
97                    />
98                    <b-form-invalid-feedback role="alert">
99                      {{ $t('global.form.fieldRequired') }}
100                    </b-form-invalid-feedback>
101                  </b-form-group>
102                </b-col>
103              </b-row>
104              <b-row>
105                <b-col lg="6">
106                  <b-form-group
107                    :label="$t('pageCertificates.modal.companyName')"
108                    label-for="company-name"
109                  >
110                    <b-form-input
111                      id="company-name"
112                      v-model="form.companyName"
113                      type="text"
114                      data-test-id="modalGenerateCsr-input-companyName"
115                      :state="getValidationState(v$.form.companyName)"
116                    />
117                    <b-form-invalid-feedback role="alert">
118                      {{ $t('global.form.fieldRequired') }}
119                    </b-form-invalid-feedback>
120                  </b-form-group>
121                </b-col>
122                <b-col lg="6">
123                  <b-form-group
124                    :label="$t('pageCertificates.modal.companyUnit')"
125                    label-for="company-unit"
126                  >
127                    <b-form-input
128                      id="company-unit"
129                      v-model="form.companyUnit"
130                      type="text"
131                      data-test-id="modalGenerateCsr-input-companyUnit"
132                      :state="getValidationState(v$.form.companyUnit)"
133                    />
134                    <b-form-invalid-feedback role="alert">
135                      {{ $t('global.form.fieldRequired') }}
136                    </b-form-invalid-feedback>
137                  </b-form-group>
138                </b-col>
139              </b-row>
140              <b-row>
141                <b-col lg="6">
142                  <b-form-group
143                    :label="$t('pageCertificates.modal.commonName')"
144                    label-for="common-name"
145                  >
146                    <b-form-input
147                      id="common-name"
148                      v-model="form.commonName"
149                      type="text"
150                      data-test-id="modalGenerateCsr-input-commonName"
151                      :state="getValidationState(v$.form.commonName)"
152                    />
153                    <b-form-invalid-feedback role="alert">
154                      {{ $t('global.form.fieldRequired') }}
155                    </b-form-invalid-feedback>
156                  </b-form-group>
157                </b-col>
158                <b-col lg="6">
159                  <b-form-group label-for="contact-person">
160                    <template #label>
161                      {{ $t('pageCertificates.modal.contactPerson') }}
162                      -
163                      <span class="form-text d-inline">
164                        {{ $t('global.form.optional') }}
165                      </span>
166                    </template>
167                    <b-form-input
168                      id="contact-person"
169                      v-model="form.contactPerson"
170                      type="text"
171                      data-test-id="modalGenerateCsr-input-contactPerson"
172                    />
173                  </b-form-group>
174                </b-col>
175              </b-row>
176              <b-row>
177                <b-col lg="6">
178                  <b-form-group label-for="email-address">
179                    <template #label>
180                      {{ $t('pageCertificates.modal.emailAddress') }}
181                      -
182                      <span class="form-text d-inline">
183                        {{ $t('global.form.optional') }}
184                      </span>
185                    </template>
186                    <b-form-input
187                      id="email-address"
188                      v-model="form.emailAddress"
189                      type="text"
190                      data-test-id="modalGenerateCsr-input-emailAddress"
191                    />
192                  </b-form-group>
193                </b-col>
194              </b-row>
195              <b-row>
196                <b-col lg="12">
197                  <b-form-group label-for="alternate-name">
198                    <template #label>
199                      {{ $t('pageCertificates.modal.alternateName') }}
200                      -
201                      <span class="form-text d-inline">
202                        {{ $t('global.form.optional') }}
203                      </span>
204                    </template>
205                    <b-form-text id="alternate-name-help-block">
206                      {{ $t('pageCertificates.modal.alternateNameHelperText') }}
207                    </b-form-text>
208                    <b-form-tags
209                      v-model="form.alternateName"
210                      :remove-on-delete="true"
211                      :tag-pills="true"
212                      input-id="alternate-name"
213                      size="lg"
214                      separator=" "
215                      :input-attrs="{
216                        'aria-describedby': 'alternate-name-help-block',
217                      }"
218                      :duplicate-tag-text="
219                        $t('pageCertificates.modal.duplicateAlternateName')
220                      "
221                      placeholder=""
222                      data-test-id="modalGenerateCsr-input-alternateName"
223                    >
224                      <template #add-button-text>
225                        <icon-add />
226                        {{ $t('global.action.add') }}
227                      </template>
228                    </b-form-tags>
229                  </b-form-group>
230                </b-col>
231              </b-row>
232            </b-col>
233            <b-col lg="3">
234              <b-row>
235                <b-col lg="12">
236                  <p class="col-form-label">
237                    {{ $t('pageCertificates.modal.privateKey') }}
238                  </p>
239                  <b-form-group
240                    :label="$t('pageCertificates.modal.keyPairAlgorithm')"
241                    label-for="key-pair-algorithm"
242                  >
243                    <b-form-select
244                      id="key-pair-algorithm"
245                      v-model="form.keyPairAlgorithm"
246                      data-test-id="modalGenerateCsr-select-keyPairAlgorithm"
247                      :options="keyPairAlgorithmOptions"
248                      :state="getValidationState(v$.form.keyPairAlgorithm)"
249                      @input="v$.form.keyPairAlgorithm.$touch()"
250                    >
251                      <template #first>
252                        <b-form-select-option :value="null" disabled>
253                          {{ $t('global.form.selectAnOption') }}
254                        </b-form-select-option>
255                      </template>
256                    </b-form-select>
257                    <b-form-invalid-feedback role="alert">
258                      {{ $t('global.form.fieldRequired') }}
259                    </b-form-invalid-feedback>
260                  </b-form-group>
261                </b-col>
262              </b-row>
263              <b-row>
264                <b-col lg="12">
265                  <template v-if="v$.form.keyPairAlgorithm.$model === 'EC'">
266                    <b-form-group
267                      :label="$t('pageCertificates.modal.keyCurveId')"
268                      label-for="key-curve-id"
269                    >
270                      <b-form-select
271                        id="key-curve-id"
272                        v-model="form.keyCurveId"
273                        data-test-id="modalGenerateCsr-select-keyCurveId"
274                        :options="keyCurveIdOptions"
275                        :state="getValidationState(v$.form.keyCurveId)"
276                        @input="v$.form.keyCurveId.$touch()"
277                      >
278                        <template #first>
279                          <b-form-select-option :value="null" disabled>
280                            {{ $t('global.form.selectAnOption') }}
281                          </b-form-select-option>
282                        </template>
283                      </b-form-select>
284                      <b-form-invalid-feedback role="alert">
285                        {{ $t('global.form.fieldRequired') }}
286                      </b-form-invalid-feedback>
287                    </b-form-group>
288                  </template>
289                  <template v-if="v$.form.keyPairAlgorithm.$model === 'RSA'">
290                    <b-form-group
291                      :label="$t('pageCertificates.modal.keyBitLength')"
292                      label-for="key-bit-length"
293                    >
294                      <b-form-select
295                        id="key-bit-length"
296                        v-model="form.keyBitLength"
297                        data-test-id="modalGenerateCsr-select-keyBitLength"
298                        :options="keyBitLengthOptions"
299                        :state="getValidationState(v$.form.keyBitLength)"
300                        @input="v$.form.keyBitLength.$touch()"
301                      >
302                        <template #first>
303                          <b-form-select-option :value="null" disabled>
304                            {{ $t('global.form.selectAnOption') }}
305                          </b-form-select-option>
306                        </template>
307                      </b-form-select>
308                      <b-form-invalid-feedback role="alert">
309                        {{ $t('global.form.fieldRequired') }}
310                      </b-form-invalid-feedback>
311                    </b-form-group>
312                  </template>
313                </b-col>
314              </b-row>
315            </b-col>
316          </b-row>
317        </b-container>
318      </b-form>
319      <template #footer="{ ok, cancel }">
320        <b-button variant="secondary" @click="cancel()">
321          {{ $t('global.action.cancel') }}
322        </b-button>
323        <b-button
324          form="generate-csr-form"
325          type="submit"
326          variant="primary"
327          data-test-id="modalGenerateCsr-button-ok"
328          @click="ok()"
329        >
330          {{ $t('pageCertificates.generateCsr') }}
331        </b-button>
332      </template>
333    </b-modal>
334    <b-modal
335      id="csr-string"
336      v-model="showCsrString"
337      no-stacking
338      size="lg"
339      :title="$t('pageCertificates.modal.certificateSigningRequest')"
340      @hidden="onHiddenCsrStringModal"
341    >
342      {{ csrString }}
343      <template #footer>
344        <b-btn variant="secondary" @click="copyCsrString">
345          <template v-if="csrStringCopied">
346            <icon-checkmark />
347            {{ $t('global.status.copied') }}
348          </template>
349          <template v-else>
350            {{ $t('global.action.copy') }}
351          </template>
352        </b-btn>
353        <a
354          :href="
355            `data:application/json;charset=utf-8,` +
356            encodeURIComponent(`${csrString}`)
357          "
358          download="certificate.csr"
359          class="btn btn-primary"
360        >
361          {{ $t('global.action.download') }}
362        </a>
363      </template>
364    </b-modal>
365  </div>
366</template>
367
368<script>
369import IconAdd from '@carbon/icons-vue/es/add--alt/20';
370import IconCheckmark from '@carbon/icons-vue/es/checkmark/20';
371
372import { required, requiredIf } from '@vuelidate/validators';
373
374import { COUNTRY_LIST } from './CsrCountryCodes';
375import BVToastMixin from '@/components/Mixins/BVToastMixin';
376import VuelidateMixin from '@/components/Mixins/VuelidateMixin.js';
377import { useVuelidate } from '@vuelidate/core';
378import { useI18n } from 'vue-i18n';
379
380export default {
381  name: 'ModalGenerateCsr',
382  components: { IconAdd, IconCheckmark },
383  mixins: [BVToastMixin, VuelidateMixin],
384  props: {
385    modelValue: {
386      type: Boolean,
387      default: false,
388    },
389  },
390  emits: ['update:modelValue'],
391  setup() {
392    return {
393      v$: useVuelidate(),
394    };
395  },
396  data() {
397    return {
398      $t: useI18n().t,
399      showCsrString: false,
400      form: {
401        certificateType: null,
402        country: null,
403        state: null,
404        city: null,
405        companyName: null,
406        companyUnit: null,
407        commonName: null,
408        contactPerson: null,
409        emailAddress: null,
410        alternateName: [],
411        keyPairAlgorithm: null,
412        keyCurveId: null,
413        keyBitLength: null,
414      },
415      countryOptions: COUNTRY_LIST.map((country) => ({
416        text: country.label,
417        value: country.code,
418      })),
419      keyPairAlgorithmOptions: ['EC', 'RSA'],
420      keyCurveIdOptions: ['prime256v1', 'secp521r1', 'secp384r1'],
421      keyBitLengthOptions: [2048],
422      csrString: '',
423      csrStringCopied: false,
424    };
425  },
426  computed: {
427    isVisible: {
428      get() {
429        return this.modelValue;
430      },
431      set(value) {
432        this.$emit('update:modelValue', value);
433      },
434    },
435    certificateTypes() {
436      return this.$store.getters['certificates/certificateTypes'];
437    },
438    certificateOptions() {
439      return this.certificateTypes.reduce((arr, cert) => {
440        if (cert.type === 'TrustStore Certificate') return arr;
441        arr.push({
442          text: cert.label,
443          value: cert.type,
444        });
445        return arr;
446      }, []);
447    },
448  },
449  validations: {
450    form: {
451      certificateType: { required },
452      country: { required },
453      state: { required },
454      city: { required },
455      companyName: { required },
456      companyUnit: { required },
457      commonName: { required },
458      contactPerson: {},
459      emailAddress: {},
460      alternateName: {},
461      keyPairAlgorithm: { required },
462      keyCurveId: {
463        reuired: requiredIf(function (form) {
464          return form.keyPairAlgorithm === 'EC';
465        }),
466      },
467      keyBitLength: {
468        reuired: requiredIf(function (form) {
469          return form.keyPairAlgorithm === 'RSA';
470        }),
471      },
472    },
473  },
474  methods: {
475    handleSubmit() {
476      this.v$.$touch();
477      if (this.v$.$invalid) return;
478      this.$store
479        .dispatch('certificates/generateCsr', this.form)
480        .then(({ data: { CSRString } }) => {
481          this.csrString = CSRString;
482          this.showCsrString = true;
483          this.v$.$reset();
484        });
485    },
486    resetForm() {
487      for (let key of Object.keys(this.form)) {
488        if (key === 'alternateName') {
489          this.form[key] = [];
490        } else {
491          this.form[key] = null;
492        }
493      }
494    },
495    onOkGenerateCsrModal(bvModalEvt) {
496      // prevent modal close
497      bvModalEvt.preventDefault();
498      this.handleSubmit();
499    },
500    onHiddenCsrStringModal() {
501      this.csrString = '';
502      this.resetForm();
503    },
504    copyCsrString(bvModalEvt) {
505      // prevent modal close
506      bvModalEvt.preventDefault();
507      navigator.clipboard.writeText(this.csrString).then(() => {
508        // Show copied text for 5 seconds
509        this.csrStringCopied = true;
510        setTimeout(() => {
511          this.csrStringCopied = false;
512        }, 5000 /*5 seconds*/);
513      });
514    },
515  },
516};
517</script>
518