@rapidmx/web-client 0.30.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (402) hide show
  1. package/apps/admin/audit-log/index.tsx +3 -3
  2. package/apps/admin/branding/index.tsx +3 -3
  3. package/apps/admin/data-requests/index.tsx +18 -7
  4. package/apps/admin/distribution-lists/[uid].tsx +3 -3
  5. package/apps/admin/distribution-lists/index.tsx +3 -3
  6. package/apps/admin/distribution-lists/new/index.tsx +3 -3
  7. package/apps/admin/domains/[uid].tsx +6 -5
  8. package/apps/admin/domains/index.tsx +3 -3
  9. package/apps/admin/domains/new/index.tsx +3 -3
  10. package/apps/admin/escrow-scopes/[uid].tsx +11 -5
  11. package/apps/admin/escrow-scopes/index.tsx +3 -3
  12. package/apps/admin/escrow-scopes/new/index.tsx +9 -3
  13. package/apps/admin/index.tsx +4 -4
  14. package/apps/admin/ingest-queue/index.tsx +3 -3
  15. package/apps/admin/mailboxes/[uid].tsx +15 -7
  16. package/apps/admin/quarantine/index.tsx +4 -4
  17. package/apps/admin/retention-policy/index.tsx +3 -3
  18. package/apps/admin/signing-certificates/index.tsx +3 -3
  19. package/apps/admin/transport-rules/[uid].tsx +4 -4
  20. package/apps/admin/transport-rules/index.tsx +3 -3
  21. package/apps/admin/transport-rules/new/index.tsx +3 -3
  22. package/apps/escrow/matters/[uid].tsx +26 -2
  23. package/apps/escrow/matters/new/index.tsx +4 -0
  24. package/apps/shared/calendar/calendarLiveUpdates.ts +11 -10
  25. package/apps/shared/calendar/useCalendarReminders.ts +2 -0
  26. package/apps/shared/components/admin/ActionAlert.tsx +32 -0
  27. package/apps/shared/components/admin/diagnostics/InformationPanel.tsx +3 -3
  28. package/apps/shared/components/admin/diagnostics/InstalledPlugins.tsx +1 -1
  29. package/apps/shared/components/admin/diagnostics/LogsPanel.tsx +1 -1
  30. package/apps/shared/components/admin/diagnostics/RuntimePanel.tsx +1 -1
  31. package/apps/shared/components/admin/diagnostics/SystemPanel.tsx +1 -1
  32. package/apps/shared/components/admin/diagnostics/diagnosticsApi.ts +2 -2
  33. package/apps/shared/components/admin/distributionLists/MemberListCard.tsx +4 -4
  34. package/apps/shared/components/admin/elevation.ts +19 -0
  35. package/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.tsx +58 -3
  36. package/apps/shared/components/admin/layout/AdminShell.tsx +6 -1
  37. package/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.tsx +3 -3
  38. package/apps/shared/components/admin/mailboxes/EscrowScopeCard.tsx +4 -4
  39. package/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.tsx +4 -4
  40. package/apps/shared/components/admin/mailboxes/ResourceSettingsCard.tsx +3 -3
  41. package/apps/shared/components/admin/mailboxes/ShareAccessCard.tsx +6 -6
  42. package/apps/shared/components/admin/settings/BrandingForm.tsx +22 -9
  43. package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +4 -4
  44. package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +33 -4
  45. package/apps/shared/components/admin/settings/LoadedSettingsForm.tsx +3 -3
  46. package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +8 -3
  47. package/apps/shared/components/admin/settings/MailboxPolicyForm.tsx +3 -3
  48. package/apps/shared/components/admin/settings/PluginsManager.tsx +195 -46
  49. package/apps/shared/components/admin/settings/RetentionPolicyForm.tsx +25 -12
  50. package/apps/shared/components/admin/setup/EscrowSetupStep.tsx +40 -7
  51. package/apps/shared/components/admin/setup/SetupWizard.tsx +8 -6
  52. package/apps/shared/components/admin/signOut.ts +3 -0
  53. package/apps/shared/components/admin/usePagedList.tsx +2 -2
  54. package/apps/shared/components/calendar/DescriptionEditor.tsx +2 -1
  55. package/apps/shared/components/calendar/EventDetails.tsx +34 -8
  56. package/apps/shared/components/calendar/EventEditor.tsx +90 -40
  57. package/apps/shared/components/calendar/EventFormParts.tsx +5 -2
  58. package/apps/shared/components/calendar/EventMatchNav.tsx +14 -2
  59. package/apps/shared/components/calendar/EventModal.tsx +25 -1
  60. package/apps/shared/components/calendar/FindATime.tsx +10 -2
  61. package/apps/shared/components/calendar/ListView.tsx +99 -25
  62. package/apps/shared/components/calendar/RecurrenceEditor.tsx +19 -8
  63. package/apps/shared/components/calendar/SplitDayView.tsx +14 -18
  64. package/apps/shared/components/calendar/TaskQuickForm.tsx +3 -2
  65. package/apps/shared/components/calendar/TimeGridView.tsx +35 -19
  66. package/apps/shared/components/calendar/gridLayout.ts +94 -0
  67. package/apps/shared/components/calendar/layout/CalendarShell.tsx +1 -1
  68. package/apps/shared/components/calendar/timePicker.ts +5 -2
  69. package/apps/shared/components/contacts/ContactCard.tsx +15 -4
  70. package/apps/shared/components/contacts/ContactDetailPane.tsx +54 -15
  71. package/apps/shared/components/contacts/ContactForm.tsx +61 -17
  72. package/apps/shared/components/contacts/ContactPhotoEditor.tsx +88 -0
  73. package/apps/shared/components/contacts/ContactPhotoField.tsx +36 -39
  74. package/apps/shared/components/contacts/layout/ContactsShell.tsx +1 -1
  75. package/apps/shared/components/contacts/participantDetails.ts +18 -1
  76. package/apps/shared/components/layout/AppShell.tsx +9 -0
  77. package/apps/shared/components/layout/KeyEnrollmentGate.tsx +9 -4
  78. package/apps/shared/components/layout/MailboxProvisioning.tsx +31 -3
  79. package/apps/shared/components/layout/RecoveryCodeUnlock.tsx +4 -2
  80. package/apps/shared/components/mail/ConversationList.tsx +49 -6
  81. package/apps/shared/components/mail/ConversationThreadPane.tsx +1 -1
  82. package/apps/shared/components/mail/MenuButton.tsx +3 -400
  83. package/apps/shared/components/mail/MessageDetailPane.tsx +16 -13
  84. package/apps/shared/components/mail/compose/ComposeToolbar.tsx +11 -1
  85. package/apps/shared/components/mail/compose/ComposeWindow.tsx +57 -11
  86. package/apps/shared/components/mail/compose/GifPicker.tsx +10 -4
  87. package/apps/shared/components/mail/compose/RecipientInput.tsx +9 -7
  88. package/apps/shared/components/mail/compose/composeFlushRegistry.ts +14 -1
  89. package/apps/shared/components/mail/compose/encryptionRequirement.ts +5 -0
  90. package/apps/shared/components/mail/compose/quotedBody.ts +31 -6
  91. package/apps/shared/components/mail/layout/MailShell.tsx +1 -1
  92. package/apps/shared/components/mail/reading/MessageBody.tsx +13 -7
  93. package/apps/shared/components/mail/reading/bodyContent.ts +2 -0
  94. package/apps/shared/components/mail/reading/bodyHtml.ts +25 -2
  95. package/apps/shared/components/mail/writableMailboxes.ts +2 -0
  96. package/apps/shared/components/rules/RuleBuilder.tsx +11 -2
  97. package/apps/shared/components/settings/GravatarSetting.tsx +27 -0
  98. package/apps/shared/components/settings/layout/SettingsShell.tsx +1 -1
  99. package/apps/shared/components/tasks/dueDate.ts +29 -0
  100. package/apps/shared/components/tasks/layout/TasksShell.tsx +1 -1
  101. package/apps/shared/mail/decryptedMessages.ts +51 -2
  102. package/apps/shared/mail/listSnapshots.ts +2 -0
  103. package/apps/shared/mail/outbox/sendDecision.ts +14 -3
  104. package/apps/shared/mail/outbox/sendJob.ts +5 -0
  105. package/apps/shared/mail/useMailLiveUpdates.ts +7 -1
  106. package/apps/shared/mail/usePermanentDelete.tsx +3 -1
  107. package/apps/shared/notifications/apiErrors.ts +2 -0
  108. package/apps/shared/notifications/preferences.ts +1 -1
  109. package/apps/shared/notifications/store.ts +3 -1
  110. package/apps/shared/plugins/pluginNav.ts +4 -2
  111. package/apps/www/calendar/index.tsx +24 -12
  112. package/apps/www/contacts/[uid].tsx +6 -1
  113. package/apps/www/contacts/index.tsx +66 -12
  114. package/apps/www/index.tsx +91 -28
  115. package/apps/www/messages/[uid].tsx +8 -3
  116. package/apps/www/settings/auto-reply/index.tsx +13 -0
  117. package/apps/www/settings/blocked-senders/index.tsx +4 -0
  118. package/apps/www/settings/encryption/index.tsx +66 -4
  119. package/apps/www/settings/privacy/index.tsx +4 -1
  120. package/apps/www/settings/profile/index.tsx +12 -1
  121. package/apps/www/settings/sharing/index.tsx +11 -1
  122. package/apps/www/settings/signatures/[uid].tsx +6 -4
  123. package/apps/www/tasks/index.tsx +97 -29
  124. package/dist/apps/admin/audit-log/index.js +3 -3
  125. package/dist/apps/admin/branding/index.js +3 -3
  126. package/dist/apps/admin/data-requests/index.js +17 -7
  127. package/dist/apps/admin/distribution-lists/[uid].js +3 -3
  128. package/dist/apps/admin/distribution-lists/index.js +3 -3
  129. package/dist/apps/admin/distribution-lists/new/index.js +3 -3
  130. package/dist/apps/admin/domains/[uid].js +6 -5
  131. package/dist/apps/admin/domains/index.js +3 -3
  132. package/dist/apps/admin/domains/new/index.js +3 -3
  133. package/dist/apps/admin/escrow-scopes/[uid].js +11 -6
  134. package/dist/apps/admin/escrow-scopes/index.js +3 -3
  135. package/dist/apps/admin/escrow-scopes/new/index.js +9 -4
  136. package/dist/apps/admin/index.js +4 -4
  137. package/dist/apps/admin/ingest-queue/index.js +3 -3
  138. package/dist/apps/admin/mailboxes/[uid].js +15 -7
  139. package/dist/apps/admin/quarantine/index.js +4 -4
  140. package/dist/apps/admin/retention-policy/index.js +3 -3
  141. package/dist/apps/admin/signing-certificates/index.js +3 -3
  142. package/dist/apps/admin/transport-rules/[uid].js +4 -4
  143. package/dist/apps/admin/transport-rules/index.js +3 -3
  144. package/dist/apps/admin/transport-rules/new/index.js +3 -3
  145. package/dist/apps/escrow/matters/[uid].js +8 -3
  146. package/dist/apps/escrow/matters/new/index.js +4 -0
  147. package/dist/apps/shared/calendar/calendarLiveUpdates.d.ts +11 -7
  148. package/dist/apps/shared/calendar/calendarLiveUpdates.js +10 -9
  149. package/dist/apps/shared/calendar/useCalendarReminders.js +2 -0
  150. package/dist/apps/shared/components/admin/ActionAlert.d.ts +13 -0
  151. package/dist/apps/shared/components/admin/ActionAlert.js +24 -0
  152. package/dist/apps/shared/components/admin/diagnostics/InformationPanel.js +2 -2
  153. package/dist/apps/shared/components/admin/diagnostics/InstalledPlugins.js +1 -1
  154. package/dist/apps/shared/components/admin/diagnostics/LogsPanel.js +1 -1
  155. package/dist/apps/shared/components/admin/diagnostics/RuntimePanel.js +1 -1
  156. package/dist/apps/shared/components/admin/diagnostics/SystemPanel.js +1 -1
  157. package/dist/apps/shared/components/admin/diagnostics/diagnosticsApi.d.ts +2 -2
  158. package/dist/apps/shared/components/admin/distributionLists/MemberListCard.js +4 -4
  159. package/dist/apps/shared/components/admin/elevation.d.ts +9 -0
  160. package/dist/apps/shared/components/admin/elevation.js +16 -0
  161. package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.d.ts +2 -0
  162. package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.js +41 -1
  163. package/dist/apps/shared/components/admin/layout/AdminShell.js +2 -1
  164. package/dist/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.js +3 -3
  165. package/dist/apps/shared/components/admin/mailboxes/EscrowScopeCard.js +4 -4
  166. package/dist/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.js +4 -4
  167. package/dist/apps/shared/components/admin/mailboxes/ResourceSettingsCard.js +3 -3
  168. package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +6 -6
  169. package/dist/apps/shared/components/admin/settings/BrandingForm.js +17 -7
  170. package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +4 -4
  171. package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.js +17 -5
  172. package/dist/apps/shared/components/admin/settings/LoadedSettingsForm.js +3 -3
  173. package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +8 -3
  174. package/dist/apps/shared/components/admin/settings/MailboxPolicyForm.js +3 -3
  175. package/dist/apps/shared/components/admin/settings/PluginsManager.js +103 -18
  176. package/dist/apps/shared/components/admin/settings/RetentionPolicyForm.js +17 -10
  177. package/dist/apps/shared/components/admin/setup/EscrowSetupStep.js +18 -8
  178. package/dist/apps/shared/components/admin/setup/SetupWizard.js +8 -6
  179. package/dist/apps/shared/components/admin/signOut.js +3 -0
  180. package/dist/apps/shared/components/admin/usePagedList.js +2 -2
  181. package/dist/apps/shared/components/calendar/DescriptionEditor.js +2 -1
  182. package/dist/apps/shared/components/calendar/EventDetails.d.ts +3 -1
  183. package/dist/apps/shared/components/calendar/EventDetails.js +7 -4
  184. package/dist/apps/shared/components/calendar/EventEditor.js +81 -33
  185. package/dist/apps/shared/components/calendar/EventFormParts.d.ts +3 -1
  186. package/dist/apps/shared/components/calendar/EventFormParts.js +3 -3
  187. package/dist/apps/shared/components/calendar/EventMatchNav.d.ts +1 -1
  188. package/dist/apps/shared/components/calendar/EventMatchNav.js +12 -2
  189. package/dist/apps/shared/components/calendar/EventModal.js +10 -2
  190. package/dist/apps/shared/components/calendar/FindATime.js +4 -3
  191. package/dist/apps/shared/components/calendar/ListView.d.ts +10 -4
  192. package/dist/apps/shared/components/calendar/ListView.js +92 -27
  193. package/dist/apps/shared/components/calendar/RecurrenceEditor.d.ts +4 -1
  194. package/dist/apps/shared/components/calendar/RecurrenceEditor.js +15 -10
  195. package/dist/apps/shared/components/calendar/SplitDayView.js +11 -17
  196. package/dist/apps/shared/components/calendar/TaskQuickForm.js +3 -2
  197. package/dist/apps/shared/components/calendar/TimeGridView.js +23 -21
  198. package/dist/apps/shared/components/calendar/gridLayout.d.ts +41 -0
  199. package/dist/apps/shared/components/calendar/gridLayout.js +83 -0
  200. package/dist/apps/shared/components/calendar/layout/CalendarShell.js +1 -1
  201. package/dist/apps/shared/components/calendar/timePicker.js +5 -2
  202. package/dist/apps/shared/components/contacts/ContactCard.js +9 -3
  203. package/dist/apps/shared/components/contacts/ContactDetailPane.d.ts +5 -3
  204. package/dist/apps/shared/components/contacts/ContactDetailPane.js +11 -5
  205. package/dist/apps/shared/components/contacts/ContactForm.js +54 -16
  206. package/dist/apps/shared/components/contacts/ContactPhotoEditor.d.ts +21 -0
  207. package/dist/apps/shared/components/contacts/ContactPhotoEditor.js +56 -0
  208. package/dist/apps/shared/components/contacts/ContactPhotoField.d.ts +7 -4
  209. package/dist/apps/shared/components/contacts/ContactPhotoField.js +19 -21
  210. package/dist/apps/shared/components/contacts/layout/ContactsShell.js +1 -1
  211. package/dist/apps/shared/components/contacts/participantDetails.d.ts +4 -1
  212. package/dist/apps/shared/components/contacts/participantDetails.js +17 -1
  213. package/dist/apps/shared/components/layout/AppShell.js +9 -0
  214. package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +2 -3
  215. package/dist/apps/shared/components/layout/MailboxProvisioning.d.ts +4 -1
  216. package/dist/apps/shared/components/layout/MailboxProvisioning.js +24 -2
  217. package/dist/apps/shared/components/layout/RecoveryCodeUnlock.d.ts +4 -2
  218. package/dist/apps/shared/components/layout/RecoveryCodeUnlock.js +4 -2
  219. package/dist/apps/shared/components/mail/ConversationList.js +49 -6
  220. package/dist/apps/shared/components/mail/ConversationThreadPane.js +1 -1
  221. package/dist/apps/shared/components/mail/MenuButton.d.ts +2 -78
  222. package/dist/apps/shared/components/mail/MenuButton.js +2 -211
  223. package/dist/apps/shared/components/mail/MessageDetailPane.js +10 -6
  224. package/dist/apps/shared/components/mail/compose/ComposeToolbar.d.ts +3 -0
  225. package/dist/apps/shared/components/mail/compose/ComposeToolbar.js +10 -1
  226. package/dist/apps/shared/components/mail/compose/ComposeWindow.js +55 -10
  227. package/dist/apps/shared/components/mail/compose/GifPicker.js +10 -4
  228. package/dist/apps/shared/components/mail/compose/RecipientInput.js +9 -7
  229. package/dist/apps/shared/components/mail/compose/composeFlushRegistry.d.ts +7 -1
  230. package/dist/apps/shared/components/mail/compose/composeFlushRegistry.js +13 -1
  231. package/dist/apps/shared/components/mail/compose/encryptionRequirement.d.ts +5 -0
  232. package/dist/apps/shared/components/mail/compose/quotedBody.js +28 -5
  233. package/dist/apps/shared/components/mail/layout/MailShell.js +1 -1
  234. package/dist/apps/shared/components/mail/reading/MessageBody.js +11 -6
  235. package/dist/apps/shared/components/mail/reading/bodyContent.js +2 -0
  236. package/dist/apps/shared/components/mail/reading/bodyHtml.d.ts +5 -1
  237. package/dist/apps/shared/components/mail/reading/bodyHtml.js +23 -2
  238. package/dist/apps/shared/components/mail/writableMailboxes.js +2 -0
  239. package/dist/apps/shared/components/rules/RuleBuilder.js +9 -1
  240. package/dist/apps/shared/components/settings/GravatarSetting.d.ts +6 -0
  241. package/dist/apps/shared/components/settings/GravatarSetting.js +15 -0
  242. package/dist/apps/shared/components/settings/layout/SettingsShell.js +1 -1
  243. package/dist/apps/shared/components/tasks/dueDate.d.ts +12 -0
  244. package/dist/apps/shared/components/tasks/dueDate.js +26 -0
  245. package/dist/apps/shared/components/tasks/layout/TasksShell.js +1 -1
  246. package/dist/apps/shared/mail/decryptedMessages.d.ts +8 -0
  247. package/dist/apps/shared/mail/decryptedMessages.js +46 -2
  248. package/dist/apps/shared/mail/listSnapshots.js +2 -0
  249. package/dist/apps/shared/mail/outbox/sendDecision.d.ts +6 -3
  250. package/dist/apps/shared/mail/outbox/sendDecision.js +13 -3
  251. package/dist/apps/shared/mail/outbox/sendJob.d.ts +1 -0
  252. package/dist/apps/shared/mail/outbox/sendJob.js +5 -1
  253. package/dist/apps/shared/mail/useMailLiveUpdates.d.ts +3 -0
  254. package/dist/apps/shared/mail/useMailLiveUpdates.js +6 -1
  255. package/dist/apps/shared/mail/usePermanentDelete.d.ts +2 -0
  256. package/dist/apps/shared/mail/usePermanentDelete.js +1 -1
  257. package/dist/apps/shared/notifications/apiErrors.js +2 -0
  258. package/dist/apps/shared/notifications/preferences.d.ts +1 -1
  259. package/dist/apps/shared/notifications/preferences.js +1 -1
  260. package/dist/apps/shared/notifications/store.js +3 -1
  261. package/dist/apps/shared/plugins/pluginNav.d.ts +2 -1
  262. package/dist/apps/shared/plugins/pluginNav.js +4 -2
  263. package/dist/apps/www/calendar/index.js +24 -12
  264. package/dist/apps/www/contacts/[uid].js +6 -2
  265. package/dist/apps/www/contacts/index.js +62 -11
  266. package/dist/apps/www/index.js +85 -30
  267. package/dist/apps/www/messages/[uid].js +8 -3
  268. package/dist/apps/www/settings/auto-reply/index.js +13 -1
  269. package/dist/apps/www/settings/blocked-senders/index.js +4 -0
  270. package/dist/apps/www/settings/encryption/index.js +29 -5
  271. package/dist/apps/www/settings/privacy/index.js +3 -2
  272. package/dist/apps/www/settings/profile/index.js +4 -1
  273. package/dist/apps/www/settings/sharing/index.js +10 -1
  274. package/dist/apps/www/settings/signatures/[uid].js +6 -4
  275. package/dist/apps/www/tasks/index.js +94 -32
  276. package/dist/lib/admin/auditLogApi.js +2 -2
  277. package/dist/lib/admin/distributionListsApi.js +2 -2
  278. package/dist/lib/admin/domainsApi.js +2 -2
  279. package/dist/lib/admin/escrowAuditLogApi.js +2 -2
  280. package/dist/lib/admin/escrowScopesApi.js +2 -2
  281. package/dist/lib/admin/pluginsApi.d.ts +31 -2
  282. package/dist/lib/admin/pluginsApi.js +23 -1
  283. package/dist/lib/admin/retentionPolicyApi.d.ts +3 -0
  284. package/dist/lib/admin/retentionPolicyApi.js +3 -0
  285. package/dist/lib/admin/transportRulesApi.js +2 -2
  286. package/dist/lib/appearance/preferencesApi.d.ts +1 -1
  287. package/dist/lib/appearance/preferencesApi.js +3 -14
  288. package/dist/lib/auth/session.d.ts +51 -12
  289. package/dist/lib/auth/session.js +214 -38
  290. package/dist/lib/branding/brandingApi.d.ts +3 -3
  291. package/dist/lib/branding/brandingApi.js +9 -21
  292. package/dist/lib/calendar/calendarApi.js +3 -1
  293. package/dist/lib/calendar/eventSearch.js +43 -6
  294. package/dist/lib/calendar/freeBusyApi.d.ts +2 -1
  295. package/dist/lib/calendar/freeBusyApi.js +13 -5
  296. package/dist/lib/calendar/recurrence.js +51 -3
  297. package/dist/lib/calendar/useDayKey.d.ts +7 -0
  298. package/dist/lib/calendar/useDayKey.js +43 -0
  299. package/dist/lib/components/avatar/ContactAvatar.d.ts +6 -2
  300. package/dist/lib/components/avatar/ContactAvatar.js +37 -8
  301. package/dist/lib/components/avatar/ContactPhotoAvatar.d.ts +11 -0
  302. package/dist/lib/components/avatar/ContactPhotoAvatar.js +18 -0
  303. package/dist/lib/components/forms/CameraCaptureModal.d.ts +17 -0
  304. package/dist/lib/components/forms/CameraCaptureModal.js +88 -0
  305. package/dist/lib/components/forms/ImageEditBadge.d.ts +28 -0
  306. package/dist/lib/components/forms/ImageEditBadge.js +62 -0
  307. package/dist/lib/components/menus/MenuButton.d.ts +78 -0
  308. package/dist/lib/components/menus/MenuButton.js +219 -0
  309. package/dist/lib/components/navigation/BottomTabBar.d.ts +3 -1
  310. package/dist/lib/components/navigation/BottomTabBar.js +22 -3
  311. package/dist/lib/components/overlays/PopoverPortal.d.ts +2 -1
  312. package/dist/lib/components/overlays/PopoverPortal.js +3 -2
  313. package/dist/lib/components/pickers/TimeZonePicker.js +13 -1
  314. package/dist/lib/contacts/contactsApi.d.ts +5 -2
  315. package/dist/lib/contacts/contactsApi.js +16 -19
  316. package/dist/lib/contacts/gravatar.d.ts +14 -3
  317. package/dist/lib/contacts/gravatar.js +42 -5
  318. package/dist/lib/contacts/preparePhoto.d.ts +24 -0
  319. package/dist/lib/contacts/preparePhoto.js +250 -0
  320. package/dist/lib/contacts/useContactPhotoSrc.d.ts +19 -0
  321. package/dist/lib/contacts/useContactPhotoSrc.js +153 -0
  322. package/dist/lib/contacts/vcard.d.ts +3 -0
  323. package/dist/lib/contacts/vcard.js +59 -7
  324. package/dist/lib/crypto/escrowKeys.d.ts +8 -0
  325. package/dist/lib/crypto/escrowKeys.js +20 -1
  326. package/dist/lib/crypto/messageSecurity.d.ts +7 -4
  327. package/dist/lib/crypto/messageSecurity.js +23 -5
  328. package/dist/lib/crypto/passwordUnlock.d.ts +6 -1
  329. package/dist/lib/crypto/passwordUnlock.js +13 -2
  330. package/dist/lib/crypto/smime.js +12 -2
  331. package/dist/lib/mail/compose/composeQuoting.js +3 -2
  332. package/dist/lib/mail/compose/quotedImages.d.ts +6 -3
  333. package/dist/lib/mail/compose/quotedImages.js +35 -9
  334. package/dist/lib/mail/labelsApi.js +2 -2
  335. package/dist/lib/mail/mailApi.d.ts +8 -5
  336. package/dist/lib/mail/mailApi.js +73 -39
  337. package/dist/lib/mail/mailDetailHooks.js +11 -3
  338. package/dist/lib/mail/mailFilterRulesApi.js +2 -2
  339. package/dist/lib/mail/mailSignaturesApi.js +2 -2
  340. package/dist/lib/mail/mailboxImportApi.d.ts +1 -1
  341. package/dist/lib/mail/mailboxImportApi.js +3 -15
  342. package/dist/lib/mail/messageBodySanitizer.d.ts +6 -0
  343. package/dist/lib/mail/messageBodySanitizer.js +44 -1
  344. package/dist/lib/search/queryGrammar.js +12 -4
  345. package/dist/lib/search/searchTier3.d.ts +6 -1
  346. package/dist/lib/search/searchTier3.js +54 -25
  347. package/dist/lib/tasks/tasksApi.js +3 -3
  348. package/dist/lib/util/api.d.ts +26 -4
  349. package/dist/lib/util/api.js +73 -16
  350. package/dist/lib/util/apiQuery.d.ts +11 -0
  351. package/dist/lib/util/apiQuery.js +11 -0
  352. package/dist/lib/util/settleWithLimit.d.ts +5 -0
  353. package/dist/lib/util/settleWithLimit.js +25 -0
  354. package/lib/admin/auditLogApi.ts +2 -2
  355. package/lib/admin/distributionListsApi.ts +2 -2
  356. package/lib/admin/domainsApi.ts +2 -2
  357. package/lib/admin/escrowAuditLogApi.ts +2 -2
  358. package/lib/admin/escrowScopesApi.ts +2 -2
  359. package/lib/admin/pluginsApi.ts +45 -3
  360. package/lib/admin/retentionPolicyApi.ts +4 -0
  361. package/lib/admin/transportRulesApi.ts +2 -2
  362. package/lib/appearance/preferencesApi.ts +3 -14
  363. package/lib/auth/session.ts +239 -39
  364. package/lib/branding/brandingApi.ts +9 -21
  365. package/lib/calendar/calendarApi.ts +3 -1
  366. package/lib/calendar/eventSearch.ts +46 -6
  367. package/lib/calendar/freeBusyApi.ts +14 -6
  368. package/lib/calendar/recurrence.ts +59 -8
  369. package/lib/calendar/useDayKey.ts +45 -0
  370. package/lib/components/avatar/ContactAvatar.tsx +46 -8
  371. package/lib/components/avatar/ContactPhotoAvatar.tsx +26 -0
  372. package/lib/components/forms/CameraCaptureModal.tsx +133 -0
  373. package/lib/components/forms/ImageEditBadge.tsx +121 -0
  374. package/lib/components/menus/MenuButton.tsx +408 -0
  375. package/lib/components/navigation/BottomTabBar.tsx +30 -8
  376. package/lib/components/overlays/PopoverPortal.tsx +3 -2
  377. package/lib/components/pickers/TimeZonePicker.tsx +17 -1
  378. package/lib/contacts/contactsApi.ts +18 -19
  379. package/lib/contacts/gravatar.ts +47 -5
  380. package/lib/contacts/preparePhoto.ts +273 -0
  381. package/lib/contacts/useContactPhotoSrc.ts +177 -0
  382. package/lib/contacts/vcard.ts +60 -7
  383. package/lib/crypto/escrowKeys.ts +21 -1
  384. package/lib/crypto/messageSecurity.ts +33 -9
  385. package/lib/crypto/passwordUnlock.ts +14 -2
  386. package/lib/crypto/smime.ts +13 -2
  387. package/lib/mail/compose/composeQuoting.ts +3 -2
  388. package/lib/mail/compose/quotedImages.ts +39 -9
  389. package/lib/mail/labelsApi.ts +2 -2
  390. package/lib/mail/mailApi.ts +96 -39
  391. package/lib/mail/mailDetailHooks.ts +11 -3
  392. package/lib/mail/mailFilterRulesApi.ts +2 -2
  393. package/lib/mail/mailSignaturesApi.ts +2 -2
  394. package/lib/mail/mailboxImportApi.ts +9 -15
  395. package/lib/mail/messageBodySanitizer.ts +47 -1
  396. package/lib/search/queryGrammar.ts +13 -4
  397. package/lib/search/searchTier3.ts +65 -32
  398. package/lib/tasks/tasksApi.ts +3 -3
  399. package/lib/util/api.ts +99 -16
  400. package/lib/util/apiQuery.ts +13 -0
  401. package/lib/util/settleWithLimit.ts +25 -0
  402. package/package.json +1 -1
