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