fix: don't count senders whose images the user allowed as contacts for the external recipient confirm

This commit is contained in:
Linus Rath
2026-10-28 05:09:03 +01:00
parent 7bbfe61783
commit 836dcba66e
3 changed files with 34 additions and 4 deletions
@@ -118,6 +118,8 @@ vi.mock('@/stores/settings-store', () => {
vi.mock('@/stores/contact-store', () => {
const state = {
contacts: [],
addressBooks: [],
trustedSendersBookId: null,
getAutocomplete: async () => [],
addToTrustedSendersBook: async () => {},
};
+20 -1
View File
@@ -126,7 +126,7 @@ describe('the active account', () => {
beforeEach(() => {
setExternalMail({});
useAuthStore.setState({ identities: [] });
useContactStore.setState({ contacts: [] });
useContactStore.setState({ contacts: [], addressBooks: [], trustedSendersBookId: null });
});
it('reads the switches from the effective policy', () => {
@@ -152,4 +152,23 @@ describe('the active account', () => {
});
expect([...contactAddresses()].sort()).toEqual(['dave@org-two.test', 'second@x.test']);
});
it('leaves out cards that are only in the Trusted Senders book', () => {
useContactStore.setState({
addressBooks: [
{ id: 'book-main', name: 'Contacts' },
{ id: 'acc1::book-trusted', name: 'Trusted Senders' },
] as never,
trustedSendersBookId: 'book-trusted-raw',
contacts: [
// Only "always load images" for this sender: not a contact.
{ id: 'c1', addressBookIds: { 'acc1::book-trusted': true }, emails: { e: { address: 'newsletter@shop.example' } } },
{ id: 'c2', addressBookIds: { 'book-trusted-raw': true }, emails: { e: { address: 'promo@shop.example' } } },
// In a real book too: a contact.
{ id: 'c3', addressBookIds: { 'book-main': true, 'acc1::book-trusted': true }, emails: { e: { address: 'dave@org-two.test' } } },
{ id: 'c4', addressBookIds: { 'book-main': true }, emails: { e: { address: 'carol@partner.example' } } },
] as unknown as ContactCard[],
});
expect([...contactAddresses()].sort()).toEqual(['carol@partner.example', 'dave@org-two.test']);
});
});
+12 -3
View File
@@ -18,7 +18,7 @@ import { DEFAULT_ORGANISATION_POLICY, normalizeDomainList, type OrganisationPoli
import { toAsciiDomain, toAsciiEmail } from '@/lib/idn';
import { usePolicyStore } from '@/stores/policy-store';
import { useAuthStore } from '@/stores/auth-store';
import { useContactStore } from '@/stores/contact-store';
import { TRUSTED_SENDERS_BOOK_NAME, useContactStore } from '@/stores/contact-store';
export type ExternalMailPolicy = OrganisationPolicy['externalMail'];
@@ -125,10 +125,19 @@ export function currentInternalDomains(policy: ExternalMailPolicy = currentExter
return internalDomains(identities.map((identity) => identity.email), policy.domains);
}
/** The addresses of every loaded contact (all address books), as addressKey makes them. */
/**
* The addresses of the loaded contacts, as addressKey makes them. A card
* that sits only in the "Trusted Senders" book is left out: that book is
* the list of senders whose images load, not people the user writes to.
*/
export function contactAddresses(): Set<string> {
const { contacts, addressBooks, trustedSendersBookId } = useContactStore.getState();
const trustedBooks = new Set(addressBooks.filter((book) => book.name === TRUSTED_SENDERS_BOOK_NAME).map((book) => book.id));
if (trustedSendersBookId) trustedBooks.add(trustedSendersBookId);
const out = new Set<string>();
for (const contact of useContactStore.getState().contacts) {
for (const contact of contacts) {
const books = Object.keys(contact.addressBookIds ?? {}).filter((id) => contact.addressBookIds?.[id]);
if (books.length > 0 && books.every((id) => trustedBooks.has(id))) continue;
for (const entry of Object.values(contact.emails ?? {})) {
const address = entry?.address?.trim();
if (address) out.add(addressKey(address));