@@ -104,6 +104,48 @@ function valueSeparatorIndex(line: string): number {
104
104
  return -1;
105
105
  }
106
106
 
107
+ /** How much of a file's text `parseVCards()` reads, and how many records it takes from it (the rest is ignored). */
108
+ export const VCARD_MAX_CHARS = 5_000_000;
109
+ export const VCARD_MAX_CONTACTS = 2_000;
110
+
111
+ /** Decodes a quoted-printable value: each `=XX` is one byte, and the bytes are text in `charset` (UTF-8 when none is named or the name is unknown). */
112
+ function decodeQuotedPrintable(value: string, charset: string | undefined): string {
113
+ const encoder = new TextEncoder();
114
+ const bytes: number[] = [];
115
+ for (const [, hex, literal] of value.matchAll(/=([0-9a-f]{2})|([\s\S])/giu)) {
116
+ if (hex) {
117
+ bytes.push(parseInt(hex, 16));
118
+ } else {
119
+ bytes.push(...encoder.encode(literal));
120
+ }
121
+ }
122
+ try {
123
+ return new TextDecoder(charset || "utf-8").decode(new Uint8Array(bytes));
124
+ } catch {
125
+ return new TextDecoder().decode(new Uint8Array(bytes));
126
+ }
127
+ }
128
+
129
+ /** `lines` with each quoted-printable line that ends in `=` (a soft line break) joined to the line that carries on from it. */
130
+ function joinQuotedPrintableLines(lines: string[]): string[] {
131
+ const joined: string[] = [];
132
+ for (let i = 0; i < lines.length; i++) {
133
+ let line = lines[i];
134
+ if (/^[^:]*QUOTED-PRINTABLE/i.test(line)) {
135
+ // Collected and joined once: appending to a growing string a line at a time is quadratic in a value of many soft breaks.
136
+ const parts: string[] = [];
137
+ while (line.endsWith("=") && i + 1 < lines.length) {
138
+ parts.push(line.slice(0, -1));
139
+ line = lines[++i];
140
+ }
141
+ parts.push(line);
142
+ line = parts.join("");
143
+ }
144
+ joined.push(line);
145
+ }
146
+ return joined;
147
+ }
148
+
107
149
  /**
108
150
  * Maps a property's type parameters onto `ContactAddressKind`: every `TYPE=` value (vCard 3/4, including
109
151
  * comma lists and quoted lists like `TYPE="work,voice"`) plus vCard 2.1's bare parameters (`EMAIL;WORK:`)
@@ -134,13 +176,15 @@ function normalizeType(params: string[]): ContactAddressKind {
134
176
  * `normalizeType()`); structured values are split on unescaped `;` only.
135
177
  */
