Recipients.vue 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850
  1. <template>
  2. <div>
  3. <div class="mb-6 flex flex-col md:flex-row justify-between md:items-center">
  4. <div class="relative">
  5. <input
  6. v-model="search"
  7. @keyup.esc="search = ''"
  8. tabindex="0"
  9. type="text"
  10. class="w-full md:w-64 appearance-none shadow bg-white text-grey-700 focus:outline-none rounded py-3 pl-3 pr-8"
  11. placeholder="Search Recipients"
  12. />
  13. <icon
  14. v-if="search"
  15. @click="search = ''"
  16. name="close-circle"
  17. class="absolute right-0 inset-y-0 w-5 h-full text-grey-300 fill-current mr-2 flex items-center cursor-pointer"
  18. />
  19. <icon
  20. v-else
  21. name="search"
  22. class="absolute right-0 inset-y-0 w-5 h-full text-grey-300 fill-current pointer-events-none mr-2 flex items-center"
  23. />
  24. </div>
  25. <div class="mt-4 md:mt-0">
  26. <button
  27. @click="addRecipientModalOpen = true"
  28. class="bg-cyan-400 hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus:outline-none ml-auto"
  29. >
  30. Add Recipient
  31. </button>
  32. </div>
  33. </div>
  34. <vue-good-table
  35. v-on:search="debounceToolips"
  36. :columns="columns"
  37. :rows="rows"
  38. :search-options="{
  39. enabled: true,
  40. skipDiacritics: true,
  41. externalQuery: search,
  42. }"
  43. :sort-options="{
  44. enabled: true,
  45. initialSortBy: { field: 'created_at', type: 'desc' },
  46. }"
  47. styleClass="vgt-table"
  48. >
  49. <template #emptystate class="flex items-center justify-center h-24 text-lg text-grey-700">
  50. No recipients found for that search!
  51. </template>
  52. <template #table-column="props">
  53. <span v-if="props.column.label == 'Key'">
  54. Key
  55. <span
  56. class="tooltip outline-none"
  57. :data-tippy-content="`Use this to attach recipients to new aliases as they are created e.g. alias+key@${domain}. You can attach multiple recipients by doing alias+2.3.4@${domain}. Separating each key by a full stop.`"
  58. >
  59. <icon name="info" class="inline-block w-4 h-4 text-grey-300 fill-current" />
  60. </span>
  61. </span>
  62. <span v-else-if="props.column.label == 'Inline Encryption'">
  63. PGP/Inline
  64. <span
  65. class="tooltip outline-none"
  66. data-tippy-content="Use inline (PGP/Inline) instead of PGP/MIME encryption for forwarded messages. Please Note: This will ONLY encrypt and forward the plain text content! Do not enable this if you wish to receive attachments or message with HTML content."
  67. >
  68. <icon name="info" class="inline-block w-4 h-4 text-grey-300 fill-current" />
  69. </span>
  70. </span>
  71. <span v-else-if="props.column.label == 'Hide Subject'">
  72. Hide Subject
  73. <span
  74. class="tooltip outline-none"
  75. data-tippy-content="Enabling this setting will hide and encrypt the email subject using protected headers. Many mail clients are able to automatically decrypt and display the subject once the email arrives."
  76. >
  77. <icon name="info" class="inline-block w-4 h-4 text-grey-300 fill-current" />
  78. </span>
  79. </span>
  80. <span v-else>
  81. {{ props.column.label }}
  82. </span>
  83. </template>
  84. <template #table-row="props">
  85. <span
  86. v-if="props.column.field == 'created_at'"
  87. class="tooltip outline-none text-sm"
  88. :data-tippy-content="$filters.formatDate(rows[props.row.originalIndex].created_at)"
  89. >{{ $filters.timeAgo(props.row.created_at) }}
  90. </span>
  91. <span v-else-if="props.column.field == 'key'">
  92. {{ props.row.key }}
  93. </span>
  94. <span v-else-if="props.column.field == 'email'">
  95. <span
  96. class="tooltip cursor-pointer outline-none"
  97. data-tippy-content="Click to copy"
  98. v-clipboard="() => rows[props.row.originalIndex].email"
  99. v-clipboard:success="clipboardSuccess"
  100. v-clipboard:error="clipboardError"
  101. >{{ $filters.truncate(props.row.email, 30) }}</span
  102. >
  103. <span
  104. v-if="isDefault(props.row.id)"
  105. class="ml-3 py-1 px-2 text-sm bg-yellow-200 text-yellow-900 rounded-full tooltip"
  106. data-tippy-content="The default recipient will be used for all aliases with no other recipients assigned"
  107. >
  108. default
  109. </span>
  110. </span>
  111. <span v-else-if="props.column.field === 'aliases'">
  112. <span
  113. v-if="props.row.aliases.length"
  114. class="tooltip outline-none"
  115. :data-tippy-content="aliasesTooltip(props.row.aliases, isDefault(props.row.id))"
  116. >{{ $filters.truncate(props.row.aliases[0].email, 40) }}
  117. <span
  118. v-if="isDefault(props.row.id) && aliasesUsingDefaultCount > 1"
  119. class="block text-grey-500 text-sm"
  120. >
  121. + {{ aliasesUsingDefaultCount - 1 }}</span
  122. >
  123. <span v-else-if="props.row.aliases.length > 1" class="block text-grey-500 text-sm">
  124. + {{ props.row.aliases.length - 1 }}</span
  125. >
  126. </span>
  127. <span v-else class="block text-grey-500 text-sm">{{ props.row.aliases.length }}</span>
  128. </span>
  129. <span
  130. v-else-if="props.column.field === 'can_reply_send'"
  131. class="flex justify-center items-center"
  132. >
  133. <Toggle
  134. v-model="rows[props.row.originalIndex].can_reply_send"
  135. @on="allowRepliesSends(props.row.id)"
  136. @off="disallowRepliesSends(props.row.id)"
  137. />
  138. </span>
  139. <span v-else-if="props.column.field === 'should_encrypt'">
  140. <span v-if="props.row.fingerprint" class="flex">
  141. <Toggle
  142. v-model="rows[props.row.originalIndex].should_encrypt"
  143. @on="turnOnEncryption(props.row.id)"
  144. @off="turnOffEncryption(props.row.id)"
  145. />
  146. <icon
  147. name="fingerprint"
  148. class="tooltip outline-none cursor-pointer block w-6 h-6 text-grey-300 fill-current mx-2"
  149. :data-tippy-content="props.row.fingerprint"
  150. v-clipboard="() => props.row.fingerprint"
  151. v-clipboard:success="clipboardSuccess"
  152. v-clipboard:error="clipboardError"
  153. />
  154. <icon
  155. name="delete"
  156. class="tooltip outline-none cursor-pointer block w-6 h-6 text-grey-300 fill-current"
  157. @click="openDeleteRecipientKeyModal(props.row)"
  158. data-tippy-content="Remove public key"
  159. />
  160. </span>
  161. <button
  162. v-else
  163. @click="openRecipientKeyModal(props.row)"
  164. class="focus:outline-none text-sm"
  165. >
  166. Add public key
  167. </button>
  168. </span>
  169. <span
  170. v-else-if="props.column.field === 'inline_encryption'"
  171. class="flex justify-center items-center"
  172. >
  173. <Toggle
  174. v-if="props.row.fingerprint"
  175. v-model="rows[props.row.originalIndex].inline_encryption"
  176. @on="turnOnInlineEncryption(props.row.id)"
  177. @off="turnOffInlineEncryption(props.row.id)"
  178. />
  179. </span>
  180. <span
  181. v-else-if="props.column.field === 'protected_headers'"
  182. class="flex justify-center items-center"
  183. >
  184. <Toggle
  185. v-if="props.row.fingerprint"
  186. v-model="rows[props.row.originalIndex].protected_headers"
  187. @on="turnOnProtectedHeaders(props.row.id)"
  188. @off="turnOffProtectedHeaders(props.row.id)"
  189. />
  190. </span>
  191. <span v-else-if="props.column.field === 'email_verified_at'">
  192. <span
  193. name="check"
  194. v-if="props.row.email_verified_at"
  195. class="py-1 px-2 bg-green-200 text-green-900 rounded-full text-xs"
  196. >
  197. verified
  198. </span>
  199. <button
  200. v-else
  201. @click="resendVerification(props.row.id)"
  202. class="focus:outline-none text-sm"
  203. :class="resendVerificationLoading ? 'cursor-not-allowed' : ''"
  204. :disabled="resendVerificationLoading"
  205. >
  206. Resend email
  207. </button>
  208. </span>
  209. <span v-else class="flex items-center justify-center outline-none" tabindex="-1">
  210. <icon
  211. v-if="!isDefault(props.row.id)"
  212. name="trash"
  213. class="block w-6 h-6 text-grey-300 fill-current cursor-pointer"
  214. @click="openDeleteModal(props.row)"
  215. />
  216. </span>
  217. </template>
  218. </vue-good-table>
  219. <Modal :open="addRecipientModalOpen" @close="addRecipientModalOpen = false">
  220. <template v-slot:title> Add new recipient </template>
  221. <template v-slot:content>
  222. <p class="mt-4 text-grey-700">
  223. Enter the individual email of the new recipient you'd like to add.
  224. </p>
  225. <p class="mt-4 text-grey-700">
  226. You will receive an email with a verification link that will expire in one hour, you can
  227. click "Resend email" to get a new one.
  228. </p>
  229. <div class="mt-6">
  230. <p v-show="errors.newRecipient" class="mb-3 text-red-500 text-sm">
  231. {{ errors.newRecipient }}
  232. </p>
  233. <input
  234. v-model="newRecipient"
  235. type="email"
  236. class="w-full appearance-none bg-grey-100 border border-transparent text-grey-700 focus:outline-none rounded p-3 mb-6"
  237. :class="errors.newRecipient ? 'border-red-500' : ''"
  238. placeholder="johndoe@example.com"
  239. autofocus
  240. />
  241. <button
  242. @click="validateNewRecipient"
  243. class="bg-cyan-400 hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus:outline-none"
  244. :class="addRecipientLoading ? 'cursor-not-allowed' : ''"
  245. :disabled="addRecipientLoading"
  246. >
  247. Add Recipient
  248. <loader v-if="addRecipientLoading" />
  249. </button>
  250. <button
  251. @click="addRecipientModalOpen = false"
  252. class="ml-4 px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus:outline-none"
  253. >
  254. Cancel
  255. </button>
  256. </div>
  257. </template>
  258. </Modal>
  259. <Modal :open="addRecipientKeyModalOpen" @close="closeRecipientKeyModal">
  260. <template v-slot:title> Add Public GPG Key </template>
  261. <template v-slot:content>
  262. <p class="mt-4 text-grey-700">Enter your <b>PUBLIC</b> key data in the text area below.</p>
  263. <p class="mt-4 text-grey-700">Make sure to remove <b>Comment:</b> and <b>Version:</b></p>
  264. <div class="mt-6">
  265. <p v-show="errors.recipientKey" class="mb-3 text-red-500 text-sm">
  266. {{ errors.recipientKey }}
  267. </p>
  268. <textarea
  269. v-model="recipientKey"
  270. class="w-full appearance-none bg-grey-100 border border-transparent text-grey-700 focus:outline-none rounded p-3 mb-6"
  271. :class="errors.recipientKey ? 'border-red-500' : ''"
  272. placeholder="Begins with '-----BEGIN PGP PUBLIC KEY BLOCK-----'"
  273. rows="10"
  274. autofocus
  275. >
  276. </textarea>
  277. <button
  278. type="button"
  279. @click="validateRecipientKey"
  280. class="bg-cyan-400 hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus:outline-none"
  281. :class="addRecipientKeyLoading ? 'cursor-not-allowed' : ''"
  282. :disabled="addRecipientKeyLoading"
  283. >
  284. Add Key
  285. <loader v-if="addRecipientKeyLoading" />
  286. </button>
  287. <button
  288. @click="closeRecipientKeyModal"
  289. class="ml-4 px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus:outline-none"
  290. >
  291. Cancel
  292. </button>
  293. </div>
  294. </template>
  295. </Modal>
  296. <Modal :open="deleteRecipientKeyModalOpen" @close="closeDeleteRecipientKeyModal">
  297. <template v-slot:title> Remove recipient public key </template>
  298. <template v-slot:content>
  299. <p class="mt-4 text-grey-700">
  300. Are you sure you want to remove the public key for this recipient? It will also be removed
  301. from any other recipients using the same key.
  302. </p>
  303. <div class="mt-6">
  304. <button
  305. type="button"
  306. @click="deleteRecipientKey(recipientKeyToDelete)"
  307. class="px-4 py-3 text-white font-semibold bg-red-500 hover:bg-red-600 border border-transparent rounded focus:outline-none"
  308. :class="deleteRecipientKeyLoading ? 'cursor-not-allowed' : ''"
  309. :disabled="deleteRecipientKeyLoading"
  310. >
  311. Remove public key
  312. <loader v-if="deleteRecipientLoading" />
  313. </button>
  314. <button
  315. @click="closeDeleteRecipientKeyModal"
  316. class="ml-4 px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus:outline-none"
  317. >
  318. Cancel
  319. </button>
  320. </div>
  321. </template>
  322. </Modal>
  323. <Modal :open="deleteRecipientModalOpen" @close="closeDeleteModal">
  324. <template v-slot:title> Delete recipient </template>
  325. <template v-slot:content>
  326. <p class="mt-4 text-grey-700">Are you sure you want to delete this recipient?</p>
  327. <div class="mt-6">
  328. <button
  329. type="button"
  330. @click="deleteRecipient(recipientToDelete)"
  331. class="px-4 py-3 text-white font-semibold bg-red-500 hover:bg-red-600 border border-transparent rounded focus:outline-none"
  332. :class="deleteRecipientLoading ? 'cursor-not-allowed' : ''"
  333. :disabled="deleteRecipientLoading"
  334. >
  335. Delete recipient
  336. <loader v-if="deleteRecipientLoading" />
  337. </button>
  338. <button
  339. @click="closeDeleteModal"
  340. class="ml-4 px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus:outline-none"
  341. >
  342. Cancel
  343. </button>
  344. </div>
  345. </template>
  346. </Modal>
  347. </div>
  348. </template>
  349. <script>
  350. import Modal from './../components/Modal.vue'
  351. import Toggle from './../components/Toggle.vue'
  352. import { roundArrow } from 'tippy.js'
  353. import 'tippy.js/dist/svg-arrow.css'
  354. import 'tippy.js/dist/tippy.css'
  355. import tippy from 'tippy.js'
  356. export default {
  357. props: {
  358. user: {
  359. type: Object,
  360. required: true,
  361. },
  362. initialRecipients: {
  363. type: Array,
  364. required: true,
  365. },
  366. aliasesUsingDefault: {
  367. type: Array,
  368. required: true,
  369. },
  370. aliasesUsingDefaultCount: {
  371. type: Number,
  372. required: true,
  373. },
  374. domain: {
  375. type: String,
  376. required: true,
  377. },
  378. },
  379. components: {
  380. Modal,
  381. Toggle,
  382. },
  383. created() {
  384. this.defaultRecipient = _.find(this.rows, ['id', this.user.default_recipient_id])
  385. this.defaultRecipient.aliases = this.defaultRecipient.aliases.concat(this.aliasesUsingDefault)
  386. },
  387. data() {
  388. return {
  389. defaultRecipient: {},
  390. newRecipient: '',
  391. recipientKey: '',
  392. search: '',
  393. addRecipientLoading: false,
  394. addRecipientModalOpen: false,
  395. recipientToDelete: null,
  396. recipientKeyToDelete: null,
  397. deleteRecipientLoading: false,
  398. deleteRecipientModalOpen: false,
  399. deleteRecipientKeyLoading: false,
  400. deleteRecipientKeyModalOpen: false,
  401. addRecipientKeyLoading: false,
  402. addRecipientKeyModalOpen: false,
  403. recipientToAddKey: {},
  404. resendVerificationLoading: false,
  405. errors: {},
  406. columns: [
  407. {
  408. label: 'Created',
  409. field: 'created_at',
  410. globalSearchDisabled: true,
  411. },
  412. {
  413. label: 'Key',
  414. field: 'key',
  415. sortable: false,
  416. type: 'number',
  417. },
  418. {
  419. label: 'Email',
  420. field: 'email',
  421. },
  422. {
  423. label: 'Recipient Aliases',
  424. field: 'aliases',
  425. sortable: true,
  426. sortFn: this.sortRecipientAliases,
  427. globalSearchDisabled: true,
  428. },
  429. {
  430. label: 'Can Reply/Send',
  431. field: 'can_reply_send',
  432. type: 'boolean',
  433. globalSearchDisabled: true,
  434. },
  435. {
  436. label: 'Encryption',
  437. field: 'should_encrypt',
  438. type: 'boolean',
  439. globalSearchDisabled: true,
  440. sortable: false,
  441. },
  442. {
  443. label: 'Inline Encryption',
  444. field: 'inline_encryption',
  445. type: 'boolean',
  446. globalSearchDisabled: true,
  447. sortable: false,
  448. },
  449. {
  450. label: 'Hide Subject',
  451. field: 'protected_headers',
  452. type: 'boolean',
  453. globalSearchDisabled: true,
  454. sortable: false,
  455. },
  456. {
  457. label: 'Verified',
  458. field: 'email_verified_at',
  459. globalSearchDisabled: true,
  460. },
  461. {
  462. label: '',
  463. field: 'actions',
  464. sortable: false,
  465. globalSearchDisabled: true,
  466. },
  467. ],
  468. rows: this.initialRecipients,
  469. tippyInstance: null,
  470. }
  471. },
  472. watch: {
  473. addRecipientKeyModalOpen: _.debounce(function () {
  474. this.addTooltips()
  475. }, 50),
  476. },
  477. methods: {
  478. addTooltips() {
  479. if (this.tippyInstance) {
  480. _.each(this.tippyInstance, instance => instance.destroy())
  481. }
  482. this.tippyInstance = tippy('.tooltip', {
  483. arrow: roundArrow,
  484. allowHTML: true,
  485. })
  486. },
  487. debounceToolips: _.debounce(function () {
  488. this.addTooltips()
  489. }, 50),
  490. aliasesTooltip(aliases, isDefault) {
  491. let ellipses =
  492. aliases.length > 5 || (isDefault && this.aliasesUsingDefaultCount > 5) ? '...' : ''
  493. return (
  494. _.reduce(_.take(aliases, 5), (list, alias) => list + `${alias.email}<br>`, '') + ellipses
  495. )
  496. },
  497. isDefault(id) {
  498. return this.user.default_recipient_id === id
  499. },
  500. validateNewRecipient(e) {
  501. this.errors = {}
  502. if (!this.newRecipient) {
  503. this.errors.newRecipient = 'Email required'
  504. } else if (!this.validEmail(this.newRecipient)) {
  505. this.errors.newRecipient = 'Valid Email required'
  506. }
  507. if (!this.errors.newRecipient) {
  508. this.addNewRecipient()
  509. }
  510. e.preventDefault()
  511. },
  512. addNewRecipient() {
  513. this.addRecipientLoading = true
  514. axios
  515. .post(
  516. '/api/v1/recipients',
  517. JSON.stringify({
  518. email: this.newRecipient,
  519. }),
  520. {
  521. headers: { 'Content-Type': 'application/json' },
  522. }
  523. )
  524. .then(({ data }) => {
  525. this.addRecipientLoading = false
  526. data.data.key = this.rows.length + 1
  527. this.rows.push(data.data)
  528. this.newRecipient = ''
  529. this.addRecipientModalOpen = false
  530. this.success('Recipient created and verification email sent')
  531. })
  532. .catch(error => {
  533. this.addRecipientLoading = false
  534. if (error.response.status === 422) {
  535. this.error(error.response.data.errors.email[0])
  536. } else if (error.response.status === 429) {
  537. this.error('You are making too many requests')
  538. } else {
  539. this.error()
  540. }
  541. })
  542. },
  543. resendVerification(id) {
  544. this.resendVerificationLoading = true
  545. axios
  546. .post(
  547. '/recipients/email/resend',
  548. JSON.stringify({
  549. recipient_id: id,
  550. }),
  551. {
  552. headers: { 'Content-Type': 'application/json' },
  553. }
  554. )
  555. .then(({ data }) => {
  556. this.resendVerificationLoading = false
  557. this.success('Verification email resent')
  558. })
  559. .catch(error => {
  560. this.resendVerificationLoading = false
  561. if (error.response.status === 429) {
  562. this.error('You can only resend the email once per minute')
  563. } else {
  564. this.error()
  565. }
  566. })
  567. },
  568. openDeleteModal(recipient) {
  569. this.deleteRecipientModalOpen = true
  570. this.recipientToDelete = recipient
  571. },
  572. closeDeleteModal() {
  573. this.deleteRecipientModalOpen = false
  574. this.recipientToDelete = null
  575. },
  576. deleteRecipient(recipient) {
  577. this.deleteRecipientLoading = true
  578. axios
  579. .delete(`/api/v1/recipients/${recipient.id}`)
  580. .then(response => {
  581. recipient.should_encrypt = false
  582. recipient.fingerprint = null
  583. this.rows = _.reject(this.rows, row => row.id === recipient.id)
  584. this.deleteRecipientModalOpen = false
  585. this.deleteRecipientLoading = false
  586. })
  587. .catch(error => {
  588. this.error()
  589. this.deleteRecipientLoading = false
  590. this.deleteRecipientModalOpen = false
  591. })
  592. },
  593. openDeleteRecipientKeyModal(recipient) {
  594. this.deleteRecipientKeyModalOpen = true
  595. this.recipientKeyToDelete = recipient
  596. },
  597. closeDeleteRecipientKeyModal() {
  598. this.deleteRecipientKeyModalOpen = false
  599. this.recipientKeyIdToDelete = null
  600. },
  601. deleteRecipientKey(recipient) {
  602. this.deleteRecipientKeyLoading = true
  603. axios
  604. .delete(`/api/v1/recipient-keys/${recipient.id}`)
  605. .then(response => {
  606. recipient.should_encrypt = false
  607. recipient.fingerprint = null
  608. this.deleteRecipientKeyModalOpen = false
  609. this.deleteRecipientKeyLoading = false
  610. })
  611. .catch(error => {
  612. if (error.response !== undefined) {
  613. this.error(error.response.data)
  614. } else {
  615. this.error()
  616. }
  617. this.deleteRecipientKeyLoading = false
  618. this.deleteRecipientKeyModalOpen = false
  619. })
  620. },
  621. validateRecipientKey(e) {
  622. this.errors = {}
  623. if (!this.recipientKey) {
  624. this.errors.recipientKey = 'Key required'
  625. } else if (!this.validKey(this.recipientKey)) {
  626. this.errors.recipientKey = 'Valid Key required'
  627. }
  628. if (!this.errors.recipientKey) {
  629. this.addRecipientKey()
  630. }
  631. e.preventDefault()
  632. },
  633. addRecipientKey() {
  634. this.addRecipientKeyLoading = true
  635. axios
  636. .patch(
  637. `/api/v1/recipient-keys/${this.recipientToAddKey.id}`,
  638. JSON.stringify({
  639. key_data: this.recipientKey,
  640. }),
  641. {
  642. headers: { 'Content-Type': 'application/json' },
  643. }
  644. )
  645. .then(({ data }) => {
  646. this.addRecipientKeyLoading = false
  647. let recipient = _.find(this.rows, ['id', this.recipientToAddKey.id])
  648. recipient.should_encrypt = data.data.should_encrypt
  649. recipient.fingerprint = data.data.fingerprint
  650. this.recipientKey = ''
  651. this.addRecipientKeyModalOpen = false
  652. this.success(
  653. `Key Successfully Added for ${this.recipientToAddKey.email}. Make sure to check the fingerprint is correct!`
  654. )
  655. })
  656. .catch(error => {
  657. this.addRecipientKeyLoading = false
  658. if (error.response !== undefined) {
  659. this.error(error.response.data)
  660. } else {
  661. this.error()
  662. }
  663. })
  664. },
  665. turnOnEncryption(id) {
  666. axios
  667. .post(
  668. `/api/v1/encrypted-recipients`,
  669. JSON.stringify({
  670. id: id,
  671. }),
  672. {
  673. headers: { 'Content-Type': 'application/json' },
  674. }
  675. )
  676. .then(response => {
  677. //
  678. })
  679. .catch(error => {
  680. this.error()
  681. })
  682. },
  683. turnOffEncryption(id) {
  684. axios
  685. .delete(`/api/v1/encrypted-recipients/${id}`)
  686. .then(response => {
  687. //
  688. })
  689. .catch(error => {
  690. this.error()
  691. })
  692. },
  693. allowRepliesSends(id) {
  694. axios
  695. .post(
  696. `/api/v1/allowed-recipients`,
  697. JSON.stringify({
  698. id: id,
  699. }),
  700. {
  701. headers: { 'Content-Type': 'application/json' },
  702. }
  703. )
  704. .then(response => {
  705. //
  706. })
  707. .catch(error => {
  708. this.error()
  709. })
  710. },
  711. disallowRepliesSends(id) {
  712. axios
  713. .delete(`/api/v1/allowed-recipients/${id}`)
  714. .then(response => {
  715. //
  716. })
  717. .catch(error => {
  718. this.error()
  719. })
  720. },
  721. turnOnInlineEncryption(id) {
  722. axios
  723. .post(
  724. `/api/v1/inline-encrypted-recipients`,
  725. JSON.stringify({
  726. id: id,
  727. }),
  728. {
  729. headers: { 'Content-Type': 'application/json' },
  730. }
  731. )
  732. .then(response => {
  733. //
  734. })
  735. .catch(error => {
  736. if (error.response.status === 422) {
  737. this.error(error.response.data)
  738. } else {
  739. this.error()
  740. }
  741. })
  742. },
  743. turnOffInlineEncryption(id) {
  744. axios
  745. .delete(`/api/v1/inline-encrypted-recipients/${id}`)
  746. .then(response => {
  747. //
  748. })
  749. .catch(error => {
  750. this.error()
  751. })
  752. },
  753. turnOnProtectedHeaders(id) {
  754. axios
  755. .post(
  756. `/api/v1/protected-headers-recipients`,
  757. JSON.stringify({
  758. id: id,
  759. }),
  760. {
  761. headers: { 'Content-Type': 'application/json' },
  762. }
  763. )
  764. .then(response => {
  765. //
  766. })
  767. .catch(error => {
  768. if (error.response.status === 422) {
  769. this.error(error.response.data)
  770. } else {
  771. this.error()
  772. }
  773. })
  774. },
  775. turnOffProtectedHeaders(id) {
  776. axios
  777. .delete(`/api/v1/protected-headers-recipients/${id}`)
  778. .then(response => {
  779. //
  780. })
  781. .catch(error => {
  782. this.error()
  783. })
  784. },
  785. openRecipientKeyModal(recipient) {
  786. this.addRecipientKeyModalOpen = true
  787. this.recipientToAddKey = recipient
  788. },
  789. closeRecipientKeyModal() {
  790. this.addRecipientKeyModalOpen = false
  791. this.recipientToAddKey = {}
  792. },
  793. validEmail(email) {
  794. let re =
  795. /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
  796. return re.test(email)
  797. },
  798. validKey(key) {
  799. let re =
  800. /-----BEGIN PGP PUBLIC KEY BLOCK-----([A-Za-z0-9+=\/\n]+)-----END PGP PUBLIC KEY BLOCK-----/i
  801. return re.test(key)
  802. },
  803. clipboardSuccess() {
  804. this.success('Copied to clipboard')
  805. },
  806. clipboardError() {
  807. this.error('Could not copy to clipboard')
  808. },
  809. success(text = '') {
  810. this.$notify({
  811. title: 'Success',
  812. text: text,
  813. type: 'success',
  814. })
  815. },
  816. error(text = 'An error has occurred, please try again later') {
  817. this.$notify({
  818. title: 'Error',
  819. text: text,
  820. type: 'error',
  821. })
  822. },
  823. },
  824. }
  825. </script>