mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 00:12:39 +00:00
fix: send from a shared mailbox through its own identity for the from address #1450
This commit is contained in:
@@ -48,7 +48,7 @@ interface Recorded {
|
||||
identityId?: string;
|
||||
}
|
||||
|
||||
function mockServer(): Recorded {
|
||||
function mockServer(identities = IDENTITIES): Recorded {
|
||||
const rec: Recorded = {};
|
||||
vi.spyOn(globalThis, 'fetch').mockImplementation(async (_url, init) => {
|
||||
const { methodCalls } = JSON.parse((init as { body: string }).body) as { methodCalls: MethodCall[] };
|
||||
@@ -60,7 +60,7 @@ function mockServer(): Recorded {
|
||||
{ id: `${accountId}-sent`, name: 'Sent', role: 'sent' },
|
||||
] }, callId];
|
||||
}
|
||||
if (name === 'Identity/get') return [name, { list: IDENTITIES[accountId] ?? [] }, callId];
|
||||
if (name === 'Identity/get') return [name, { list: identities[accountId] ?? [] }, callId];
|
||||
if (name === 'Email/set') {
|
||||
if (args.create) {
|
||||
rec.emailSetAccount = accountId;
|
||||
@@ -154,6 +154,42 @@ describe('JMAPClient.sendEmail picks the account that owns the From identity (#1
|
||||
expectAccount(rec, 'grp');
|
||||
});
|
||||
|
||||
// #1450: the composer's identity ids are the member's own. In the group
|
||||
// account the same id can be another of the group's addresses, and the
|
||||
// server takes MAIL FROM from the identity the submission names.
|
||||
describe('identity ids of the member’s account (#1450)', () => {
|
||||
const COLLIDING: Record<string, Array<{ id: string; email: string }>> = {
|
||||
me: [
|
||||
{ id: 'by', email: 'info@radar.example' },
|
||||
{ id: 'bw', email: '*@lists.example' },
|
||||
{ id: 'c', email: 'carol@example.org' },
|
||||
],
|
||||
grp: [
|
||||
{ id: 'by', email: 'info@alias.example' },
|
||||
{ id: 'bw', email: 'info@alias.example' },
|
||||
{ id: 'b7', email: 'info@radar.example' },
|
||||
{ id: 'w', email: '*@lists.example' },
|
||||
],
|
||||
};
|
||||
|
||||
it.each([
|
||||
['the From address', 'info@radar.example', 'by', 'b7'],
|
||||
['a sub-addressed From', 'info+news@radar.example', 'by', 'b7'],
|
||||
['a From covered by a wildcard identity', 'announce@lists.example', 'bw', 'w'],
|
||||
])('submits through the group’s identity for %s', async (_case, fromEmail, memberId, groupId) => {
|
||||
const rec = mockServer(COLLIDING);
|
||||
await send(createClient({ me: 'carol@example.org', grp: 'team' }), fromEmail, memberId);
|
||||
expectAccount(rec, 'grp');
|
||||
expect(rec.identityId).toBe(groupId);
|
||||
});
|
||||
|
||||
it('keeps the id when it names the From address in the group account too', async () => {
|
||||
const rec = mockServer();
|
||||
await send(createClient({ me: 'carol@example.org', grp: 'team' }), 'team@example.org', 'b');
|
||||
expect(rec.identityId).toBe('b');
|
||||
});
|
||||
});
|
||||
|
||||
describe('drafts', () => {
|
||||
const draft = (client: JMAPClient, fromEmail: string, draftId?: string, options?: { accountId?: string; previousDraftAccountId?: string }) =>
|
||||
client.createDraft(['rcpt@example.net'], 'Subject', 'body', undefined, undefined, 'b', fromEmail, draftId,
|
||||
|
||||
+16
-1
@@ -4865,13 +4865,28 @@ export class JMAPClient implements IJMAPClient {
|
||||
? identities.find((id) => id.email.toLowerCase() === requestedMailFrom.toLowerCase())
|
||||
: undefined;
|
||||
if (!matchingIdentity && identityId) {
|
||||
matchingIdentity = identities.find((id) => id.id === identityId);
|
||||
// Identity ids are per account, and the composer's come from the
|
||||
// user's own account. In a shared account the same id can be
|
||||
// another of its addresses, and the server derives MAIL FROM from
|
||||
// that identity (#1450): there the id only counts when it names
|
||||
// the From address.
|
||||
const byId = identities.find((id) => id.id === identityId);
|
||||
if (byId && (targetAccountId === this.accountId || !fromEmail || emailMatchesLogin(byId.email, fromEmail))) {
|
||||
matchingIdentity = byId;
|
||||
}
|
||||
}
|
||||
if (!matchingIdentity) {
|
||||
// Addresses compared as the server does: regardless of case.
|
||||
const target = fromEmail || this.username;
|
||||
matchingIdentity = identities.find((id) => emailMatchesLogin(id.email, target));
|
||||
}
|
||||
if (!matchingIdentity && fromEmail) {
|
||||
// A sub-addressed From or one covered by a wildcard identity, the
|
||||
// other ways resolveSendAccountId picks an account.
|
||||
const from = parseRecipientString(fromEmail).email.toLowerCase();
|
||||
matchingIdentity = identities.find((id) => identityEmailIsSubAddressOf(id.email, from))
|
||||
?? identities.find((id) => identityEmailIsWildcardFor(id.email, from));
|
||||
}
|
||||
finalIdentityId = matchingIdentity?.id || identities[0].id;
|
||||
identityReplyTo = matchingIdentity?.replyTo || identities[0].replyTo;
|
||||
identityEmail = (matchingIdentity || identities[0]).email;
|
||||
|
||||
Reference in New Issue
Block a user