@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.
- package/apps/admin/audit-log/index.tsx +3 -3
- package/apps/admin/branding/index.tsx +3 -3
- package/apps/admin/data-requests/index.tsx +18 -7
- package/apps/admin/distribution-lists/[uid].tsx +3 -3
- package/apps/admin/distribution-lists/index.tsx +3 -3
- package/apps/admin/distribution-lists/new/index.tsx +3 -3
- package/apps/admin/domains/[uid].tsx +6 -5
- package/apps/admin/domains/index.tsx +3 -3
- package/apps/admin/domains/new/index.tsx +3 -3
- package/apps/admin/escrow-scopes/[uid].tsx +11 -5
- package/apps/admin/escrow-scopes/index.tsx +3 -3
- package/apps/admin/escrow-scopes/new/index.tsx +9 -3
- package/apps/admin/index.tsx +4 -4
- package/apps/admin/ingest-queue/index.tsx +3 -3
- package/apps/admin/mailboxes/[uid].tsx +15 -7
- package/apps/admin/quarantine/index.tsx +4 -4
- package/apps/admin/retention-policy/index.tsx +3 -3
- package/apps/admin/signing-certificates/index.tsx +3 -3
- package/apps/admin/transport-rules/[uid].tsx +4 -4
- package/apps/admin/transport-rules/index.tsx +3 -3
- package/apps/admin/transport-rules/new/index.tsx +3 -3
- package/apps/escrow/matters/[uid].tsx +26 -2
- package/apps/escrow/matters/new/index.tsx +4 -0
- package/apps/shared/calendar/calendarLiveUpdates.ts +11 -10
- package/apps/shared/calendar/useCalendarReminders.ts +2 -0
- package/apps/shared/components/admin/ActionAlert.tsx +32 -0
- package/apps/shared/components/admin/diagnostics/InformationPanel.tsx +3 -3
- package/apps/shared/components/admin/diagnostics/InstalledPlugins.tsx +1 -1
- package/apps/shared/components/admin/diagnostics/LogsPanel.tsx +1 -1
- package/apps/shared/components/admin/diagnostics/RuntimePanel.tsx +1 -1
- package/apps/shared/components/admin/diagnostics/SystemPanel.tsx +1 -1
- package/apps/shared/components/admin/diagnostics/diagnosticsApi.ts +2 -2
- package/apps/shared/components/admin/distributionLists/MemberListCard.tsx +4 -4
- package/apps/shared/components/admin/elevation.ts +19 -0
- package/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.tsx +58 -3
- package/apps/shared/components/admin/layout/AdminShell.tsx +6 -1
- package/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.tsx +3 -3
- package/apps/shared/components/admin/mailboxes/EscrowScopeCard.tsx +4 -4
- package/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.tsx +4 -4
- package/apps/shared/components/admin/mailboxes/ResourceSettingsCard.tsx +3 -3
- package/apps/shared/components/admin/mailboxes/ShareAccessCard.tsx +6 -6
- package/apps/shared/components/admin/settings/BrandingForm.tsx +22 -9
- package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +4 -4
- package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +33 -4
- package/apps/shared/components/admin/settings/LoadedSettingsForm.tsx +3 -3
- package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +8 -3
- package/apps/shared/components/admin/settings/MailboxPolicyForm.tsx +3 -3
- package/apps/shared/components/admin/settings/PluginsManager.tsx +195 -46
- package/apps/shared/components/admin/settings/RetentionPolicyForm.tsx +25 -12
- package/apps/shared/components/admin/setup/EscrowSetupStep.tsx +40 -7
- package/apps/shared/components/admin/setup/SetupWizard.tsx +8 -6
- package/apps/shared/components/admin/signOut.ts +3 -0
- package/apps/shared/components/admin/usePagedList.tsx +2 -2
- package/apps/shared/components/calendar/DescriptionEditor.tsx +2 -1
- package/apps/shared/components/calendar/EventDetails.tsx +34 -8
- package/apps/shared/components/calendar/EventEditor.tsx +90 -40
- package/apps/shared/components/calendar/EventFormParts.tsx +5 -2
- package/apps/shared/components/calendar/EventMatchNav.tsx +14 -2
- package/apps/shared/components/calendar/EventModal.tsx +25 -1
- package/apps/shared/components/calendar/FindATime.tsx +10 -2
- package/apps/shared/components/calendar/ListView.tsx +99 -25
- package/apps/shared/components/calendar/RecurrenceEditor.tsx +19 -8
- package/apps/shared/components/calendar/SplitDayView.tsx +14 -18
- package/apps/shared/components/calendar/TaskQuickForm.tsx +3 -2
- package/apps/shared/components/calendar/TimeGridView.tsx +35 -19
- package/apps/shared/components/calendar/gridLayout.ts +94 -0
- package/apps/shared/components/calendar/layout/CalendarShell.tsx +1 -1
- package/apps/shared/components/calendar/timePicker.ts +5 -2
- package/apps/shared/components/contacts/ContactCard.tsx +15 -4
- package/apps/shared/components/contacts/ContactDetailPane.tsx +54 -15
- package/apps/shared/components/contacts/ContactForm.tsx +61 -17
- package/apps/shared/components/contacts/ContactPhotoEditor.tsx +88 -0
- package/apps/shared/components/contacts/ContactPhotoField.tsx +36 -39
- package/apps/shared/components/contacts/layout/ContactsShell.tsx +1 -1
- package/apps/shared/components/contacts/participantDetails.ts +18 -1
- package/apps/shared/components/layout/AppShell.tsx +9 -0
- package/apps/shared/components/layout/KeyEnrollmentGate.tsx +9 -4
- package/apps/shared/components/layout/MailboxProvisioning.tsx +31 -3
- package/apps/shared/components/layout/RecoveryCodeUnlock.tsx +4 -2
- package/apps/shared/components/mail/ConversationList.tsx +49 -6
- package/apps/shared/components/mail/ConversationThreadPane.tsx +1 -1
- package/apps/shared/components/mail/MenuButton.tsx +3 -400
- package/apps/shared/components/mail/MessageDetailPane.tsx +16 -13
- package/apps/shared/components/mail/compose/ComposeToolbar.tsx +11 -1
- package/apps/shared/components/mail/compose/ComposeWindow.tsx +57 -11
- package/apps/shared/components/mail/compose/GifPicker.tsx +10 -4
- package/apps/shared/components/mail/compose/RecipientInput.tsx +9 -7
- package/apps/shared/components/mail/compose/composeFlushRegistry.ts +14 -1
- package/apps/shared/components/mail/compose/encryptionRequirement.ts +5 -0
- package/apps/shared/components/mail/compose/quotedBody.ts +31 -6
- package/apps/shared/components/mail/layout/MailShell.tsx +1 -1
- package/apps/shared/components/mail/reading/MessageBody.tsx +13 -7
- package/apps/shared/components/mail/reading/bodyContent.ts +2 -0
- package/apps/shared/components/mail/reading/bodyHtml.ts +25 -2
- package/apps/shared/components/mail/writableMailboxes.ts +2 -0
- package/apps/shared/components/rules/RuleBuilder.tsx +11 -2
- package/apps/shared/components/settings/GravatarSetting.tsx +27 -0
- package/apps/shared/components/settings/layout/SettingsShell.tsx +1 -1
- package/apps/shared/components/tasks/dueDate.ts +29 -0
- package/apps/shared/components/tasks/layout/TasksShell.tsx +1 -1
- package/apps/shared/mail/decryptedMessages.ts +51 -2
- package/apps/shared/mail/listSnapshots.ts +2 -0
- package/apps/shared/mail/outbox/sendDecision.ts +14 -3
- package/apps/shared/mail/outbox/sendJob.ts +5 -0
- package/apps/shared/mail/useMailLiveUpdates.ts +7 -1
- package/apps/shared/mail/usePermanentDelete.tsx +3 -1
- package/apps/shared/notifications/apiErrors.ts +2 -0
- package/apps/shared/notifications/preferences.ts +1 -1
- package/apps/shared/notifications/store.ts +3 -1
- package/apps/shared/plugins/pluginNav.ts +4 -2
- package/apps/www/calendar/index.tsx +24 -12
- package/apps/www/contacts/[uid].tsx +6 -1
- package/apps/www/contacts/index.tsx +66 -12
- package/apps/www/index.tsx +91 -28
- package/apps/www/messages/[uid].tsx +8 -3
- package/apps/www/settings/auto-reply/index.tsx +13 -0
- package/apps/www/settings/blocked-senders/index.tsx +4 -0
- package/apps/www/settings/encryption/index.tsx +66 -4
- package/apps/www/settings/privacy/index.tsx +4 -1
- package/apps/www/settings/profile/index.tsx +12 -1
- package/apps/www/settings/sharing/index.tsx +11 -1
- package/apps/www/settings/signatures/[uid].tsx +6 -4
- package/apps/www/tasks/index.tsx +97 -29
- package/dist/apps/admin/audit-log/index.js +3 -3
- package/dist/apps/admin/branding/index.js +3 -3
- package/dist/apps/admin/data-requests/index.js +17 -7
- package/dist/apps/admin/distribution-lists/[uid].js +3 -3
- package/dist/apps/admin/distribution-lists/index.js +3 -3
- package/dist/apps/admin/distribution-lists/new/index.js +3 -3
- package/dist/apps/admin/domains/[uid].js +6 -5
- package/dist/apps/admin/domains/index.js +3 -3
- package/dist/apps/admin/domains/new/index.js +3 -3
- package/dist/apps/admin/escrow-scopes/[uid].js +11 -6
- package/dist/apps/admin/escrow-scopes/index.js +3 -3
- package/dist/apps/admin/escrow-scopes/new/index.js +9 -4
- package/dist/apps/admin/index.js +4 -4
- package/dist/apps/admin/ingest-queue/index.js +3 -3
- package/dist/apps/admin/mailboxes/[uid].js +15 -7
- package/dist/apps/admin/quarantine/index.js +4 -4
- package/dist/apps/admin/retention-policy/index.js +3 -3
- package/dist/apps/admin/signing-certificates/index.js +3 -3
- package/dist/apps/admin/transport-rules/[uid].js +4 -4
- package/dist/apps/admin/transport-rules/index.js +3 -3
- package/dist/apps/admin/transport-rules/new/index.js +3 -3
- package/dist/apps/escrow/matters/[uid].js +8 -3
- package/dist/apps/escrow/matters/new/index.js +4 -0
- package/dist/apps/shared/calendar/calendarLiveUpdates.d.ts +11 -7
- package/dist/apps/shared/calendar/calendarLiveUpdates.js +10 -9
- package/dist/apps/shared/calendar/useCalendarReminders.js +2 -0
- package/dist/apps/shared/components/admin/ActionAlert.d.ts +13 -0
- package/dist/apps/shared/components/admin/ActionAlert.js +24 -0
- package/dist/apps/shared/components/admin/diagnostics/InformationPanel.js +2 -2
- package/dist/apps/shared/components/admin/diagnostics/InstalledPlugins.js +1 -1
- package/dist/apps/shared/components/admin/diagnostics/LogsPanel.js +1 -1
- package/dist/apps/shared/components/admin/diagnostics/RuntimePanel.js +1 -1
- package/dist/apps/shared/components/admin/diagnostics/SystemPanel.js +1 -1
- package/dist/apps/shared/components/admin/diagnostics/diagnosticsApi.d.ts +2 -2
- package/dist/apps/shared/components/admin/distributionLists/MemberListCard.js +4 -4
- package/dist/apps/shared/components/admin/elevation.d.ts +9 -0
- package/dist/apps/shared/components/admin/elevation.js +16 -0
- package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.d.ts +2 -0
- package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.js +41 -1
- package/dist/apps/shared/components/admin/layout/AdminShell.js +2 -1
- package/dist/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.js +3 -3
- package/dist/apps/shared/components/admin/mailboxes/EscrowScopeCard.js +4 -4
- package/dist/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.js +4 -4
- package/dist/apps/shared/components/admin/mailboxes/ResourceSettingsCard.js +3 -3
- package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +6 -6
- package/dist/apps/shared/components/admin/settings/BrandingForm.js +17 -7
- package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +4 -4
- package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.js +17 -5
- package/dist/apps/shared/components/admin/settings/LoadedSettingsForm.js +3 -3
- package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +8 -3
- package/dist/apps/shared/components/admin/settings/MailboxPolicyForm.js +3 -3
- package/dist/apps/shared/components/admin/settings/PluginsManager.js +103 -18
- package/dist/apps/shared/components/admin/settings/RetentionPolicyForm.js +17 -10
- package/dist/apps/shared/components/admin/setup/EscrowSetupStep.js +18 -8
- package/dist/apps/shared/components/admin/setup/SetupWizard.js +8 -6
- package/dist/apps/shared/components/admin/signOut.js +3 -0
- package/dist/apps/shared/components/admin/usePagedList.js +2 -2
- package/dist/apps/shared/components/calendar/DescriptionEditor.js +2 -1
- package/dist/apps/shared/components/calendar/EventDetails.d.ts +3 -1
- package/dist/apps/shared/components/calendar/EventDetails.js +7 -4
- package/dist/apps/shared/components/calendar/EventEditor.js +81 -33
- package/dist/apps/shared/components/calendar/EventFormParts.d.ts +3 -1
- package/dist/apps/shared/components/calendar/EventFormParts.js +3 -3
- package/dist/apps/shared/components/calendar/EventMatchNav.d.ts +1 -1
- package/dist/apps/shared/components/calendar/EventMatchNav.js +12 -2
- package/dist/apps/shared/components/calendar/EventModal.js +10 -2
- package/dist/apps/shared/components/calendar/FindATime.js +4 -3
- package/dist/apps/shared/components/calendar/ListView.d.ts +10 -4
- package/dist/apps/shared/components/calendar/ListView.js +92 -27
- package/dist/apps/shared/components/calendar/RecurrenceEditor.d.ts +4 -1
- package/dist/apps/shared/components/calendar/RecurrenceEditor.js +15 -10
- package/dist/apps/shared/components/calendar/SplitDayView.js +11 -17
- package/dist/apps/shared/components/calendar/TaskQuickForm.js +3 -2
- package/dist/apps/shared/components/calendar/TimeGridView.js +23 -21
- package/dist/apps/shared/components/calendar/gridLayout.d.ts +41 -0
- package/dist/apps/shared/components/calendar/gridLayout.js +83 -0
- package/dist/apps/shared/components/calendar/layout/CalendarShell.js +1 -1
- package/dist/apps/shared/components/calendar/timePicker.js +5 -2
- package/dist/apps/shared/components/contacts/ContactCard.js +9 -3
- package/dist/apps/shared/components/contacts/ContactDetailPane.d.ts +5 -3
- package/dist/apps/shared/components/contacts/ContactDetailPane.js +11 -5
- package/dist/apps/shared/components/contacts/ContactForm.js +54 -16
- package/dist/apps/shared/components/contacts/ContactPhotoEditor.d.ts +21 -0
- package/dist/apps/shared/components/contacts/ContactPhotoEditor.js +56 -0
- package/dist/apps/shared/components/contacts/ContactPhotoField.d.ts +7 -4
- package/dist/apps/shared/components/contacts/ContactPhotoField.js +19 -21
- package/dist/apps/shared/components/contacts/layout/ContactsShell.js +1 -1
- package/dist/apps/shared/components/contacts/participantDetails.d.ts +4 -1
- package/dist/apps/shared/components/contacts/participantDetails.js +17 -1
- package/dist/apps/shared/components/layout/AppShell.js +9 -0
- package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +2 -3
- package/dist/apps/shared/components/layout/MailboxProvisioning.d.ts +4 -1
- package/dist/apps/shared/components/layout/MailboxProvisioning.js +24 -2
- package/dist/apps/shared/components/layout/RecoveryCodeUnlock.d.ts +4 -2
- package/dist/apps/shared/components/layout/RecoveryCodeUnlock.js +4 -2
- package/dist/apps/shared/components/mail/ConversationList.js +49 -6
- package/dist/apps/shared/components/mail/ConversationThreadPane.js +1 -1
- package/dist/apps/shared/components/mail/MenuButton.d.ts +2 -78
- package/dist/apps/shared/components/mail/MenuButton.js +2 -211
- package/dist/apps/shared/components/mail/MessageDetailPane.js +10 -6
- package/dist/apps/shared/components/mail/compose/ComposeToolbar.d.ts +3 -0
- package/dist/apps/shared/components/mail/compose/ComposeToolbar.js +10 -1
- package/dist/apps/shared/components/mail/compose/ComposeWindow.js +55 -10
- package/dist/apps/shared/components/mail/compose/GifPicker.js +10 -4
- package/dist/apps/shared/components/mail/compose/RecipientInput.js +9 -7
- package/dist/apps/shared/components/mail/compose/composeFlushRegistry.d.ts +7 -1
- package/dist/apps/shared/components/mail/compose/composeFlushRegistry.js +13 -1
- package/dist/apps/shared/components/mail/compose/encryptionRequirement.d.ts +5 -0
- package/dist/apps/shared/components/mail/compose/quotedBody.js +28 -5
- package/dist/apps/shared/components/mail/layout/MailShell.js +1 -1
- package/dist/apps/shared/components/mail/reading/MessageBody.js +11 -6
- package/dist/apps/shared/components/mail/reading/bodyContent.js +2 -0
- package/dist/apps/shared/components/mail/reading/bodyHtml.d.ts +5 -1
- package/dist/apps/shared/components/mail/reading/bodyHtml.js +23 -2
- package/dist/apps/shared/components/mail/writableMailboxes.js +2 -0
- package/dist/apps/shared/components/rules/RuleBuilder.js +9 -1
- package/dist/apps/shared/components/settings/GravatarSetting.d.ts +6 -0
- package/dist/apps/shared/components/settings/GravatarSetting.js +15 -0
- package/dist/apps/shared/components/settings/layout/SettingsShell.js +1 -1
- package/dist/apps/shared/components/tasks/dueDate.d.ts +12 -0
- package/dist/apps/shared/components/tasks/dueDate.js +26 -0
- package/dist/apps/shared/components/tasks/layout/TasksShell.js +1 -1
- package/dist/apps/shared/mail/decryptedMessages.d.ts +8 -0
- package/dist/apps/shared/mail/decryptedMessages.js +46 -2
- package/dist/apps/shared/mail/listSnapshots.js +2 -0
- package/dist/apps/shared/mail/outbox/sendDecision.d.ts +6 -3
- package/dist/apps/shared/mail/outbox/sendDecision.js +13 -3
- package/dist/apps/shared/mail/outbox/sendJob.d.ts +1 -0
- package/dist/apps/shared/mail/outbox/sendJob.js +5 -1
- package/dist/apps/shared/mail/useMailLiveUpdates.d.ts +3 -0
- package/dist/apps/shared/mail/useMailLiveUpdates.js +6 -1
- package/dist/apps/shared/mail/usePermanentDelete.d.ts +2 -0
- package/dist/apps/shared/mail/usePermanentDelete.js +1 -1
- package/dist/apps/shared/notifications/apiErrors.js +2 -0
- package/dist/apps/shared/notifications/preferences.d.ts +1 -1
- package/dist/apps/shared/notifications/preferences.js +1 -1
- package/dist/apps/shared/notifications/store.js +3 -1
- package/dist/apps/shared/plugins/pluginNav.d.ts +2 -1
- package/dist/apps/shared/plugins/pluginNav.js +4 -2
- package/dist/apps/www/calendar/index.js +24 -12
- package/dist/apps/www/contacts/[uid].js +6 -2
- package/dist/apps/www/contacts/index.js +62 -11
- package/dist/apps/www/index.js +85 -30
- package/dist/apps/www/messages/[uid].js +8 -3
- package/dist/apps/www/settings/auto-reply/index.js +13 -1
- package/dist/apps/www/settings/blocked-senders/index.js +4 -0
- package/dist/apps/www/settings/encryption/index.js +29 -5
- package/dist/apps/www/settings/privacy/index.js +3 -2
- package/dist/apps/www/settings/profile/index.js +4 -1
- package/dist/apps/www/settings/sharing/index.js +10 -1
- package/dist/apps/www/settings/signatures/[uid].js +6 -4
- package/dist/apps/www/tasks/index.js +94 -32
- package/dist/lib/admin/auditLogApi.js +2 -2
- package/dist/lib/admin/distributionListsApi.js +2 -2
- package/dist/lib/admin/domainsApi.js +2 -2
- package/dist/lib/admin/escrowAuditLogApi.js +2 -2
- package/dist/lib/admin/escrowScopesApi.js +2 -2
- package/dist/lib/admin/pluginsApi.d.ts +31 -2
- package/dist/lib/admin/pluginsApi.js +23 -1
- package/dist/lib/admin/retentionPolicyApi.d.ts +3 -0
- package/dist/lib/admin/retentionPolicyApi.js +3 -0
- package/dist/lib/admin/transportRulesApi.js +2 -2
- package/dist/lib/appearance/preferencesApi.d.ts +1 -1
- package/dist/lib/appearance/preferencesApi.js +3 -14
- package/dist/lib/auth/session.d.ts +51 -12
- package/dist/lib/auth/session.js +214 -38
- package/dist/lib/branding/brandingApi.d.ts +3 -3
- package/dist/lib/branding/brandingApi.js +9 -21
- package/dist/lib/calendar/calendarApi.js +3 -1
- package/dist/lib/calendar/eventSearch.js +43 -6
- package/dist/lib/calendar/freeBusyApi.d.ts +2 -1
- package/dist/lib/calendar/freeBusyApi.js +13 -5
- package/dist/lib/calendar/recurrence.js +51 -3
- package/dist/lib/calendar/useDayKey.d.ts +7 -0
- package/dist/lib/calendar/useDayKey.js +43 -0
- package/dist/lib/components/avatar/ContactAvatar.d.ts +6 -2
- package/dist/lib/components/avatar/ContactAvatar.js +37 -8
- package/dist/lib/components/avatar/ContactPhotoAvatar.d.ts +11 -0
- package/dist/lib/components/avatar/ContactPhotoAvatar.js +18 -0
- package/dist/lib/components/forms/CameraCaptureModal.d.ts +17 -0
- package/dist/lib/components/forms/CameraCaptureModal.js +88 -0
- package/dist/lib/components/forms/ImageEditBadge.d.ts +28 -0
- package/dist/lib/components/forms/ImageEditBadge.js +62 -0
- package/dist/lib/components/menus/MenuButton.d.ts +78 -0
- package/dist/lib/components/menus/MenuButton.js +219 -0
- package/dist/lib/components/navigation/BottomTabBar.d.ts +3 -1
- package/dist/lib/components/navigation/BottomTabBar.js +22 -3
- package/dist/lib/components/overlays/PopoverPortal.d.ts +2 -1
- package/dist/lib/components/overlays/PopoverPortal.js +3 -2
- package/dist/lib/components/pickers/TimeZonePicker.js +13 -1
- package/dist/lib/contacts/contactsApi.d.ts +5 -2
- package/dist/lib/contacts/contactsApi.js +16 -19
- package/dist/lib/contacts/gravatar.d.ts +14 -3
- package/dist/lib/contacts/gravatar.js +42 -5
- package/dist/lib/contacts/preparePhoto.d.ts +24 -0
- package/dist/lib/contacts/preparePhoto.js +250 -0
- package/dist/lib/contacts/useContactPhotoSrc.d.ts +19 -0
- package/dist/lib/contacts/useContactPhotoSrc.js +153 -0
- package/dist/lib/contacts/vcard.d.ts +3 -0
- package/dist/lib/contacts/vcard.js +59 -7
- package/dist/lib/crypto/escrowKeys.d.ts +8 -0
- package/dist/lib/crypto/escrowKeys.js +20 -1
- package/dist/lib/crypto/messageSecurity.d.ts +7 -4
- package/dist/lib/crypto/messageSecurity.js +23 -5
- package/dist/lib/crypto/passwordUnlock.d.ts +6 -1
- package/dist/lib/crypto/passwordUnlock.js +13 -2
- package/dist/lib/crypto/smime.js +12 -2
- package/dist/lib/mail/compose/composeQuoting.js +3 -2
- package/dist/lib/mail/compose/quotedImages.d.ts +6 -3
- package/dist/lib/mail/compose/quotedImages.js +35 -9
- package/dist/lib/mail/labelsApi.js +2 -2
- package/dist/lib/mail/mailApi.d.ts +8 -5
- package/dist/lib/mail/mailApi.js +73 -39
- package/dist/lib/mail/mailDetailHooks.js +11 -3
- package/dist/lib/mail/mailFilterRulesApi.js +2 -2
- package/dist/lib/mail/mailSignaturesApi.js +2 -2
- package/dist/lib/mail/mailboxImportApi.d.ts +1 -1
- package/dist/lib/mail/mailboxImportApi.js +3 -15
- package/dist/lib/mail/messageBodySanitizer.d.ts +6 -0
- package/dist/lib/mail/messageBodySanitizer.js +44 -1
- package/dist/lib/search/queryGrammar.js +12 -4
- package/dist/lib/search/searchTier3.d.ts +6 -1
- package/dist/lib/search/searchTier3.js +54 -25
- package/dist/lib/tasks/tasksApi.js +3 -3
- package/dist/lib/util/api.d.ts +26 -4
- package/dist/lib/util/api.js +73 -16
- package/dist/lib/util/apiQuery.d.ts +11 -0
- package/dist/lib/util/apiQuery.js +11 -0
- package/dist/lib/util/settleWithLimit.d.ts +5 -0
- package/dist/lib/util/settleWithLimit.js +25 -0
- package/lib/admin/auditLogApi.ts +2 -2
- package/lib/admin/distributionListsApi.ts +2 -2
- package/lib/admin/domainsApi.ts +2 -2
- package/lib/admin/escrowAuditLogApi.ts +2 -2
- package/lib/admin/escrowScopesApi.ts +2 -2
- package/lib/admin/pluginsApi.ts +45 -3
- package/lib/admin/retentionPolicyApi.ts +4 -0
- package/lib/admin/transportRulesApi.ts +2 -2
- package/lib/appearance/preferencesApi.ts +3 -14
- package/lib/auth/session.ts +239 -39
- package/lib/branding/brandingApi.ts +9 -21
- package/lib/calendar/calendarApi.ts +3 -1
- package/lib/calendar/eventSearch.ts +46 -6
- package/lib/calendar/freeBusyApi.ts +14 -6
- package/lib/calendar/recurrence.ts +59 -8
- package/lib/calendar/useDayKey.ts +45 -0
- package/lib/components/avatar/ContactAvatar.tsx +46 -8
- package/lib/components/avatar/ContactPhotoAvatar.tsx +26 -0
- package/lib/components/forms/CameraCaptureModal.tsx +133 -0
- package/lib/components/forms/ImageEditBadge.tsx +121 -0
- package/lib/components/menus/MenuButton.tsx +408 -0
- package/lib/components/navigation/BottomTabBar.tsx +30 -8
- package/lib/components/overlays/PopoverPortal.tsx +3 -2
- package/lib/components/pickers/TimeZonePicker.tsx +17 -1
- package/lib/contacts/contactsApi.ts +18 -19
- package/lib/contacts/gravatar.ts +47 -5
- package/lib/contacts/preparePhoto.ts +273 -0
- package/lib/contacts/useContactPhotoSrc.ts +177 -0
- package/lib/contacts/vcard.ts +60 -7
- package/lib/crypto/escrowKeys.ts +21 -1
- package/lib/crypto/messageSecurity.ts +33 -9
- package/lib/crypto/passwordUnlock.ts +14 -2
- package/lib/crypto/smime.ts +13 -2
- package/lib/mail/compose/composeQuoting.ts +3 -2
- package/lib/mail/compose/quotedImages.ts +39 -9
- package/lib/mail/labelsApi.ts +2 -2
- package/lib/mail/mailApi.ts +96 -39
- package/lib/mail/mailDetailHooks.ts +11 -3
- package/lib/mail/mailFilterRulesApi.ts +2 -2
- package/lib/mail/mailSignaturesApi.ts +2 -2
- package/lib/mail/mailboxImportApi.ts +9 -15
- package/lib/mail/messageBodySanitizer.ts +47 -1
- package/lib/search/queryGrammar.ts +13 -4
- package/lib/search/searchTier3.ts +65 -32
- package/lib/tasks/tasksApi.ts +3 -3
- package/lib/util/api.ts +99 -16
- package/lib/util/apiQuery.ts +13 -0
- package/lib/util/settleWithLimit.ts +25 -0
- package/package.json +1 -1
package/lib/contacts/vcard.ts
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
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
|
-
|
|
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);
|
package/lib/crypto/escrowKeys.ts
CHANGED
|
@@ -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`)
|
|
127
|
-
*
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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. */
|
package/lib/crypto/smime.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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 (
|
|
39
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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;
|
package/lib/mail/labelsApi.ts
CHANGED
|
@@ -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> {
|