Recipients.vue 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865
  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.native="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. @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. <div slot="emptystate" class="flex items-center justify-center h-24 text-lg text-grey-700">
  50. No recipients found for that search!
  51. </div>
  52. <template slot="table-column" slot-scope="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 encyption for forwarded messages. Please Note: This will ONLY encrypt and forward the plain text 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 slot="table-row" slot-scope="props">
  85. <span
  86. v-if="props.column.field == 'created_at'"
  87. class="tooltip outline-none text-sm"
  88. :data-tippy-content="rows[props.row.originalIndex].created_at | formatDate"
  89. >{{ props.row.created_at | timeAgo }}
  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. >{{ props.row.email | truncate(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. >{{ props.row.aliases[0].email | truncate(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.native="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.native="openDeleteModal(props.row)"
  215. />
  216. </span>
  217. </template>
  218. </vue-good-table>
  219. <Modal :open="addRecipientModalOpen" @close="addRecipientModalOpen = false">
  220. <div class="max-w-lg w-full bg-white rounded-lg shadow-2xl p-6">
  221. <h2
  222. class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
  223. >
  224. Add new recipient
  225. </h2>
  226. <p class="mt-4 text-grey-700">
  227. Enter the individual email of the new recipient you'd like to add.
  228. </p>
  229. <p class="mt-4 text-grey-700">
  230. You will receive an email with a verification link that will expire in one hour, you can
  231. click "Resend email" to get a new one.
  232. </p>
  233. <div class="mt-6">
  234. <p v-show="errors.newRecipient" class="mb-3 text-red-500 text-sm">
  235. {{ errors.newRecipient }}
  236. </p>
  237. <input
  238. v-model="newRecipient"
  239. type="email"
  240. class="w-full appearance-none bg-grey-100 border border-transparent text-grey-700 focus:outline-none rounded p-3 mb-6"
  241. :class="errors.newRecipient ? 'border-red-500' : ''"
  242. placeholder="johndoe@example.com"
  243. autofocus
  244. />
  245. <button
  246. @click="validateNewRecipient"
  247. class="bg-cyan-400 hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus:outline-none"
  248. :class="addRecipientLoading ? 'cursor-not-allowed' : ''"
  249. :disabled="addRecipientLoading"
  250. >
  251. Add Recipient
  252. <loader v-if="addRecipientLoading" />
  253. </button>
  254. <button
  255. @click="addRecipientModalOpen = false"
  256. 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"
  257. >
  258. Cancel
  259. </button>
  260. </div>
  261. </div>
  262. </Modal>
  263. <Modal :open="addRecipientKeyModalOpen" @close="closeRecipientKeyModal">
  264. <div class="max-w-lg w-full bg-white rounded-lg shadow-2xl p-6">
  265. <h2
  266. class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
  267. >
  268. Add Public GPG Key
  269. </h2>
  270. <p class="mt-4 text-grey-700">Enter your <b>PUBLIC</b> key data in the text area below.</p>
  271. <p class="mt-4 text-grey-700">Make sure to remove <b>Comment:</b> and <b>Version:</b></p>
  272. <div class="mt-6">
  273. <p v-show="errors.recipientKey" class="mb-3 text-red-500 text-sm">
  274. {{ errors.recipientKey }}
  275. </p>
  276. <textarea
  277. v-model="recipientKey"
  278. class="w-full appearance-none bg-grey-100 border border-transparent text-grey-700 focus:outline-none rounded p-3 mb-6"
  279. :class="errors.recipientKey ? 'border-red-500' : ''"
  280. placeholder="Begins with '-----BEGIN PGP PUBLIC KEY BLOCK-----'"
  281. rows="10"
  282. autofocus
  283. >
  284. </textarea>
  285. <button
  286. type="button"
  287. @click="validateRecipientKey"
  288. class="bg-cyan-400 hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus:outline-none"
  289. :class="addRecipientKeyLoading ? 'cursor-not-allowed' : ''"
  290. :disabled="addRecipientKeyLoading"
  291. >
  292. Add Key
  293. <loader v-if="addRecipientKeyLoading" />
  294. </button>
  295. <button
  296. @click="closeRecipientKeyModal"
  297. 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"
  298. >
  299. Cancel
  300. </button>
  301. </div>
  302. </div>
  303. </Modal>
  304. <Modal :open="deleteRecipientKeyModalOpen" @close="closeDeleteRecipientKeyModal">
  305. <div class="max-w-lg w-full bg-white rounded-lg shadow-2xl p-6">
  306. <h2
  307. class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
  308. >
  309. Remove recipient public key
  310. </h2>
  311. <p class="mt-4 text-grey-700">
  312. Are you sure you want to remove the public key for this recipient? It will also be removed
  313. from any other recipients using the same key.
  314. </p>
  315. <div class="mt-6">
  316. <button
  317. type="button"
  318. @click="deleteRecipientKey(recipientKeyToDelete)"
  319. class="px-4 py-3 text-white font-semibold bg-red-500 hover:bg-red-600 border border-transparent rounded focus:outline-none"
  320. :class="deleteRecipientKeyLoading ? 'cursor-not-allowed' : ''"
  321. :disabled="deleteRecipientKeyLoading"
  322. >
  323. Remove public key
  324. <loader v-if="deleteRecipientLoading" />
  325. </button>
  326. <button
  327. @click="closeDeleteRecipientKeyModal"
  328. 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"
  329. >
  330. Cancel
  331. </button>
  332. </div>
  333. </div>
  334. </Modal>
  335. <Modal :open="deleteRecipientModalOpen" @close="closeDeleteModal">
  336. <div class="max-w-lg w-full bg-white rounded-lg shadow-2xl p-6">
  337. <h2
  338. class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
  339. >
  340. Delete recipient
  341. </h2>
  342. <p class="mt-4 text-grey-700">Are you sure you want to delete this recipient?</p>
  343. <div class="mt-6">
  344. <button
  345. type="button"
  346. @click="deleteRecipient(recipientToDelete)"
  347. class="px-4 py-3 text-white font-semibold bg-red-500 hover:bg-red-600 border border-transparent rounded focus:outline-none"
  348. :class="deleteRecipientLoading ? 'cursor-not-allowed' : ''"
  349. :disabled="deleteRecipientLoading"
  350. >
  351. Delete recipient
  352. <loader v-if="deleteRecipientLoading" />
  353. </button>
  354. <button
  355. @click="closeDeleteModal"
  356. 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"
  357. >
  358. Cancel
  359. </button>
  360. </div>
  361. </div>
  362. </Modal>
  363. </div>
  364. </template>
  365. <script>
  366. import Modal from './../components/Modal.vue'
  367. import Toggle from './../components/Toggle.vue'
  368. import { roundArrow } from 'tippy.js'
  369. import 'tippy.js/dist/svg-arrow.css'
  370. import 'tippy.js/dist/tippy.css'
  371. import tippy from 'tippy.js'
  372. export default {
  373. props: {
  374. user: {
  375. type: Object,
  376. required: true,
  377. },
  378. initialRecipients: {
  379. type: Array,
  380. required: true,
  381. },
  382. aliasesUsingDefault: {
  383. type: Array,
  384. required: true,
  385. },
  386. aliasesUsingDefaultCount: {
  387. type: Number,
  388. required: true,
  389. },
  390. domain: {
  391. type: String,
  392. required: true,
  393. },
  394. },
  395. components: {
  396. Modal,
  397. Toggle,
  398. },
  399. created() {
  400. this.defaultRecipient = _.find(this.rows, ['id', this.user.default_recipient_id])
  401. this.defaultRecipient.aliases = this.defaultRecipient.aliases.concat(this.aliasesUsingDefault)
  402. },
  403. data() {
  404. return {
  405. defaultRecipient: {},
  406. newRecipient: '',
  407. recipientKey: '',
  408. search: '',
  409. addRecipientLoading: false,
  410. addRecipientModalOpen: false,
  411. recipientToDelete: null,
  412. recipientKeyToDelete: null,
  413. deleteRecipientLoading: false,
  414. deleteRecipientModalOpen: false,
  415. deleteRecipientKeyLoading: false,
  416. deleteRecipientKeyModalOpen: false,
  417. addRecipientKeyLoading: false,
  418. addRecipientKeyModalOpen: false,
  419. recipientToAddKey: {},
  420. resendVerificationLoading: false,
  421. errors: {},
  422. columns: [
  423. {
  424. label: 'Created',
  425. field: 'created_at',
  426. globalSearchDisabled: true,
  427. },
  428. {
  429. label: 'Key',
  430. field: 'key',
  431. sortable: false,
  432. type: 'number',
  433. },
  434. {
  435. label: 'Email',
  436. field: 'email',
  437. },
  438. {
  439. label: 'Recipient Aliases',
  440. field: 'aliases',
  441. sortable: true,
  442. sortFn: this.sortRecipientAliases,
  443. globalSearchDisabled: true,
  444. },
  445. {
  446. label: 'Can Reply/Send',
  447. field: 'can_reply_send',
  448. type: 'boolean',
  449. globalSearchDisabled: true,
  450. },
  451. {
  452. label: 'Encryption',
  453. field: 'should_encrypt',
  454. type: 'boolean',
  455. globalSearchDisabled: true,
  456. },
  457. {
  458. label: 'Inline Encryption',
  459. field: 'inline_encryption',
  460. type: 'boolean',
  461. globalSearchDisabled: true,
  462. sortable: false,
  463. },
  464. {
  465. label: 'Hide Subject',
  466. field: 'protected_headers',
  467. type: 'boolean',
  468. globalSearchDisabled: true,
  469. sortable: false,
  470. },
  471. {
  472. label: 'Verified',
  473. field: 'email_verified_at',
  474. globalSearchDisabled: true,
  475. },
  476. {
  477. label: '',
  478. field: 'actions',
  479. sortable: false,
  480. globalSearchDisabled: true,
  481. },
  482. ],
  483. rows: this.initialRecipients,
  484. tippyInstance: null,
  485. }
  486. },
  487. watch: {
  488. addRecipientKeyModalOpen: _.debounce(function () {
  489. this.addTooltips()
  490. }, 50),
  491. },
  492. methods: {
  493. addTooltips() {
  494. if (this.tippyInstance) {
  495. _.each(this.tippyInstance, instance => instance.destroy())
  496. }
  497. this.tippyInstance = tippy('.tooltip', {
  498. arrow: roundArrow,
  499. allowHTML: true,
  500. })
  501. },
  502. debounceToolips: _.debounce(function () {
  503. this.addTooltips()
  504. }, 50),
  505. aliasesTooltip(aliases, isDefault) {
  506. let ellipses =
  507. aliases.length > 5 || (isDefault && this.aliasesUsingDefaultCount > 5) ? '...' : ''
  508. return (
  509. _.reduce(_.take(aliases, 5), (list, alias) => list + `${alias.email}<br>`, '') + ellipses
  510. )
  511. },
  512. isDefault(id) {
  513. return this.user.default_recipient_id === id
  514. },
  515. validateNewRecipient(e) {
  516. this.errors = {}
  517. if (!this.newRecipient) {
  518. this.errors.newRecipient = 'Email required'
  519. } else if (!this.validEmail(this.newRecipient)) {
  520. this.errors.newRecipient = 'Valid Email required'
  521. }
  522. if (!this.errors.newRecipient) {
  523. this.addNewRecipient()
  524. }
  525. e.preventDefault()
  526. },
  527. addNewRecipient() {
  528. this.addRecipientLoading = true
  529. axios
  530. .post(
  531. '/api/v1/recipients',
  532. JSON.stringify({
  533. email: this.newRecipient,
  534. }),
  535. {
  536. headers: { 'Content-Type': 'application/json' },
  537. }
  538. )
  539. .then(({ data }) => {
  540. this.addRecipientLoading = false
  541. data.data.key = this.rows.length + 1
  542. this.rows.push(data.data)
  543. this.newRecipient = ''
  544. this.addRecipientModalOpen = false
  545. this.success('Recipient created and verification email sent')
  546. })
  547. .catch(error => {
  548. this.addRecipientLoading = false
  549. if (error.response.status === 422) {
  550. this.error(error.response.data.errors.email[0])
  551. } else if (error.response.status === 429) {
  552. this.error('You are making too many requests')
  553. } else {
  554. this.error()
  555. }
  556. })
  557. },
  558. resendVerification(id) {
  559. this.resendVerificationLoading = true
  560. axios
  561. .post(
  562. '/recipients/email/resend',
  563. JSON.stringify({
  564. recipient_id: id,
  565. }),
  566. {
  567. headers: { 'Content-Type': 'application/json' },
  568. }
  569. )
  570. .then(({ data }) => {
  571. this.resendVerificationLoading = false
  572. this.success('Verification email resent')
  573. })
  574. .catch(error => {
  575. this.resendVerificationLoading = false
  576. if (error.response.status === 429) {
  577. this.error('You can only resend the email once per minute')
  578. } else {
  579. this.error()
  580. }
  581. })
  582. },
  583. openDeleteModal(recipient) {
  584. this.deleteRecipientModalOpen = true
  585. this.recipientToDelete = recipient
  586. },
  587. closeDeleteModal() {
  588. this.deleteRecipientModalOpen = false
  589. this.recipientToDelete = null
  590. },
  591. deleteRecipient(recipient) {
  592. this.deleteRecipientLoading = true
  593. axios
  594. .delete(`/api/v1/recipients/${recipient.id}`)
  595. .then(response => {
  596. recipient.should_encrypt = false
  597. recipient.fingerprint = null
  598. this.rows = _.reject(this.rows, row => row.id === recipient.id)
  599. this.deleteRecipientModalOpen = false
  600. this.deleteRecipientLoading = false
  601. })
  602. .catch(error => {
  603. this.error()
  604. this.deleteRecipientLoading = false
  605. this.deleteRecipientModalOpen = false
  606. })
  607. },
  608. openDeleteRecipientKeyModal(recipient) {
  609. this.deleteRecipientKeyModalOpen = true
  610. this.recipientKeyToDelete = recipient
  611. },
  612. closeDeleteRecipientKeyModal() {
  613. this.deleteRecipientKeyModalOpen = false
  614. this.recipientKeyIdToDelete = null
  615. },
  616. deleteRecipientKey(recipient) {
  617. this.deleteRecipientKeyLoading = true
  618. axios
  619. .delete(`/api/v1/recipient-keys/${recipient.id}`)
  620. .then(response => {
  621. recipient.should_encrypt = false
  622. recipient.fingerprint = null
  623. this.deleteRecipientKeyModalOpen = false
  624. this.deleteRecipientKeyLoading = false
  625. })
  626. .catch(error => {
  627. if (error.response !== undefined) {
  628. this.error(error.response.data)
  629. } else {
  630. this.error()
  631. }
  632. this.deleteRecipientKeyLoading = false
  633. this.deleteRecipientKeyModalOpen = false
  634. })
  635. },
  636. validateRecipientKey(e) {
  637. this.errors = {}
  638. if (!this.recipientKey) {
  639. this.errors.recipientKey = 'Key required'
  640. } else if (!this.validKey(this.recipientKey)) {
  641. this.errors.recipientKey = 'Valid Key required'
  642. }
  643. if (!this.errors.recipientKey) {
  644. this.addRecipientKey()
  645. }
  646. e.preventDefault()
  647. },
  648. addRecipientKey() {
  649. this.addRecipientKeyLoading = true
  650. axios
  651. .patch(
  652. `/api/v1/recipient-keys/${this.recipientToAddKey.id}`,
  653. JSON.stringify({
  654. key_data: this.recipientKey,
  655. }),
  656. {
  657. headers: { 'Content-Type': 'application/json' },
  658. }
  659. )
  660. .then(({ data }) => {
  661. this.addRecipientKeyLoading = false
  662. let recipient = _.find(this.rows, ['id', this.recipientToAddKey.id])
  663. recipient.should_encrypt = data.data.should_encrypt
  664. recipient.fingerprint = data.data.fingerprint
  665. this.recipientKey = ''
  666. this.addRecipientKeyModalOpen = false
  667. this.success(
  668. `Key Successfully Added for ${this.recipientToAddKey.email}. Make sure to check the fingerprint is correct!`
  669. )
  670. })
  671. .catch(error => {
  672. this.addRecipientKeyLoading = false
  673. if (error.response !== undefined) {
  674. this.error(error.response.data)
  675. } else {
  676. this.error()
  677. }
  678. })
  679. },
  680. turnOnEncryption(id) {
  681. axios
  682. .post(
  683. `/api/v1/encrypted-recipients`,
  684. JSON.stringify({
  685. id: id,
  686. }),
  687. {
  688. headers: { 'Content-Type': 'application/json' },
  689. }
  690. )
  691. .then(response => {
  692. //
  693. })
  694. .catch(error => {
  695. this.error()
  696. })
  697. },
  698. turnOffEncryption(id) {
  699. axios
  700. .delete(`/api/v1/encrypted-recipients/${id}`)
  701. .then(response => {
  702. //
  703. })
  704. .catch(error => {
  705. this.error()
  706. })
  707. },
  708. allowRepliesSends(id) {
  709. axios
  710. .post(
  711. `/api/v1/allowed-recipients`,
  712. JSON.stringify({
  713. id: id,
  714. }),
  715. {
  716. headers: { 'Content-Type': 'application/json' },
  717. }
  718. )
  719. .then(response => {
  720. //
  721. })
  722. .catch(error => {
  723. this.error()
  724. })
  725. },
  726. disallowRepliesSends(id) {
  727. axios
  728. .delete(`/api/v1/allowed-recipients/${id}`)
  729. .then(response => {
  730. //
  731. })
  732. .catch(error => {
  733. this.error()
  734. })
  735. },
  736. turnOnInlineEncryption(id) {
  737. axios
  738. .post(
  739. `/api/v1/inline-encrypted-recipients`,
  740. JSON.stringify({
  741. id: id,
  742. }),
  743. {
  744. headers: { 'Content-Type': 'application/json' },
  745. }
  746. )
  747. .then(response => {
  748. //
  749. })
  750. .catch(error => {
  751. if (error.response.status === 422) {
  752. this.error(error.response.data)
  753. } else {
  754. this.error()
  755. }
  756. })
  757. },
  758. turnOffInlineEncryption(id) {
  759. axios
  760. .delete(`/api/v1/inline-encrypted-recipients/${id}`)
  761. .then(response => {
  762. //
  763. })
  764. .catch(error => {
  765. this.error()
  766. })
  767. },
  768. turnOnProtectedHeaders(id) {
  769. axios
  770. .post(
  771. `/api/v1/protected-headers-recipients`,
  772. JSON.stringify({
  773. id: id,
  774. }),
  775. {
  776. headers: { 'Content-Type': 'application/json' },
  777. }
  778. )
  779. .then(response => {
  780. //
  781. })
  782. .catch(error => {
  783. if (error.response.status === 422) {
  784. this.error(error.response.data)
  785. } else {
  786. this.error()
  787. }
  788. })
  789. },
  790. turnOffProtectedHeaders(id) {
  791. axios
  792. .delete(`/api/v1/protected-headers-recipients/${id}`)
  793. .then(response => {
  794. //
  795. })
  796. .catch(error => {
  797. this.error()
  798. })
  799. },
  800. openRecipientKeyModal(recipient) {
  801. this.addRecipientKeyModalOpen = true
  802. this.recipientToAddKey = recipient
  803. },
  804. closeRecipientKeyModal() {
  805. this.addRecipientKeyModalOpen = false
  806. this.recipientToAddKey = {}
  807. },
  808. validEmail(email) {
  809. let re =
  810. /^(([^<>()\[\]\\.,;:\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,}))$/
  811. return re.test(email)
  812. },
  813. validKey(key) {
  814. let re =
  815. /-----BEGIN PGP PUBLIC KEY BLOCK-----([A-Za-z0-9+=\/\n]+)-----END PGP PUBLIC KEY BLOCK-----/i
  816. return re.test(key)
  817. },
  818. clipboardSuccess() {
  819. this.success('Copied to clipboard')
  820. },
  821. clipboardError() {
  822. this.error('Could not copy to clipboard')
  823. },
  824. success(text = '') {
  825. this.$notify({
  826. title: 'Success',
  827. text: text,
  828. type: 'success',
  829. })
  830. },
  831. error(text = 'An error has occurred, please try again later') {
  832. this.$notify({
  833. title: 'Error',
  834. text: text,
  835. type: 'error',
  836. })
  837. },
  838. },
  839. }
  840. </script>