Compare commits

...
Author SHA1 Message Date
neo773 13a5daba10 lint 2026-05-07 14:45:28 +05:30
neo773 285b66b63c changes 2026-05-07 14:44:53 +05:30
neo773 6c6760495b fix(messaging): preserve same-domain imports for institutional accounts
Domain equality is unreliable at universities and other institutional
domains where unrelated members share an address space. Gate
filterOutInternals and the contact-self/workspace-member filter on a
Hipolabs-backed institutional domain list so legitimate collaborators
are not silently dropped during import.
2026-05-07 13:02:28 +05:30
7 changed files with 10749 additions and 4 deletions
@@ -0,0 +1,85 @@
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
import { type Contact } from 'src/modules/contact-creation-manager/types/contact.type';
import { filterOutContactsThatBelongToSelfOrWorkspaceMembers } from 'src/modules/contact-creation-manager/utils/filter-out-contacts-that-belong-to-self-or-workspace-members.util';
import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
const account = (
handle: string,
handleAliases: string[] = [],
): ConnectedAccountEntity =>
({ handle, handleAliases }) as ConnectedAccountEntity;
const member = (userEmail: string): WorkspaceMemberWorkspaceEntity =>
({ userEmail }) as unknown as WorkspaceMemberWorkspaceEntity;
const contact = (handle: string): Contact => ({ handle, displayName: handle });
describe('filterOutContactsThatBelongToSelfOrWorkspaceMembers', () => {
it('drops contacts sharing a company domain with the connected account', () => {
const result = filterOutContactsThatBelongToSelfOrWorkspaceMembers(
[contact('colleague@acme.com'), contact('lead@prospect.com')],
account('me@acme.com'),
[],
);
expect(result.map((c) => c.handle)).toEqual(['lead@prospect.com']);
});
it('keeps same-domain contacts when the connected account is on a consumer email provider', () => {
// gmail.com is shared by millions of unrelated people, so domain
// equality cannot mean "teammate".
const result = filterOutContactsThatBelongToSelfOrWorkspaceMembers(
[contact('friend@gmail.com')],
account('me@gmail.com'),
[],
);
expect(result).toHaveLength(1);
});
it('keeps same-domain contacts when the connected account is on a university domain', () => {
// Universities host thousands of unrelated members; treat them like
// consumer providers and let collaborators through.
const result = filterOutContactsThatBelongToSelfOrWorkspaceMembers(
[contact('professor@mit.edu'), contact('lab-member@mit.edu')],
account('researcher@mit.edu'),
[],
);
expect(result).toHaveLength(2);
});
it('drops workspace members regardless of domain', () => {
const result = filterOutContactsThatBelongToSelfOrWorkspaceMembers(
[contact('teammate@partner.com'), contact('outsider@partner.com')],
account('me@acme.com'),
[member('teammate@partner.com')],
);
expect(result.map((c) => c.handle)).toEqual(['outsider@partner.com']);
});
it('drops the connected account handle and any of its aliases', () => {
const result = filterOutContactsThatBelongToSelfOrWorkspaceMembers(
[
contact('me@acme.com'),
contact('me+work@acme.com'),
contact('real-contact@partner.com'),
],
account('me@acme.com', ['me+work@acme.com']),
[],
);
expect(result.map((c) => c.handle)).toEqual(['real-contact@partner.com']);
});
it('matches handles case-insensitively', () => {
const result = filterOutContactsThatBelongToSelfOrWorkspaceMembers(
[contact('Teammate@Partner.com')],
account('me@acme.com'),
[member('teammate@partner.com')],
);
expect(result).toEqual([]);
});
});
@@ -4,6 +4,7 @@ import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connect
import { type Contact } from 'src/modules/contact-creation-manager/types/contact.type';
import { getDomainNameFromHandle } from 'src/modules/contact-creation-manager/utils/get-domain-name-from-handle.util';
import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
import { isInstitutionalDomain } from 'src/utils/is-institutional-email';
import { isWorkDomain } from 'src/utils/is-work-email';
export function filterOutContactsThatBelongToSelfOrWorkspaceMembers(
@@ -41,7 +42,8 @@ export function filterOutContactsThatBelongToSelfOrWorkspaceMembers(
return contacts.filter(
(contact) =>
(isDifferentDomain(contact, selfDomainName) ||
!isWorkDomain(selfDomainName)) &&
!isWorkDomain(selfDomainName) ||
isInstitutionalDomain(selfDomainName)) &&
// @ts-expect-error legacy noImplicitAny
!workspaceMembersMap[contact.handle.toLowerCase()] &&
!allHandles.includes(contact.handle.toLowerCase()),
@@ -37,6 +37,43 @@ describe('filterEmails', () => {
expect(filteredMessages).toEqual([]);
});
it('Should keep same-domain emails when primary handle is on a university domain', () => {
// Universities host thousands of unrelated members across departments
// and labs, so domain equality is not a reliable proxy for "internal
// teammate". Treat them like consumer providers and skip the same-domain
// filter — collaborators sharing the institution domain should be
// imported, not silently dropped.
const primaryHandle = 'researcher@mit.edu';
const messages: MessageWithParticipants[] = [
{
externalId: 'institution-internal',
subject: 'Research collaboration',
receivedAt: new Date('2025-01-09T09:54:37.000Z'),
text: 'Following up on the paper.',
headerMessageId: '<msg@mit.edu>',
messageThreadExternalId: 'thread-1',
direction: MessageDirection.OUTGOING,
participants: [
{
role: MessageParticipantRole.FROM,
handle: 'researcher@mit.edu',
displayName: 'Researcher',
},
{
role: MessageParticipantRole.TO,
handle: 'professor@mit.edu',
displayName: 'Professor',
},
],
attachments: [],
},
];
const result = filterEmails(primaryHandle, [], messages, []);
expect(result).toEqual(messages);
});
it('Should filter messages with participant from the blocklist', () => {
const primaryHandle = 'guillim@acme.com';
const messages = messagingGetMessagesServiceGetMessages.filter(
@@ -7,6 +7,7 @@ import { filterOutIcsAttachments } from 'src/modules/messaging/message-import-ma
import { filterOutInternals } from 'src/modules/messaging/message-import-manager/utils/filter-out-internals.util';
import { isGroupEmail } from 'src/modules/messaging/message-import-manager/utils/is-group-email';
import { isMessageSenderMatchingHandles } from 'src/modules/messaging/message-import-manager/utils/is-message-sender-matching-handles.util';
import { isInstitutionalEmail } from 'src/utils/is-institutional-email';
import { isWorkEmail } from 'src/utils/is-work-email';
export const filterEmails = (
@@ -24,9 +25,12 @@ export const filterEmails = (
blocklist,
);
const messagesWithoutInternals = isWorkEmail(primaryHandle)
? filterOutInternals(primaryHandle, messagesWithoutBlocklisted)
: messagesWithoutBlocklisted;
const isSharedDomain =
!isWorkEmail(primaryHandle) || isInstitutionalEmail(primaryHandle);
const messagesWithoutInternals = isSharedDomain
? messagesWithoutBlocklisted
: filterOutInternals(primaryHandle, messagesWithoutBlocklisted);
if (!excludeGroupEmails) {
return messagesWithoutInternals;
@@ -0,0 +1,48 @@
import {
isInstitutionalDomain,
isInstitutionalEmail,
} from 'src/utils/is-institutional-email';
describe('isInstitutionalDomain', () => {
it('returns true for universities in the Hipolabs dataset', () => {
expect(isInstitutionalDomain('mit.edu')).toBe(true);
expect(isInstitutionalDomain('cam.ac.uk')).toBe(true);
expect(isInstitutionalDomain('ethz.ch')).toBe(true);
});
it('returns true for one-level subdomains under a known university', () => {
expect(isInstitutionalDomain('stud.tu-darmstadt.de')).toBe(true);
expect(isInstitutionalDomain('cs.stanford.edu')).toBe(true);
expect(isInstitutionalDomain('alumni.mit.edu')).toBe(true);
});
it('returns false for company-owned domains', () => {
expect(isInstitutionalDomain('acme.com')).toBe(false);
expect(isInstitutionalDomain('example.io')).toBe(false);
});
it('does not match bare TLDs via parent walk', () => {
expect(isInstitutionalDomain('de')).toBe(false);
expect(isInstitutionalDomain('edu')).toBe(false);
expect(isInstitutionalDomain('random.de')).toBe(false);
});
it('does not treat consumer providers as institutional', () => {
expect(isInstitutionalDomain('gmail.com')).toBe(false);
expect(isInstitutionalDomain('outlook.com')).toBe(false);
});
});
describe('isInstitutionalEmail', () => {
it('extracts the domain and applies the institutional rules', () => {
expect(isInstitutionalEmail('researcher@mit.edu')).toBe(true);
expect(isInstitutionalEmail('student@stud.tu-darmstadt.de')).toBe(true);
expect(isInstitutionalEmail('user@acme.com')).toBe(false);
});
it('returns false rather than throwing on malformed input', () => {
expect(isInstitutionalEmail('')).toBe(false);
expect(isInstitutionalEmail('not-an-email')).toBe(false);
expect(isInstitutionalEmail('missing-domain@')).toBe(false);
});
});
@@ -0,0 +1,19 @@
import { getDomainNameByEmail } from 'src/utils/get-domain-name-by-email';
import { universityDomainsSet } from 'src/utils/university-domains';
export const isInstitutionalDomain = (domain: string) => {
if (universityDomainsSet.has(domain)) return true;
const dot = domain.indexOf('.');
const parent = dot === -1 ? '' : domain.slice(dot + 1);
return parent.includes('.') && universityDomainsSet.has(parent);
};
export const isInstitutionalEmail = (email: string) => {
try {
return isInstitutionalDomain(getDomainNameByEmail(email));
} catch {
return false;
}
};
File diff suppressed because it is too large Load Diff