ArticleSecurityBadge.vue 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. <!-- Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <script setup lang="ts">
  3. import { computed, ref } from 'vue'
  4. import {
  5. NotificationTypes,
  6. useNotifications,
  7. } from '#shared/components/CommonNotifications/index.ts'
  8. import { useTicketArticleRetrySecurityProcessMutation } from '#shared/entities/ticket-article/graphql/mutations/ticketArticleRetrySecurityProcess.api.ts'
  9. import type { TicketArticleSecurityState } from '#shared/graphql/types.ts'
  10. import { i18n } from '#shared/i18n.ts'
  11. import { MutationHandler } from '#shared/server/apollo/handler/index.ts'
  12. import CommonSectionPopup from '#mobile/components/CommonSectionPopup/CommonSectionPopup.vue'
  13. export interface Props {
  14. articleId: string
  15. security: TicketArticleSecurityState
  16. successClass?: string
  17. }
  18. const props = defineProps<Props>()
  19. const securityIcon = computed(() => {
  20. const { signingSuccess } = props.security
  21. if (signingSuccess === false) return 'not-signed'
  22. return 'unlock'
  23. })
  24. const hasError = computed(() => {
  25. const {
  26. signingMessage,
  27. signingSuccess,
  28. encryptionMessage,
  29. encryptionSuccess,
  30. } = props.security
  31. if (signingSuccess === false && signingMessage) return true
  32. if (encryptionSuccess === false && encryptionMessage) return true
  33. return false
  34. })
  35. const canView = computed(() => {
  36. const { signingSuccess, encryptionSuccess } = props.security
  37. return signingSuccess === true || encryptionSuccess === true
  38. })
  39. const showPopup = ref(false)
  40. const retryMutation = new MutationHandler(
  41. useTicketArticleRetrySecurityProcessMutation(() => ({
  42. variables: {
  43. articleId: props.articleId,
  44. },
  45. })),
  46. )
  47. const { notify } = useNotifications()
  48. const tryAgain = async () => {
  49. const result = await retryMutation.send()
  50. const security = result?.ticketArticleRetrySecurityProcess?.retryResult
  51. if (!security) {
  52. notify({
  53. id: 'retry-security-error',
  54. type: NotificationTypes.Error,
  55. message: __('The retried security process failed!'),
  56. timeout: 2000,
  57. })
  58. return
  59. }
  60. if (security.type !== props.security.type) {
  61. // shouldn't be possible, we only support S/MIME
  62. notify({
  63. id: 'security-mechanism-error',
  64. type: NotificationTypes.Error,
  65. message: __('Article uses different security mechanism.'),
  66. timeout: 2000,
  67. })
  68. showPopup.value = false
  69. return
  70. }
  71. let hidePopup = true
  72. if (security.signingSuccess) {
  73. notify({
  74. id: 'signature-verified',
  75. type: NotificationTypes.Success,
  76. message: __('The signature was successfully verified.'),
  77. })
  78. } else if (security.signingMessage) {
  79. notify({
  80. id: 'signature-verification-failed',
  81. type: NotificationTypes.Error,
  82. message: __('Signature verification failed! %s'),
  83. messagePlaceholder: [i18n.t(security.signingMessage)],
  84. timeout: 2000,
  85. })
  86. hidePopup = false
  87. }
  88. if (security.encryptionSuccess) {
  89. notify({
  90. id: 'decryption-success',
  91. type: NotificationTypes.Success,
  92. message: __('Decryption was successful.'),
  93. })
  94. } else if (security.encryptionMessage) {
  95. notify({
  96. id: 'decryption-failed',
  97. type: NotificationTypes.Error,
  98. message: __('Decryption failed! %s'),
  99. messagePlaceholder: [i18n.t(security.encryptionMessage)],
  100. timeout: 2000,
  101. })
  102. hidePopup = false
  103. }
  104. if (hidePopup) {
  105. showPopup.value = false
  106. }
  107. }
  108. const popupItems = computed(() =>
  109. hasError.value
  110. ? [
  111. {
  112. type: 'button' as const,
  113. label: __('Try again'),
  114. onAction: tryAgain,
  115. noHideOnSelect: true,
  116. },
  117. ]
  118. : [],
  119. )
  120. </script>
  121. <template>
  122. <button
  123. v-if="hasError"
  124. v-bind="$attrs"
  125. type="button"
  126. class="bg-yellow inline-flex h-7 grow items-center gap-1 rounded-lg px-2 py-1 text-xs font-bold text-black"
  127. @click.prevent="showPopup = !showPopup"
  128. @keydown.space.prevent="showPopup = !showPopup"
  129. >
  130. <CommonIcon :name="securityIcon" decorative size="xs" />
  131. {{ $t('Security Error') }}
  132. </button>
  133. <button
  134. v-else-if="canView"
  135. v-bind="$attrs"
  136. :class="successClass"
  137. class="inline-flex h-7 grow items-center gap-1 rounded-lg px-2 py-1"
  138. type="button"
  139. data-test-id="securityBadge"
  140. @click.prevent="showPopup = !showPopup"
  141. @keydown.space.prevent="showPopup = !showPopup"
  142. >
  143. <CommonIcon
  144. v-if="security.encryptionSuccess"
  145. name="lock"
  146. size="tiny"
  147. :label="$t('Encrypted')"
  148. />
  149. <CommonIcon
  150. v-if="security.signingSuccess"
  151. name="signed"
  152. size="tiny"
  153. :label="$t('Signed')"
  154. />
  155. </button>
  156. <CommonSectionPopup v-model:state="showPopup" :messages="popupItems">
  157. <template #header>
  158. <div
  159. class="flex flex-col items-center gap-2 border-b border-b-white/10 p-4"
  160. >
  161. <div
  162. v-if="hasError"
  163. class="text-yellow flex w-full items-center justify-center gap-1"
  164. >
  165. <CommonIcon :name="securityIcon" size="tiny" />
  166. {{ $t('Security Error') }}
  167. </div>
  168. <div
  169. v-if="security.signingMessage"
  170. :class="{
  171. 'text-orange': !hasError && security.signingSuccess === false,
  172. }"
  173. >
  174. {{ $t('Sign:') }} {{ $t(security.signingMessage) }}
  175. </div>
  176. <div
  177. v-if="security.encryptionMessage"
  178. class="break-all"
  179. :class="{
  180. 'text-orange': !hasError && security.encryptionSuccess === false,
  181. }"
  182. >
  183. {{ $t('Encryption:') }} {{ $t(security.encryptionMessage) }}
  184. </div>
  185. <div v-if="!security.encryptionMessage && !security.signingMessage">
  186. {{ $t('No security information available.') }}
  187. </div>
  188. </div>
  189. </template>
  190. </CommonSectionPopup>
  191. </template>