136
178
  export function parseVCards(text: string): ParsedVCardContact[] {
179
+ // A file is read only so far: nothing legitimate is this large, and a huge one would be minutes of parsing and thousands of records.
180
+ const source = text.length > VCARD_MAX_CHARS ? text.slice(0, VCARD_MAX_CHARS) : text;
137
181
  // Unfold first, then split only on a line that is exactly BEGIN:VCARD - a NOTE (or a folded line) that
138
182
  // merely contains the text "BEGIN:VCARD" must not start a bogus record.
139
- const unfolded = text.replace(/(?:\r\n|\r|\n)[ \t]/g, "");
140
- const cards = unfolded.split(/^[ \t]*BEGIN:VCARD[ \t]*$/im).slice(1);
183
+ const unfolded = source.replace(/(?:\r\n|\r|\n)[ \t]/g, "");
184
+ const cards = unfolded.split(/^[ \t]*BEGIN:VCARD[ \t]*$/im).slice(1, VCARD_MAX_CONTACTS + 1);
141
185
  return cards.map((card) => {
142
186
  const contact: ParsedVCardContact = { displayName: "", emails: [], phones: [], addresses: [] };
143
- const lines = card.split(/\r\n|\r|\n/);
187
+ const lines = joinQuotedPrintableLines(card.split(/\r\n|\r|\n/));
144
188
  for (const rawLine of lines) {
145
189
  const line = rawLine.trim();
146
190
  const colonIndex = valueSeparatorIndex(line);
@@ -148,9 +192,13 @@ export function parseVCards(text: string): ParsedVCardContact[] {
148
192
  continue;
149
193
  }
150
194
  const key = line.slice(0, colonIndex);
151
- const rawValue = line.slice(colonIndex + 1);
152
- const value = unescapeVCardValue(rawValue);
153
195
  const [groupedName, ...params] = key.split(";");
196
+ // vCard 2.1 may encode a value as quoted-printable (`ENCODING=QUOTED-PRINTABLE`, or the bare parameter), in a named charset;
197
+ // such a value has no backslash escapes.
198
+ const quotedPrintable = params.some((param) => /^(?:ENCODING=)?QUOTED-PRINTABLE$/i.test(param.trim()));
199
+ const charset = params.find((param) => /^CHARSET=/i.test(param.trim()))?.trim().slice("CHARSET=".length).replace(/"/g, "");
200
+ const rawValue = quotedPrintable ? decodeQuotedPrintable(line.slice(colonIndex + 1), charset) : line.slice(colonIndex + 1);
201
+ const value = quotedPrintable ? rawValue : unescapeVCardValue(rawValue);
154
202
  const name = groupedName.slice(groupedName.lastIndexOf(".") + 1).toUpperCase();
155
203
  const type = normalizeType(params);
156
204
 
@@ -172,10 +220,15 @@ export function parseVCards(text: string): ParsedVCardContact[] {
172
220
  contact.jobTitle = value;
173
221
  break;
174
222
  case "EMAIL":
175
- contact.emails!.push({ address: value, type });
223
+ // An empty one would make the server refuse the whole contact.
224
+ if (value.trim()) {
225
+ contact.emails!.push({ address: value.trim(), type });
226
+ }
176
227
  break;
177
228
  case "TEL":
178
- contact.phones!.push({ phoneNumber: value, type });
229
+ if (value.trim()) {
230
+ contact.phones!.push({ phoneNumber: value, type });
231
+ }
179
232
  break;
180
233
  case "ADR": {
181
234
  const [, , street, city, state, postalCode, country] = splitStructuredValue(rawValue);
@@ -15,7 +15,7 @@
15
15
  import "reflect-metadata";
16
16
  import * as x509 from "@peculiar/x509";
17
17
  import type { EscrowScopePublicKey } from "../admin/escrowScopesApi.js";
18
- import { toBase64 } from "./encoding.js";
18
+ import { fromBase64, toBase64 } from "./encoding.js";
19
19
  import { computeCertFingerprint } from "./smime.js";
20
20
 
21
21
  x509.cryptoProvider.set(crypto);
@@ -31,6 +31,26 @@ export interface GeneratedEscrowKeys {
31
31
  publicKey: EscrowScopePublicKey;
32
32
  }
33
33
 
34
+ /**
35
+ * The SHA-256 fingerprint (lower-case hex, as `computeCertFingerprint()` gives it) of the certificate that `publicKey` - an escrow scope's pasted public key,
36
+ * the base64 of its DER - holds, or `null` when it is not a certificate. The holders compare a scope's fingerprint out of band, so one typed beside a
37
+ * different key makes that check worthless: this is what it has to be.
38
+ */
39
+ export async function escrowPublicKeyFingerprint(publicKey: string): Promise<string | null> {
40
+ try {
41
+ const der = fromBase64(publicKey.trim());
42
+ void new x509.X509Certificate(new Uint8Array(der));
43
+ return await computeCertFingerprint(der);
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ /** A fingerprint as `escrowPublicKeyFingerprint()` writes it: lower-case hex only, whatever separators (`AB:CD`, spaces) it was typed with. */
50
+ export function normalizeFingerprint(fingerprint: string): string {
51
+ return fingerprint.replace(/[^0-9a-f]/gi, "").toLowerCase();
52
+ }
53
+
34
54
  function toPem(label: string, der: Uint8Array): string {
35
55
  const lines = toBase64(der).match(/.{1,64}/g) ?? [];
36
56
  return `-----BEGIN ${label}-----\n${lines.join("\n")}\n-----END ${label}-----\n`;
@@ -123,8 +123,11 @@ export interface MessageSecurityResult {
123
123
  * recovered and it carried a protected Subject. */
124
124
  subject?: string;
125
125
  /** Every protected header recovered from inside the signed/decrypted entity - present only when that entity
126
- * carries RFC 9788 protected headers (a protected `From`). Absent for a legacy S/MIME sender's signed-only
127
- * message: its outer Subject/To/Cc were then never signed, and a UI should not present them as verified. */
126
+ * carries RFC 9788 protected headers (a protected `From`) AND a signature over them verified (so also for a
127
+ * `"verified_at_first_open"` result). Absent for a legacy S/MIME sender's signed-only message (its outer
128
+ * Subject/To/Cc were then never signed, and a UI should not present them as verified), and for an encrypted
129
+ * message without a verified signature: anyone holding the reader's public certificate can encrypt headers of their
130
+ * own choosing, so they are never exposed. */
128
131
  protectedHeaders?: MessageProtectedHeaders;
129
132
  /** The attachments inside the verified/decrypted entity - for a signed-only message the only attachments the
130
133
  * signature covers, so a UI showing a verified badge should list these rather than the server's attachment
@@ -189,7 +192,7 @@ function isEncryptedContentType(contentType: { value: string; params: Record<str
189
192
 
190
193
  const NO_KEY_ERROR = "This device doesn't have the key needed to decrypt this message.";
191
194
  const UNSUPPORTED_ENCRYPTION_ERROR =
192
- "This message uses an older, unauthenticated encryption algorithm that RapidMX doesn't accept, so it can't be opened safely.";
195
+ "This message uses an older, unauthenticated encryption algorithm, or an encryption format, that RapidMX doesn't accept, so it can't be opened safely.";
193
196
 
194
197
  function renderDisplayBody(display: DisplayBody | undefined): Pick<MessageSecurityResult, "html" | "text"> {
195
198
  if (display?.html !== undefined) {
@@ -219,7 +222,7 @@ export interface SignerBindingInput {
219
222
  /** The certificate matched to the verified SignerInfo; `undefined` fails closed. */
220
223
  signerCertificateDer: Uint8Array | undefined;
221
224
  /** Headers recovered from inside the signed content (RFC 9788), if any. */
222
- protectedHeaders: (Pick<ProtectedHeaders, "from" | "to"> & Partial<Pick<ProtectedHeaders, "cc" | "subject">>) | undefined;
225
+ protectedHeaders: (Pick<ProtectedHeaders, "from" | "to"> & Partial<Pick<ProtectedHeaders, "cc" | "subject" | "date">>) | undefined;
223
226
  /** The received message's outer header map (lowercased names), as `parseMimeEntity()` returns it. */
224
227
  outerHeaders: Record<string, string>;
225
228
  /** Every outer header field in order (`parseMimeEntity()`'s `fields`) - checked for repeated
@@ -229,7 +232,7 @@ export interface SignerBindingInput {
229
232
  protectedFields?: MimeHeaderField[];
230
233
  /** One trusted fingerprint or several (any match is accepted). An empty array counts as none supplied. */
231
234
  pinnedSignerFingerprint?: string | string[];
232
- /** Also require the protected `Subject` (when protected headers are present) to equal the outer one - right for
235
+ /** Also require the protected `Subject` and `Date` (when protected headers are present) to equal the outer ones - right for
233
236
  * a signed-only (`hp="clear"`) message, wrong for an encrypted one whose outer Subject is obscured. */
234
237
  compareSubject?: boolean;
235
238
  }
@@ -246,6 +249,15 @@ function hasRepeatedAddressField(fields: MimeHeaderField[] | undefined): boolean
246
249
  return SINGLETON_ADDRESS_FIELDS.some((name) => (counts.get(name) ?? 0) > 1);
247
250
  }
248
251
 
252
+ /** Whether the unsigned (outer) `Date:` header value `outer` names the same instant as the signed one, `signed`. A signed date
253
+ * that is missing or unreadable leaves nothing to compare, but when there is one the outer must match it: a replayed message
254
+ * that dropped or garbled its outer Date is as changed as one that carries a new one. */
255
+ function sameInstant(signed: string | undefined, outer: string | undefined): boolean {
256
+ // `String(undefined)` is not a date either.
257
+ const left = Date.parse(String(signed));
258
+ return Number.isNaN(left) || left === Date.parse(String(outer));
259
+ }
260
+
249
261
  function normalizePins(pins: string | string[] | undefined): string[] {
250
262
  return (Array.isArray(pins) ? pins : pins === undefined ? [] : [pins]).map((pin) => pin.toLowerCase());
251
263
  }
@@ -292,7 +304,13 @@ function checkIdentityAndHeaders(input: SignerBindingInput, certDer: Uint8Array)
292
304
  if (!sameAddressSet(extractAddresses(protectedHeaders?.cc), extractAddresses(outerHeaders["cc"]))) {
293
305
  return "header_mismatch";
294
306
  }
295
- if (input.compareSubject && normalizeSubject(protectedHeaders?.subject) !== normalizeSubject(outerHeaders["subject"])) {
307
+ // A signed-only message's outer Date is unsigned too: a signed message replayed later carries a new one. (An encrypted
308
+ // message's outer Date is checked against its HP-Outer copy instead - see `headerTamperDetected`.)
309
+ if (
310
+ input.compareSubject &&
311
+ (normalizeSubject(protectedHeaders?.subject) !== normalizeSubject(outerHeaders["subject"]) ||
312
+ !sameInstant(protectedHeaders?.date, outerHeaders["date"]))
313
+ ) {
296
314
  return "header_mismatch";
297
315
  }
298
316
  }
@@ -448,14 +466,16 @@ async function evaluateLive(
448
466
  if (!parsed.decrypted) {
449
467
  return { result: { state: "encrypted", decryptError: parsed.unsupportedContentEncryption ? UNSUPPORTED_ENCRYPTION_ERROR : NO_KEY_ERROR }, kind: "encrypted" };
450
468
  }
469
+ // Anyone holding the reader's public certificate can encrypt a message with any protected headers they like, so they
470
+ // are only exposed once a signature over them verified - and a key-changed message keeps them aside for a seal.
451
471
  const content = {
452
472
  ...renderDisplayBody(parsed.displayBody),
453
473
  headerTamperDetected: parsed.headerTamperDetected,
454
474
  subject: parsed.protectedHeaders?.subject || undefined,
455
- ...exposedHeaders(parsed.protectedHeaders),
456
475
  attachments: parsed.attachments,
457
476
  ...addressing(parsed.protectedHeaders),
458
477
  };
478
+ const signedHeaders = exposedHeaders(parsed.protectedHeaders);
459
479
  if (parsed.signatureVerified === undefined) {
460
480
  return { result: { state: "encrypted", ...content }, kind: "encrypted" };
461
481
  }
@@ -464,9 +484,13 @@ async function evaluateLive(
464
484
  }
465
485
  const { failure, trusted, signer, acceptedSigner } = await checkSigner(parsed.signerCertificateDer, parsed.protectedHeaders, parsed.protectedHeaderFields, false);
466
486
  if (failure) {
467
- return { result: { state: "signature_failed", signatureFailureReason: failure, ...content, ...(failure === "signer_key_changed" ? acceptedSigner : signer) }, kind: "encrypted" };
487
+ return {
488
+ result: { state: "signature_failed", signatureFailureReason: failure, ...content, ...(failure === "signer_key_changed" ? acceptedSigner : signer) },
489
+ kind: "encrypted",
490
+ ...(failure === "signer_key_changed" ? { signedContent: signedHeaders } : {}),
491
+ };
468
492
  }
469
- return { result: { state: trusted ? "encrypted_verified" : "encrypted_unverified_signer", ...content, ...acceptedSigner }, kind: "encrypted" };
493
+ return { result: { state: trusted ? "encrypted_verified" : "encrypted_unverified_signer", ...content, ...signedHeaders, ...acceptedSigner }, kind: "encrypted" };
470
494
  }
471
495
 
472
496
  return { result: { state: "unprotected" }, kind: "other" };
@@ -33,6 +33,11 @@ export const DEFAULT_ARGON2ID_PARAMS: Argon2idParams = { memorySize: 65536, iter
33
33
 
34
34
  const ARGON2ID_HASH_LENGTH_BYTES = 32;
35
35
 
36
+ /** The most a `MasterKeyWrap.kdf` label read back from the server may ask this device to spend: 1 GiB (in KiB),
37
+ * 10 passes and 16 lanes - well above every default this system has used, but far from what a hostile or buggy
38
+ * server could name (`m=4294967295`) to freeze the tab or exhaust WebAssembly memory. */
39
+ export const MAX_ARGON2ID_PARAMS: Argon2idParams = { memorySize: 1048576, iterations: 10, parallelism: 16 };
40
+
36
41
  /** Builds the `kdf` string stored alongside a password-method `MasterKeyWrap`, so parameters can be
37
42
  * upgraded over time without breaking existing accounts (each wrap records the parameters it was
38
43
  * actually created with). */
@@ -44,7 +49,8 @@ export function argon2idKdfLabel(params: Argon2idParams): string {
44
49
  * created with, so unlocking always re-derives with the *original* parameters even if
45
50
  * `DEFAULT_ARGON2ID_PARAMS` has since changed (the spec's own "KDF parameters ... MUST be stored
46
51
  * alongside each wrap, so costs can be raised over time without breaking existing accounts"). Returns
47
- * `undefined` for anything that isn't a well-formed `argon2id:m=...,t=...,p=...` label — including a
52
+ * `undefined` for anything that isn't a well-formed `argon2id:m=...,t=...,p=...` label, or whose parameters are
53
+ * zero or beyond `MAX_ARGON2ID_PARAMS` — including a
48
54
  * different KDF entirely (e.g. recovery codes' own `"hkdf-sha256"` label), which callers must not
49
55
  * attempt to unlock as if it were password-derived.
50
56
  */
@@ -53,7 +59,13 @@ export function parseArgon2idKdfLabel(label: string): Argon2idParams | undefined
53
59
  if (!match) {
54
60
  return undefined;
55
61
  }
56
- return { memorySize: Number(match[1]), iterations: Number(match[2]), parallelism: Number(match[3]) };
62
+ const params = { memorySize: Number(match[1]), iterations: Number(match[2]), parallelism: Number(match[3]) };
63
+ const inRange = (value: number, max: number) => value >= 1 && value <= max;
64
+ return inRange(params.memorySize, MAX_ARGON2ID_PARAMS.memorySize) &&
65
+ inRange(params.iterations, MAX_ARGON2ID_PARAMS.iterations) &&
66
+ inRange(params.parallelism, MAX_ARGON2ID_PARAMS.parallelism)
67
+ ? params
68
+ : undefined;
57
69
  }
58
70
 
59
71
  /** A fresh random salt for a new password enrollment. 16 bytes is Argon2id's own recommended minimum. */
@@ -41,6 +41,10 @@ const ID_DATA = pkijs.ContentInfo.DATA;
41
41
 
42
42
  /** AES-GCM content-encryption OIDs (id-aes128-GCM, id-aes192-GCM, id-aes256-GCM - RFC 5084). The only
43
43
  * content encryption `decryptEnvelopedData()` accepts: see `UnsupportedContentEncryptionError`. */
44
+ /** The CMS content type of RFC 5083 `AuthEnvelopedData` (`smime-type="authenveloped-data"`): authenticated, but not the
45
+ * structure `decryptEnvelopedDataWithKeys()` reads - it is reported as unsupported rather than as a missing key. */
46
+ const AUTH_ENVELOPED_DATA_OID = "1.2.840.113549.1.9.16.1.23";
47
+
44
48
  const AEAD_CONTENT_ENCRYPTION_OIDS = new Set(["2.16.840.1.101.3.4.1.6", "2.16.840.1.101.3.4.1.26", "2.16.840.1.101.3.4.1.46"]);
45
49
 
46
50
  /**
@@ -56,7 +60,7 @@ export class UnsupportedContentEncryptionError extends Error {
56
60
  public readonly algorithmOid: string;
57
61
 
58
62
  constructor(algorithmOid: string) {
59
- super(`This message uses unauthenticated content encryption (${algorithmOid}), which is not accepted.`);
63
+ super(`This message uses content encryption (${algorithmOid}) that is not accepted: only AES-GCM EnvelopedData is.`);
60
64
  this.name = "UnsupportedContentEncryptionError";
61
65
  this.algorithmOid = algorithmOid;
62
66
  }
@@ -178,10 +182,12 @@ export function extractCertificateEmails(certDer: Uint8Array): string[] {
178
182
  return [];
179
183
  }
180
184
  const sanEmails: string[] = [];
185
+ let hasSan = false;
181
186
  for (const extension of cert.extensions ?? []) {
182
187
  if (extension.extnID !== "2.5.29.17") {
183
188
  continue;
184
189
  }
190
+ hasSan = true;
185
191
  // pkijs parses a well-formed SubjectAlternativeName into an AltName automatically, and leaves
186
192
  // `parsedValue` unset for a malformed one - a certificate asserting an unreadable SAN names nobody.
187
193
  const altNames = (extension.parsedValue as pkijs.AltName | undefined)?.altNames;
@@ -195,7 +201,9 @@ export function extractCertificateEmails(certDer: Uint8Array): string[] {
195
201
  }
196
202
  }
197
203
  }
198
- if (sanEmails.length > 0) {
204
+ // The SAN is authoritative whenever the certificate has one: DNS names alone name no mailbox, so a subject email
205
+ // address next to them must not stand in for it.
206
+ if (hasSan) {
199
207
  return sanEmails;
200
208
  }
201
209
  const subjectEmails: string[] = [];
@@ -376,6 +384,9 @@ function recipientMatchesCertificate(recipientInfo: pkijs.RecipientInfo, certifi
376
384
  */
377
385
  export async function decryptEnvelopedDataWithKeys(envelopedDer: Uint8Array, keys: DecryptionKey[]): Promise<Uint8Array> {
378
386
  const contentInfo = pkijs.ContentInfo.fromBER(toArrayBuffer(envelopedDer));
387
+ if (contentInfo.contentType === AUTH_ENVELOPED_DATA_OID) {
388
+ throw new UnsupportedContentEncryptionError(AUTH_ENVELOPED_DATA_OID);
389
+ }
379
390
  if (contentInfo.contentType !== pkijs.ContentInfo.ENVELOPED_DATA) {
380
391
  throw new Error("This CMS content is not EnvelopedData.");
381
392
  }
@@ -15,7 +15,7 @@
15
15
  */
16
16
 
17
17
  import { Message, Recipient } from "../mailApi.js";
18
- import { sanitizeQuotedHtml } from "../messageBodySanitizer.js";
18
+ import { sanitizeQuotedHtml, sanitizeSignatureHtml } from "../messageBodySanitizer.js";
19
19
 
20
20
  function escapeHtml(text: string): string {
21
21
  return text
@@ -154,7 +154,8 @@ export function buildComposeBodyHtml(signatureHtml?: string, quotedHtml?: string
154
154
  }
155
155
  const separator = signatureHtml && quotedHtml ? "<p></p>" : "";
156
156
  const aboveQuote = quotedHtml ? "<p></p>" : "";
157
- return `<p></p>${signatureHtml ?? ""}${separator}${aboveQuote}${quotedHtml ?? ""}`;
157
+ // A signature can be written by someone else (a shared mailbox's delegate): it is sanitized like anything else the editor is given.
158
+ return `<p></p>${signatureHtml ? sanitizeSignatureHtml(signatureHtml) : ""}${separator}${aboveQuote}${quotedHtml ?? ""}`;
158
159
  }
159
160
 
160
161
  /**
@@ -13,6 +13,8 @@
13
13
  export const MAX_QUOTED_IMAGE_BYTES = 2_000_000;
14
14
  /** The most picture data a quote carries, in all (as `data:` URI characters). */
15
15
  export const MAX_QUOTED_IMAGES_CHARS = 6_000_000;
16
+ /** How many pictures are fetched at once. */
17
+ const QUOTED_IMAGE_CONCURRENCY = 4;
16
18
 
17
19
  export interface QuotedImageReference {
18
20
  /** The token of a `cid:` reference, absent for an image with no source. */
@@ -22,24 +24,52 @@ export interface QuotedImageReference {
22
24
 
23
25
  /**
24
26
  * `html` with each of its embedded images (`cid:`, or no source at all) replaced by the `data:` URI `resolve` gives for it. An image
25
- * `resolve` has no picture for is left as it is (the quote's sanitizer then drops it); images that already are `data:` URIs and remote
26
- * ones are not asked about. `html` is returned as given when it has no such image, or where there is no DOM to parse it with.
27
+ * `resolve` has no picture for (or fails on) is left as it is (the quote's sanitizer then drops it); images that already are `data:` URIs and
28
+ * remote ones are not asked about. `html` is returned as given when it has no such image, or where there is no DOM to parse it with.
29
+ *
30
+ * The images are resolved a few at a time. When `signal` aborts (the caller's deadline) the wait ends there: the pictures that have arrived
31
+ * are embedded, the rest left as they were, and `resolve` - which is given `signal` to cancel its own request with - is asked for no more.
27
32
  */
28
- export async function embedQuotedImages(html: string, resolve: (reference: QuotedImageReference) => Promise<string | undefined>): Promise<string> {
33
+ export async function embedQuotedImages(
34
+ html: string,
35
+ resolve: (reference: QuotedImageReference, signal?: AbortSignal) => Promise<string | undefined>,
36
+ signal?: AbortSignal,
37
+ ): Promise<string> {
29
38
  if (typeof DOMParser === "undefined" || !/<img\b/i.test(html)) {
30
39
  return html;
31
40
  }
32
41
  const doc = new DOMParser().parseFromString(html, "text/html");
33
- let budget = MAX_QUOTED_IMAGES_CHARS;
34
- let changed = false;
42
+ const targets: { image: Element; reference: QuotedImageReference; uri?: string }[] = [];
35
43
  for (const image of Array.from(doc.querySelectorAll("img"))) {
36
44
  const src = (image.getAttribute("src") ?? "").trim();
37
45
  const cid = /^cid:/i.test(src);
38
- if (!cid && src !== "") {
39
- continue;
46
+ if (cid || src === "") {
47
+ targets.push({ image, reference: { ...(cid ? { cid: src.slice(4) } : {}), alt: image.getAttribute("alt") ?? undefined } });
48
+ }
49
+ }
50
+ let next = 0;
51
+ const worker = async () => {
52
+ while (next < targets.length && !signal?.aborted) {
53
+ const target = targets[next++];
54
+ target.uri = await resolve(target.reference, signal).catch(() => undefined);
40
55
  }
41
- const uri = await resolve({ ...(cid ? { cid: src.slice(4) } : {}), alt: image.getAttribute("alt") ?? undefined });
42
- if (uri !== undefined && uri.length <= budget) {
56
+ };
57
+ let onAbort!: () => void;
58
+ const deadline = new Promise<void>((done) => {
59
+ onAbort = done;
60
+ if (signal?.aborted) {
61
+ done();
62
+ }
63
+ signal?.addEventListener("abort", onAbort, { once: true });
64
+ });
65
+ await Promise.race([Promise.all(Array.from({ length: Math.min(QUOTED_IMAGE_CONCURRENCY, targets.length) }, worker)), deadline]);
66
+ signal?.removeEventListener("abort", onAbort);
67
+ // Whatever has arrived by now is embedded, in document order, for as long as the budget lasts.
68
+ let budget = MAX_QUOTED_IMAGES_CHARS;
69
+ let changed = false;
70
+ for (const { image, uri } of targets) {
71
+ // Only a picture of the original's own is ever put in: whatever a resolver answers, a `src` that is not a `data:` image never gets in.
72
+ if (uri !== undefined && /^data:image\//i.test(uri) && uri.length <= budget) {
43
73
  budget -= uri.length;
44
74
  image.setAttribute("src", uri);
45
75
  changed = true;
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  import { ApiClient, withClient } from "../util/api.js";
14
- import { ListParams, buildQuery } from "../util/apiQuery.js";
14
+ import { ListParams, SETTINGS_LIST_LIMIT, buildQuery } from "../util/apiQuery.js";
15
15
 
16
16
  export type { ListParams };
17
17
 
@@ -31,7 +31,7 @@ export interface Label {
31
31
  * account of a multi-account app) to call instead of the default global `apiFetch()` - see `withClient()`'s
32
32
  * own doc comment in `util/api.ts`. Omitted (the default), every function here behaves exactly as before. */
33
33
  export function listLabels(mailboxUid: string, params: ListParams = {}, client?: ApiClient): Promise<Label[]> {
34
- return withClient(client, `/mail/labels?${buildQuery(params, { mailboxUid })}`);
34
+ return withClient(client, `/mail/labels?${buildQuery({ limit: SETTINGS_LIST_LIMIT, ...params }, { mailboxUid })}`);
35
35
  }
36
36
 
37
37
  export function getLabel(uid: string, client?: ApiClient): Promise<Label> {