@rapidmx/web-client 0.35.0 → 0.37.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/escrow/matters/[uid].tsx +4 -3
- package/apps/shared/calendar/calendarReminders.ts +35 -0
- package/apps/shared/calendar/sealedEvents.ts +113 -0
- package/apps/shared/calendar/useCalendarReminders.ts +28 -9
- package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +4 -0
- package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +89 -3
- package/apps/shared/components/calendar/EventDetails.tsx +13 -2
- package/apps/shared/components/calendar/EventEditor.tsx +124 -17
- package/apps/shared/components/calendar/EventFormParts.tsx +1 -0
- package/apps/shared/components/calendar/EventModal.tsx +19 -2
- package/apps/shared/components/calendar/RequestChangeForm.tsx +4 -1
- package/apps/shared/components/calendar/eventForm.ts +2 -0
- package/apps/shared/components/contacts/ContactCard.tsx +13 -1
- package/apps/shared/components/contacts/ContactDetailPane.tsx +189 -9
- package/apps/shared/components/contacts/ContactForm.tsx +56 -7
- package/apps/shared/components/contacts/KeyChangeReview.tsx +5 -1
- package/apps/shared/components/contacts/contactCardData.ts +8 -4
- package/apps/shared/components/contacts/contactKeys.ts +3 -3
- package/apps/shared/components/layout/AppShell.tsx +34 -5
- package/apps/shared/components/layout/KeyEnrollmentGate.tsx +30 -1
- package/apps/shared/components/layout/SharedMailboxKeyManager.tsx +122 -0
- package/apps/shared/components/layout/UnlockPromptProvider.tsx +34 -0
- package/apps/shared/components/mail/ConversationList.tsx +1 -1
- package/apps/shared/components/mail/InviteCard.tsx +11 -1
- package/apps/shared/components/mail/MessageDetailPane.tsx +59 -26
- package/apps/shared/components/mail/compose/ComposeWindow.tsx +9 -2
- package/apps/shared/components/mail/compose/RecipientInput.tsx +2 -1
- package/apps/shared/components/mail/compose/draftResume.ts +28 -3
- package/apps/shared/components/mail/compose/protectedContactSuggestions.ts +121 -0
- package/apps/shared/components/mail/compose/quotedBody.ts +4 -3
- package/apps/shared/components/mail/invite/inviteStore.ts +17 -1
- package/apps/shared/components/mail/layout/MailShell.tsx +2 -0
- package/apps/shared/components/mail/pinnedSigners.ts +14 -0
- package/apps/shared/components/mail/reading/conversationPrint.ts +3 -3
- package/apps/shared/components/mail/reading/useMessageActions.ts +4 -1
- package/apps/shared/components/settings/KeyExportSetting.tsx +173 -0
- package/apps/shared/components/settings/OpenPgpKeySetting.tsx +240 -0
- package/apps/shared/components/settings/PolicyLockedNotice.tsx +23 -0
- package/apps/shared/components/settings/ZeroAccessSetting.tsx +128 -0
- package/apps/shared/components/settings/ZeroAccessTypes.tsx +175 -0
- package/apps/shared/components/settings/layout/SettingsShell.tsx +9 -2
- package/apps/shared/mail/decryptedMessages.ts +11 -3
- package/apps/shared/mail/outbox/sendJob.ts +349 -50
- package/apps/shared/mail/outbox/sendOutcomes.ts +91 -20
- package/apps/shared/mail/reportNotices.ts +3 -1
- package/apps/shared/mail/zeroAccessSource.ts +27 -0
- package/apps/shared/search/LocalIndexLifecycle.tsx +84 -24
- package/apps/shared/search/localIndexBuilder.ts +57 -20
- package/apps/shared/search/localIndexRpcClient.ts +96 -12
- package/apps/shared/search/localIndexSchema.ts +20 -4
- package/apps/shared/search/localIndexTransport.ts +6 -2
- package/apps/shared/search/localIndexWorker.ts +37 -18
- package/apps/shared/search/searchTier2.ts +5 -0
- package/apps/shared/search/zeroAccessIndexSync.ts +679 -0
- package/apps/shared/search/zeroAccessSyncState.ts +70 -0
- package/apps/shared/zeroAccess/ProtectedMark.tsx +19 -0
- package/apps/shared/zeroAccess/useKeysLocked.ts +25 -0
- package/apps/shared/zeroAccess/useOpenedRecord.ts +71 -0
- package/apps/www/_shell.tsx +2 -0
- package/apps/www/calendar/index.tsx +225 -61
- package/apps/www/contacts/index.tsx +67 -11
- package/apps/www/index.tsx +19 -3
- package/apps/www/settings/encryption/index.tsx +21 -1
- package/apps/www/settings/privacy/index.tsx +89 -39
- package/apps/www/tasks/index.tsx +24 -6
- package/dist/apps/escrow/matters/[uid].js +2 -2
- package/dist/apps/shared/calendar/calendarReminders.d.ts +14 -0
- package/dist/apps/shared/calendar/calendarReminders.js +26 -0
- package/dist/apps/shared/calendar/sealedEvents.d.ts +14 -0
- package/dist/apps/shared/calendar/sealedEvents.js +85 -0
- package/dist/apps/shared/calendar/useCalendarReminders.d.ts +2 -1
- package/dist/apps/shared/calendar/useCalendarReminders.js +27 -9
- package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +3 -0
- package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.js +25 -2
- package/dist/apps/shared/components/calendar/EventDetails.js +13 -2
- package/dist/apps/shared/components/calendar/EventEditor.js +115 -18
- package/dist/apps/shared/components/calendar/EventFormParts.js +1 -1
- package/dist/apps/shared/components/calendar/EventModal.d.ts +1 -1
- package/dist/apps/shared/components/calendar/EventModal.js +15 -2
- package/dist/apps/shared/components/calendar/RequestChangeForm.d.ts +3 -0
- package/dist/apps/shared/components/calendar/RequestChangeForm.js +1 -1
- package/dist/apps/shared/components/calendar/eventForm.d.ts +2 -0
- package/dist/apps/shared/components/contacts/ContactCard.js +12 -1
- package/dist/apps/shared/components/contacts/ContactDetailPane.d.ts +2 -1
- package/dist/apps/shared/components/contacts/ContactDetailPane.js +103 -9
- package/dist/apps/shared/components/contacts/ContactForm.d.ts +4 -1
- package/dist/apps/shared/components/contacts/ContactForm.js +37 -7
- package/dist/apps/shared/components/contacts/KeyChangeReview.d.ts +3 -1
- package/dist/apps/shared/components/contacts/KeyChangeReview.js +3 -2
- package/dist/apps/shared/components/contacts/contactCardData.js +8 -4
- package/dist/apps/shared/components/contacts/contactKeys.d.ts +1 -1
- package/dist/apps/shared/components/contacts/contactKeys.js +3 -3
- package/dist/apps/shared/components/layout/AppShell.d.ts +18 -1
- package/dist/apps/shared/components/layout/AppShell.js +17 -6
- package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +27 -2
- package/dist/apps/shared/components/layout/SharedMailboxKeyManager.d.ts +17 -0
- package/dist/apps/shared/components/layout/SharedMailboxKeyManager.js +98 -0
- package/dist/apps/shared/components/layout/UnlockPromptProvider.d.ts +3 -0
- package/dist/apps/shared/components/layout/UnlockPromptProvider.js +32 -0
- package/dist/apps/shared/components/mail/ConversationList.js +1 -1
- package/dist/apps/shared/components/mail/InviteCard.js +11 -1
- package/dist/apps/shared/components/mail/MessageDetailPane.js +46 -21
- package/dist/apps/shared/components/mail/compose/ComposeWindow.js +2 -2
- package/dist/apps/shared/components/mail/compose/RecipientInput.js +3 -2
- package/dist/apps/shared/components/mail/compose/draftResume.d.ts +2 -0
- package/dist/apps/shared/components/mail/compose/draftResume.js +28 -3
- package/dist/apps/shared/components/mail/compose/protectedContactSuggestions.d.ts +14 -0
- package/dist/apps/shared/components/mail/compose/protectedContactSuggestions.js +96 -0
- package/dist/apps/shared/components/mail/compose/quotedBody.d.ts +1 -1
- package/dist/apps/shared/components/mail/compose/quotedBody.js +4 -3
- package/dist/apps/shared/components/mail/invite/inviteStore.js +16 -1
- package/dist/apps/shared/components/mail/layout/MailShell.js +2 -1
- package/dist/apps/shared/components/mail/pinnedSigners.d.ts +6 -0
- package/dist/apps/shared/components/mail/pinnedSigners.js +10 -1
- package/dist/apps/shared/components/mail/reading/conversationPrint.d.ts +2 -2
- package/dist/apps/shared/components/mail/reading/conversationPrint.js +3 -3
- package/dist/apps/shared/components/mail/reading/useMessageActions.js +4 -1
- package/dist/apps/shared/components/settings/KeyExportSetting.d.ts +13 -0
- package/dist/apps/shared/components/settings/KeyExportSetting.js +90 -0
- package/dist/apps/shared/components/settings/OpenPgpKeySetting.d.ts +13 -0
- package/dist/apps/shared/components/settings/OpenPgpKeySetting.js +143 -0
- package/dist/apps/shared/components/settings/PolicyLockedNotice.d.ts +11 -0
- package/dist/apps/shared/components/settings/PolicyLockedNotice.js +9 -0
- package/dist/apps/shared/components/settings/ZeroAccessSetting.d.ts +16 -0
- package/dist/apps/shared/components/settings/ZeroAccessSetting.js +80 -0
- package/dist/apps/shared/components/settings/ZeroAccessTypes.d.ts +29 -0
- package/dist/apps/shared/components/settings/ZeroAccessTypes.js +107 -0
- package/dist/apps/shared/components/settings/layout/SettingsShell.d.ts +3 -0
- package/dist/apps/shared/components/settings/layout/SettingsShell.js +3 -2
- package/dist/apps/shared/mail/decryptedMessages.d.ts +1 -1
- package/dist/apps/shared/mail/decryptedMessages.js +11 -3
- package/dist/apps/shared/mail/outbox/sendJob.d.ts +37 -4
- package/dist/apps/shared/mail/outbox/sendJob.js +287 -42
- package/dist/apps/shared/mail/outbox/sendOutcomes.d.ts +12 -0
- package/dist/apps/shared/mail/outbox/sendOutcomes.js +76 -20
- package/dist/apps/shared/mail/reportNotices.js +3 -1
- package/dist/apps/shared/mail/zeroAccessSource.d.ts +8 -0
- package/dist/apps/shared/mail/zeroAccessSource.js +26 -0
- package/dist/apps/shared/search/LocalIndexLifecycle.d.ts +2 -0
- package/dist/apps/shared/search/LocalIndexLifecycle.js +74 -24
- package/dist/apps/shared/search/localIndexBuilder.d.ts +9 -1
- package/dist/apps/shared/search/localIndexBuilder.js +50 -17
- package/dist/apps/shared/search/localIndexRpcClient.d.ts +5 -39
- package/dist/apps/shared/search/localIndexRpcClient.js +129 -11
- package/dist/apps/shared/search/localIndexSchema.d.ts +14 -2
- package/dist/apps/shared/search/localIndexSchema.js +18 -4
- package/dist/apps/shared/search/localIndexTransport.d.ts +6 -2
- package/dist/apps/shared/search/localIndexWorker.d.ts +11 -0
- package/dist/apps/shared/search/localIndexWorker.js +22 -15
- package/dist/apps/shared/search/searchTier2.js +5 -0
- package/dist/apps/shared/search/zeroAccessIndexSync.d.ts +102 -0
- package/dist/apps/shared/search/zeroAccessIndexSync.js +544 -0
- package/dist/apps/shared/search/zeroAccessSyncState.d.ts +20 -0
- package/dist/apps/shared/search/zeroAccessSyncState.js +65 -0
- package/dist/apps/shared/zeroAccess/ProtectedMark.d.ts +7 -0
- package/dist/apps/shared/zeroAccess/ProtectedMark.js +10 -0
- package/dist/apps/shared/zeroAccess/useKeysLocked.d.ts +6 -0
- package/dist/apps/shared/zeroAccess/useKeysLocked.js +20 -0
- package/dist/apps/shared/zeroAccess/useOpenedRecord.d.ts +18 -0
- package/dist/apps/shared/zeroAccess/useOpenedRecord.js +66 -0
- package/dist/apps/www/_shell.d.ts +1 -1
- package/dist/apps/www/_shell.js +2 -2
- package/dist/apps/www/calendar/index.js +88 -20
- package/dist/apps/www/contacts/index.js +64 -11
- package/dist/apps/www/index.js +19 -3
- package/dist/apps/www/settings/encryption/index.js +16 -4
- package/dist/apps/www/settings/privacy/index.js +47 -6
- package/dist/apps/www/tasks/index.js +21 -7
- package/dist/lib/admin/domainsApi.d.ts +1 -1
- package/dist/lib/admin/escrowAccessRequestsApi.d.ts +6 -0
- package/dist/lib/calendar/calendarApi.d.ts +52 -6
- package/dist/lib/calendar/calendarApi.js +110 -17
- package/dist/lib/calendar/calendarMutations.d.ts +20 -7
- package/dist/lib/calendar/calendarMutations.js +73 -14
- package/dist/lib/calendar/inviteApi.d.ts +17 -3
- package/dist/lib/calendar/inviteApi.js +86 -7
- package/dist/lib/calendar/protectedEvents.d.ts +31 -0
- package/dist/lib/calendar/protectedEvents.js +113 -0
- package/dist/lib/calendar/recurrence.d.ts +2 -0
- package/dist/lib/calendar/recurrence.js +16 -0
- package/dist/lib/calendar/sealedInvites.d.ts +9 -0
- package/dist/lib/calendar/sealedInvites.js +65 -0
- package/dist/lib/contacts/contactsApi.d.ts +29 -3
- package/dist/lib/contacts/contactsApi.js +23 -7
- package/dist/lib/contacts/useContactPhotoSrc.d.ts +5 -2
- package/dist/lib/contacts/useContactPhotoSrc.js +91 -11
- package/dist/lib/crypto/composeSecurity.d.ts +11 -3
- package/dist/lib/crypto/composeSecurity.js +13 -5
- package/dist/lib/crypto/keyExport.d.ts +54 -0
- package/dist/lib/crypto/keyExport.js +161 -0
- package/dist/lib/crypto/keyPairMatch.d.ts +9 -0
- package/dist/lib/crypto/keyPairMatch.js +65 -0
- package/dist/lib/crypto/keySession.d.ts +64 -3
- package/dist/lib/crypto/keySession.js +179 -12
- package/dist/lib/crypto/keyvaultApi.d.ts +129 -8
- package/dist/lib/crypto/keyvaultApi.js +45 -3
- package/dist/lib/crypto/meetingKeys.d.ts +25 -0
- package/dist/lib/crypto/meetingKeys.js +87 -0
- package/dist/lib/crypto/messageSecurity.d.ts +56 -3
- package/dist/lib/crypto/messageSecurity.js +201 -13
- package/dist/lib/crypto/mime.d.ts +5 -0
- package/dist/lib/crypto/mime.js +25 -0
- package/dist/lib/crypto/openPgpKey.d.ts +39 -0
- package/dist/lib/crypto/openPgpKey.js +89 -0
- package/dist/lib/crypto/openpgp.d.ts +47 -0
- package/dist/lib/crypto/openpgp.js +88 -0
- package/dist/lib/crypto/pgpMessage.d.ts +51 -0
- package/dist/lib/crypto/pgpMessage.js +271 -0
- package/dist/lib/crypto/sharedMailboxKeys.d.ts +62 -0
- package/dist/lib/crypto/sharedMailboxKeys.js +337 -0
- package/dist/lib/crypto/smimeMessage.d.ts +21 -0
- package/dist/lib/crypto/smimeMessage.js +11 -7
- package/dist/lib/crypto/zeroAccessKey.d.ts +32 -0
- package/dist/lib/crypto/zeroAccessKey.js +71 -0
- package/dist/lib/mail/conversationsApi.d.ts +6 -0
- package/dist/lib/mail/mailApi.d.ts +63 -2
- package/dist/lib/mail/mailApi.js +5 -1
- package/dist/lib/mail/mailboxImportApi.d.ts +10 -0
- package/dist/lib/mail/mailboxImportApi.js +15 -0
- package/dist/lib/search/zeroAccessIndexApi.d.ts +85 -0
- package/dist/lib/search/zeroAccessIndexApi.js +35 -0
- package/dist/lib/tasks/tasksApi.d.ts +11 -1
- package/dist/lib/tasks/tasksApi.js +10 -4
- package/dist/lib/util/clientPlatform.d.ts +14 -0
- package/dist/lib/util/clientPlatform.js +26 -0
- package/dist/lib/videoconf/protectedMeetings.d.ts +74 -0
- package/dist/lib/videoconf/protectedMeetings.js +181 -0
- package/dist/lib/videoconf/videoMeetingsApi.d.ts +25 -4
- package/dist/lib/videoconf/videoMeetingsApi.js +1 -1
- package/dist/lib/zeroAccess/sealedMoves.d.ts +41 -0
- package/dist/lib/zeroAccess/sealedMoves.js +63 -0
- package/dist/lib/zeroAccess/sealedRecords.d.ts +29 -0
- package/dist/lib/zeroAccess/sealedRecords.js +136 -0
- package/lib/admin/domainsApi.ts +1 -1
- package/lib/admin/escrowAccessRequestsApi.ts +6 -0
- package/lib/calendar/calendarApi.ts +143 -17
- package/lib/calendar/calendarMutations.ts +79 -12
- package/lib/calendar/inviteApi.ts +101 -8
- package/lib/calendar/protectedEvents.ts +143 -0
- package/lib/calendar/recurrence.ts +17 -0
- package/lib/calendar/sealedInvites.ts +73 -0
- package/lib/contacts/contactsApi.ts +43 -17
- package/lib/contacts/useContactPhotoSrc.ts +103 -13
- package/lib/crypto/composeSecurity.ts +20 -5
- package/lib/crypto/keyExport.ts +229 -0
- package/lib/crypto/keyPairMatch.ts +69 -0
- package/lib/crypto/keySession.ts +216 -12
- package/lib/crypto/keyvaultApi.ts +174 -10
- package/lib/crypto/meetingKeys.ts +118 -0
- package/lib/crypto/messageSecurity.ts +284 -16
- package/lib/crypto/mime.ts +26 -0
- package/lib/crypto/openPgpKey.ts +118 -0
- package/lib/crypto/openpgp.ts +115 -0
- package/lib/crypto/pgpMessage.ts +336 -0
- package/lib/crypto/sharedMailboxKeys.ts +402 -0
- package/lib/crypto/smimeMessage.ts +11 -7
- package/lib/crypto/zeroAccessKey.ts +102 -0
- package/lib/mail/conversationsApi.ts +3 -0
- package/lib/mail/mailApi.ts +60 -3
- package/lib/mail/mailboxImportApi.ts +24 -0
- package/lib/search/zeroAccessIndexApi.ts +123 -0
- package/lib/tasks/tasksApi.ts +19 -5
- package/lib/util/clientPlatform.ts +50 -0
- package/lib/videoconf/protectedMeetings.ts +221 -0
- package/lib/videoconf/videoMeetingsApi.ts +15 -4
- package/lib/zeroAccess/sealedMoves.ts +79 -0
- package/lib/zeroAccess/sealedRecords.ts +153 -0
- package/package.json +14 -5
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
4
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
5
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6
|
+
import { useState } from "react";
|
|
7
|
+
import { getMailbox, updateMailbox } from "../../../../lib/mail/mailApi.js";
|
|
8
|
+
import { ApiRequestError } from "../../../../lib/util/api.js";
|
|
9
|
+
import { useApiClient } from "../../../../lib/util/apiClientContext.js";
|
|
10
|
+
import { notifyApiError } from "../../notifications/apiErrors.js";
|
|
11
|
+
import { useSettingsShell } from "./layout/SettingsShell.js";
|
|
12
|
+
/** What each data type's protection covers, what stays readable and why, and what it breaks on a phone or Outlook - the copy restapi
|
|
13
|
+
* `specs/search.md` §3 *What It Covers* requires settings to state plainly. */
|
|
14
|
+
export const ZERO_ACCESS_TYPE_INFO = {
|
|
15
|
+
mail: {
|
|
16
|
+
label: "Mail",
|
|
17
|
+
encrypted: "Message bodies, subjects, previews and attachments",
|
|
18
|
+
readable: "Sender, recipients, dates, size, flags and folder - to deliver, thread and search your mail",
|
|
19
|
+
onDevices: "can't open protected mail until you install your exported keys on it (Export keys below)",
|
|
20
|
+
},
|
|
21
|
+
calendar: {
|
|
22
|
+
label: "Calendar",
|
|
23
|
+
encrypted: "Event titles, locations and descriptions",
|
|
24
|
+
readable: "Times, repeats, time zone, attendees, responses and busy status - for free/busy, invitations and reminders",
|
|
25
|
+
onDevices: "shows protected events only as busy blocks named “Protected event”, and attendees aren't told when it changes a meeting (a phone) or sends one (Outlook) - send those from RapidMX",
|
|
26
|
+
},
|
|
27
|
+
contacts: {
|
|
28
|
+
label: "Contacts",
|
|
29
|
+
encrypted: "Names, company, job title, phone numbers, postal addresses, notes, categories and photos",
|
|
30
|
+
readable: "Email addresses - the server needs them to deliver mail and to find each contact's encryption keys",
|
|
31
|
+
onDevices: "shows protected contacts by their email address only",
|
|
32
|
+
},
|
|
33
|
+
tasks: {
|
|
34
|
+
label: "Tasks",
|
|
35
|
+
encrypted: "Task titles and notes",
|
|
36
|
+
readable: "Due dates, reminders, priority, completion and list",
|
|
37
|
+
onDevices: "shows protected tasks as “Protected task”",
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
/** Whether `type` is included: the owner's choice, else (undecided) mail on and the rest off - restapi's `zeroAccessTypeIncluded()`. */
|
|
41
|
+
export function typeIncluded(types, type) {
|
|
42
|
+
const chosen = types?.[type];
|
|
43
|
+
return typeof chosen === "boolean" ? chosen : type === "mail";
|
|
44
|
+
}
|
|
45
|
+
/** The names of the owner's ActiveSync devices that sync `type`, and Outlook when it syncs the mailbox. */
|
|
46
|
+
export function devicesSyncing(mailbox, type) {
|
|
47
|
+
const names = (mailbox.syncClients?.activesync?.devices ?? []).filter((device) => device.types.includes(type)).map((device) => device.model || "A phone");
|
|
48
|
+
return [...new Set(names), ...(mailbox.syncClients?.mapi ? ["Outlook"] : [])];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* "What to protect": one switch per data type zero-access encryption covers, each saying what it encrypts and - for a type one of the
|
|
52
|
+
* owner's ActiveSync devices or Outlook syncs - plainly what that device will no longer show (restapi `specs/search.md` §3 *What It
|
|
53
|
+
* Covers*). Then the table of what stays readable to the server and why.
|
|
54
|
+
*/
|
|
55
|
+
export default function ZeroAccessTypes({ mailbox, types, canManage, disabled = false }) {
|
|
56
|
+
const client = useApiClient();
|
|
57
|
+
const { patchMailbox } = useSettingsShell();
|
|
58
|
+
const [chosen, setChosen] = useState(mailbox.zeroAccessTypes ?? {});
|
|
59
|
+
const [saving, setSaving] = useState(false);
|
|
60
|
+
// What the server saved, once the mailbox has it (turning zero-access on decides the undecided types there): shown instead of what this
|
|
61
|
+
// was mounted with - set while rendering, so the stale switches are never drawn.
|
|
62
|
+
const storedTypes = JSON.stringify(mailbox.zeroAccessTypes ?? {});
|
|
63
|
+
const [shownTypes, setShownTypes] = useState(storedTypes);
|
|
64
|
+
if (shownTypes !== storedTypes) {
|
|
65
|
+
setShownTypes(storedTypes);
|
|
66
|
+
setChosen(JSON.parse(storedTypes));
|
|
67
|
+
}
|
|
68
|
+
async function save(type, on) {
|
|
69
|
+
const previous = chosen;
|
|
70
|
+
const next = { ...chosen, [type]: on };
|
|
71
|
+
setChosen(next);
|
|
72
|
+
setSaving(true);
|
|
73
|
+
try {
|
|
74
|
+
const write = (version) => updateMailbox({ uid: mailbox.uid, version, zeroAccessTypes: { [type]: on } }, client);
|
|
75
|
+
let saved;
|
|
76
|
+
try {
|
|
77
|
+
saved = await write(mailbox.version);
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
// The server records devices on the mailbox, moving its version on: read it again and retry once.
|
|
81
|
+
if (!(err instanceof ApiRequestError && err.status === 409)) {
|
|
82
|
+
throw err;
|
|
83
|
+
}
|
|
84
|
+
saved = await write((await getMailbox(mailbox.uid, {}, client)).version);
|
|
85
|
+
}
|
|
86
|
+
// Kept by the shell, so turning zero-access off and on again (which mounts this anew) shows what was saved: the server's merge of
|
|
87
|
+
// this choice into the stored one.
|
|
88
|
+
const merged = saved.zeroAccessTypes ?? next;
|
|
89
|
+
setChosen(merged);
|
|
90
|
+
patchMailbox?.(mailbox.uid, { zeroAccessTypes: merged, version: saved.version });
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
setChosen(previous);
|
|
94
|
+
notifyApiError(err, "Couldn't save what zero-access encryption protects");
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
setSaving(false);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return (_jsxs("div", { className: "mt-3", children: [_jsx("h3", { className: "text-sm font-semibold mb-1", children: "What to protect" }), _jsx("ul", { className: "flex flex-col gap-2", children: types.map((type) => {
|
|
101
|
+
const info = ZERO_ACCESS_TYPE_INFO[type];
|
|
102
|
+
const devices = devicesSyncing(mailbox, type);
|
|
103
|
+
const on = typeIncluded(chosen, type);
|
|
104
|
+
const keyInstalled = type === "mail" && !!mailbox.zeroAccessKeysExportedAt;
|
|
105
|
+
return (_jsx("li", { children: _jsxs("label", { className: "flex items-start gap-2 text-sm", children: [_jsx("input", { type: "checkbox", className: "mt-1", checked: on, disabled: !canManage || saving || disabled, onChange: (e) => void save(type, e.target.checked) }), _jsxs("span", { children: [_jsx("span", { className: "font-semibold block", children: info.label }), _jsxs("span", { className: "text-xs text-text-muted block", children: [info.encrypted, "."] }), devices.length > 0 && !keyInstalled && (_jsxs("span", { role: on ? "alert" : undefined, className: `text-xs block ${on ? "text-danger" : "text-text-muted"}`, children: [on ? "Breaks: " : "Left off because ", devices.join(", "), " ", devices.length === 1 ? "syncs" : "sync", " this - ", on ? "it " : "protected, it ", info.onDevices, "."] }))] })] }) }, type));
|
|
106
|
+
}) }), _jsxs("details", { className: "mt-3 text-sm", children: [_jsx("summary", { className: "cursor-pointer font-semibold", children: "What\u2019s encrypted, and what isn\u2019t" }), _jsxs("table", { className: "mt-2 w-full text-xs border-collapse", children: [_jsx("thead", { children: _jsxs("tr", { className: "text-left", children: [_jsx("th", { className: "py-1 pr-2", children: "Type" }), _jsx("th", { className: "py-1 pr-2", children: "Encrypted" }), _jsx("th", { className: "py-1", children: "Readable by the server, and why" })] }) }), _jsx("tbody", { children: types.map((type) => (_jsxs("tr", { className: "border-t border-border align-top", children: [_jsx("td", { className: "py-1 pr-2 font-semibold", children: ZERO_ACCESS_TYPE_INFO[type].label }), _jsx("td", { className: "py-1 pr-2", children: typeIncluded(chosen, type) ? ZERO_ACCESS_TYPE_INFO[type].encrypted : "Nothing - not protected" }), _jsx("td", { className: "py-1", children: ZERO_ACCESS_TYPE_INFO[type].readable })] }, type))) })] }), _jsx("p", { className: "mt-2 text-xs text-text-muted", children: "For every type, the server can also see folder and list names, how many items there are and when they change. Zero-access encryption protects what is stored: the server sees what you save while it saves it, then keeps only the encrypted copy." })] })] }));
|
|
107
|
+
}
|
|
@@ -30,6 +30,9 @@ export interface SettingsShellContextValue {
|
|
|
30
30
|
* every settings section so far (Automatic Replies) edits mailbox-scoped fields. */
|
|
31
31
|
mailboxUid?: string;
|
|
32
32
|
mailboxes: Mailbox[];
|
|
33
|
+
/** Merges what a section just saved of a mailbox (its new `version` included) into the shell's copy, so a section mounted again for
|
|
34
|
+
* it - after the mailbox switcher, say - starts from what was saved rather than from the list loaded once. */
|
|
35
|
+
patchMailbox?: (mailboxUid: string, patch: Partial<Mailbox>) => void;
|
|
33
36
|
}
|
|
34
37
|
/** Reads the mailbox Settings is currently scoped to, as resolved by the enclosing `SettingsShell`. */
|
|
35
38
|
export declare function useSettingsShell(): SettingsShellContextValue;
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
4
4
|
// SPDX-License-Identifier: MPL-2.0
|
|
5
5
|
///////////////////////////////////////////////////////////////////////////////
|
|
6
|
-
import { createContext, useContext, useEffect, useMemo, useState } from "react";
|
|
6
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
|
|
7
7
|
import { HiOutlineArrowUturnLeft, HiOutlineBars3, HiOutlineEnvelopeOpen, HiOutlineFunnel, HiOutlineLockClosed, HiOutlineNoSymbol, HiOutlinePaintBrush, HiOutlinePencilSquare, HiOutlineShieldCheck, HiOutlineTag, HiOutlineUserCircle, HiOutlineUsers, } from "react-icons/hi2";
|
|
8
8
|
import { ApiRequestError } from "../../../../../lib/util/api.js";
|
|
9
9
|
import Drawer from "../../../../../lib/components/overlays/Drawer.js";
|
|
@@ -89,7 +89,8 @@ export default function SettingsShell({ active, userUid, authServerUrl, imperson
|
|
|
89
89
|
}, [userUid]);
|
|
90
90
|
const mailboxUid = (requestedMailboxUid && mailboxes.some((mb) => mb.uid === requestedMailboxUid) ? requestedMailboxUid : undefined) ??
|
|
91
91
|
primaryMailboxUid(mailboxes, userUid);
|
|
92
|
-
const
|
|
92
|
+
const patchMailbox = useCallback((uid, patch) => setMailboxes((list) => list.map((mb) => (mb.uid === uid ? { ...mb, ...patch } : mb))), []);
|
|
93
|
+
const contextValue = useMemo(() => ({ mailboxUid, mailboxes, patchMailbox }), [mailboxUid, mailboxes, patchMailbox]);
|
|
93
94
|
// A full-screen takeover, not nested inside the rest of the app's chrome — same as ContactsShell/
|
|
94
95
|
// TasksShell, there's nothing else for a mailbox-less caller to configure here yet.
|
|
95
96
|
if (userUid && status === "ready" && !mailboxUid) {
|
|
@@ -32,4 +32,4 @@ export declare function useUnlockEpoch(): number;
|
|
|
32
32
|
* Decrypts one message to recover its subject and preview, when its mailbox's keys are unlocked and it isn't already known. Best effort: a
|
|
33
33
|
* message that can't be fetched or opened is simply left as it was. Never prompts.
|
|
34
34
|
*/
|
|
35
|
-
export declare function decryptForDisplay(uid: string, mailboxUid: string): Promise<void>;
|
|
35
|
+
export declare function decryptForDisplay(uid: string, mailboxUid: string, sealedSummary?: string): Promise<void>;
|
|
@@ -113,16 +113,24 @@ export function useUnlockEpoch() {
|
|
|
113
113
|
* Decrypts one message to recover its subject and preview, when its mailbox's keys are unlocked and it isn't already known. Best effort: a
|
|
114
114
|
* message that can't be fetched or opened is simply left as it was. Never prompts.
|
|
115
115
|
*/
|
|
116
|
-
export async function decryptForDisplay(uid, mailboxUid) {
|
|
116
|
+
export async function decryptForDisplay(uid, mailboxUid, sealedSummary) {
|
|
117
117
|
const unlocked = getUnlockedKeys(mailboxUid);
|
|
118
118
|
if (!unlocked || entries[uid] || inFlight.has(uid) || decryptFailedBefore(uid)) {
|
|
119
119
|
return;
|
|
120
120
|
}
|
|
121
121
|
inFlight.add(uid);
|
|
122
122
|
try {
|
|
123
|
+
const securityApi = await import("../../../lib/crypto/messageSecurity.js");
|
|
124
|
+
// A message sealed at rest has its subject and preview sealed on its row: opening that small summary saves downloading it.
|
|
125
|
+
const summary = sealedSummary ? await securityApi.openZeroAccessSummary(sealedSummary, unlocked.zeroAccessKeys) : undefined;
|
|
126
|
+
if (summary) {
|
|
127
|
+
if (getUnlockedKeys(mailboxUid) === unlocked) {
|
|
128
|
+
rememberDecrypted(uid, mailboxUid, { subject: summary.subject, preview: summary.preview.slice(0, PREVIEW_MAX_LENGTH) });
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
123
132
|
const rawMime = await getMessageRawContent(uid);
|
|
124
|
-
const
|
|
125
|
-
const security = await evaluateMessageSecurity(rawMime, unlocked);
|
|
133
|
+
const security = await securityApi.evaluateMessageSecurity(rawMime, unlocked);
|
|
126
134
|
// A lock while this ran: the result must not go back on screen.
|
|
127
135
|
if (getUnlockedKeys(mailboxUid) === unlocked) {
|
|
128
136
|
rememberDecrypted(uid, mailboxUid, { subject: security.subject, preview: security.html ? previewOf(security.html) : security.text?.slice(0, PREVIEW_MAX_LENGTH) });
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ApiClient } from "../../../../lib/util/api.js";
|
|
2
2
|
import { Attachment, ComposeRecipientInput, DraftThreading, Mailbox, Message } from "../../../../lib/mail/mailApi.js";
|
|
3
3
|
import { RecipientEncryptionStatus } from "../../../../lib/crypto/composeSecurity.js";
|
|
4
|
+
import { type UnlockedKeys } from "../../../../lib/crypto/keySession.js";
|
|
4
5
|
import { EncryptionPolicy } from "../../../../lib/crypto/keyvaultApi.js";
|
|
5
6
|
import { SendDecisionInput } from "./sendDecision.js";
|
|
6
7
|
import { OutboxCountTracker } from "./sendState.js";
|
|
@@ -39,20 +40,52 @@ export interface SendRequest {
|
|
|
39
40
|
* show the message as soon as it is sent (see `outgoingReplies.ts`). Absent for a new message, and for a draft opened again, which the draft's own
|
|
40
41
|
* `inReplyTo`/`references` stand in for. */
|
|
41
42
|
threading?: DraftThreading;
|
|
43
|
+
/** The copies of this message (`assemble()`: one per encryption format) already built but not handed over yet, after an attempt that
|
|
44
|
+
* failed part-way: a retry hands over just these - never one that already went, and without building them again. */
|
|
45
|
+
pendingCopies?: Message[];
|
|
46
|
+
/** Some copies already went (`pendingCopies` holds only the rest): every later failure says so, and never offers "Open draft", which
|
|
47
|
+
* would send those recipients a second copy - kept across any number of retries. */
|
|
48
|
+
partial?: boolean;
|
|
49
|
+
/** The uids of every copy of this message, when it is more than one (one per encryption format, each delivered only to its own
|
|
50
|
+
* recipients): the request is retained under each, and a failure the server reports later for one of them offers no "Open draft",
|
|
51
|
+
* which would send the other copy's recipients a second message. */
|
|
52
|
+
copyUids?: string[];
|
|
53
|
+
/** What has become of each of those copies, by uid: one map shared by every request made from this one (a retry's, the one retained), so
|
|
54
|
+
* how any copy went is known to all of them. */
|
|
55
|
+
copyFates?: Map<string, CopyFate>;
|
|
42
56
|
}
|
|
57
|
+
/** One copy of a message sent as several (`SendRequest.copyFates`): `unsent` - not handed over (or its hand-over failed), `pending` - handed
|
|
58
|
+
* over, how it went still to be reported, `sent`, or `failed` - the server reported it couldn't relay it. */
|
|
59
|
+
export type CopyFate = "unsent" | "pending" | "sent" | "failed";
|
|
43
60
|
/** How long a background send waits for the mailbox / encryption policy it did not have, before it goes ahead treating them as unknown. Nobody waits for it. */
|
|
44
61
|
export declare const CONTEXT_GRACE_MS = 4000;
|
|
45
62
|
export declare function registerOutboxCounter(tracker: (mailboxUid: string) => OutboxCountTracker, onQueued: (mailboxUid: string) => void): () => void;
|
|
46
|
-
/** The request a message was queued from, while this tab still holds it: what lets "Open draft" and "Retry" work on a failure the server
|
|
63
|
+
/** The request a message was queued from, while this tab still holds it: what lets "Open draft" and "Retry" work on a failure the server
|
|
64
|
+
* reports later. A message sent as several copies (`SendRequest.copyUids`) is held under each copy still to go, from just before it is handed
|
|
65
|
+
* over - how the first went can be reported while the next is still being handed over. */
|
|
47
66
|
export declare function retainedRequest(draftUid: string): SendRequest | undefined;
|
|
48
|
-
/** Drops the request a message was queued from, and the pictures remembered as attached to its draft: it has gone, so
|
|
49
|
-
|
|
67
|
+
/** Drops the request a message (or one of its copies) was queued from, and the pictures remembered as attached to its draft: it has gone, so
|
|
68
|
+
* neither is wanted again. Whether the message has now gone in full - the last of its copies to go. */
|
|
69
|
+
export declare function forgetRetainedRequest(draftUid: string): boolean;
|
|
70
|
+
/** Records that the server couldn't relay copy `uid` of the message `request` sent as several copies (nothing for a message sent as one). */
|
|
71
|
+
export declare function copyFailed(request: SendRequest | undefined, uid: string): void;
|
|
72
|
+
/** Copy `uid` of the message `request` sent as several copies is handed over again (a Retry): how it goes is to be reported once more, and
|
|
73
|
+
* finds the request held under it. Nothing for a message sent as one. */
|
|
74
|
+
export declare function copyRetried(request: SendRequest | undefined, uid: string): void;
|
|
75
|
+
/** Every copy of the message `request` sent as several, when none of them has gone nor may still go - each failed, or was never handed over:
|
|
76
|
+
* nobody has the message. `undefined` while one has gone or may, and for a message sent as one. */
|
|
77
|
+
export declare function unsentCopies(request: SendRequest | undefined): string[] | undefined;
|
|
78
|
+
/** Stops holding a message sent as several copies under any of them: it is being written again. */
|
|
79
|
+
export declare function forgetCopies(fates: Map<string, CopyFate>): void;
|
|
50
80
|
/** The sender's signing/encryption key state right now, read from the tab's key session (they can lock mid-send). */
|
|
51
81
|
export declare function readSendKeys(mailboxUid: string, signEnabled: boolean, mailbox: Mailbox | undefined): {
|
|
52
|
-
unlocked:
|
|
82
|
+
unlocked: UnlockedKeys | undefined;
|
|
53
83
|
canSign: boolean;
|
|
54
84
|
canEncryptSelf: boolean;
|
|
55
85
|
};
|
|
86
|
+
/** Whether the sender can make an OpenPGP copy (it is encrypted to them too, with their own OpenPGP key) - or can't be told yet, the
|
|
87
|
+
* mailbox being locked: `decideSend()` then asks for the unlock rather than settling for another format, or none. */
|
|
88
|
+
export declare function senderCanUseOpenPgp(unlocked: UnlockedKeys | undefined): boolean;
|
|
56
89
|
/** Whether this sender could encrypt at all: an encryption key unlocked here, enrolled on the mailbox, or offered earlier this session. */
|
|
57
90
|
export declare function senderHasEncryptionKey(request: Pick<SendRequest, "mailbox" | "offeredEncrypt">, canEncryptSelf: boolean): boolean;
|
|
58
91
|
/** The input `decideSend()` reads, from a request and what has been looked up. */
|