mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 00:12:39 +00:00
fix: send a sub-address of your own address from your account, not a shared catch-all
This commit is contained in:
@@ -19,6 +19,7 @@ const IDENTITIES: Record<string, Array<{ id: string; email: string }>> = {
|
||||
grp: [{ id: 'b', email: 'team@example.org' }, { id: 'x', email: 'info@example.org' }],
|
||||
alice: [{ id: 'a', email: 'alice@example.org' }],
|
||||
wild: [{ id: 'w', email: '*@lists.example.org' }],
|
||||
catchall: [{ id: 'k', email: '*@example.org' }],
|
||||
};
|
||||
|
||||
function createClient(accounts: Record<string, string>): JMAPClient {
|
||||
@@ -141,6 +142,12 @@ describe('JMAPClient.sendEmail picks the account that owns the From identity (#1
|
||||
expectAccount(sub, 'me');
|
||||
});
|
||||
|
||||
it('keeps a sub-address of the member’s own address over a shared catch-all identity', async () => {
|
||||
const rec = mockServer();
|
||||
await send(createClient({ me: 'carol-renamed', catchall: 'catchall' }), 'carol+x@example.org', 'c');
|
||||
expectAccount(rec, 'me');
|
||||
});
|
||||
|
||||
it('still prefers an account named exactly like the From address', async () => {
|
||||
const rec = mockServer();
|
||||
await send(createClient({ me: 'carol@example.org', grp: 'team@example.org' }), 'team@example.org');
|
||||
|
||||
+14
-10
@@ -1027,20 +1027,22 @@ function identityEmailMatchesExactly(identityEmail: string | undefined, from: st
|
||||
return !!identityEmail && identityEmail.toLowerCase() === from;
|
||||
}
|
||||
|
||||
/**
|
||||
* An identity address that covers `from` without being it: a sub-address of
|
||||
* it (team+tag@ for team@, with the common "+" / "-" delimiters) or a
|
||||
* wildcard identity for its domain (RFC 8621 §6, "*@example.com").
|
||||
*/
|
||||
function identityEmailCovers(identityEmail: string | undefined, from: string): boolean {
|
||||
/** An identity address `from` is a sub-address of (team+tag@ for team@, with the common "+" / "-" delimiters). */
|
||||
function identityEmailIsSubAddressOf(identityEmail: string | undefined, from: string): boolean {
|
||||
if (!identityEmail) return false;
|
||||
const [idLocal, idDomain] = identityEmail.toLowerCase().split('@');
|
||||
const [fromLocal, fromDomain] = from.split('@');
|
||||
if (!idDomain || !fromDomain || idDomain !== fromDomain) return false;
|
||||
if (idLocal === '*') return true;
|
||||
if (!idDomain || !fromDomain || idDomain !== fromDomain || idLocal === '*') return false;
|
||||
return fromLocal.startsWith(`${idLocal}+`) || fromLocal.startsWith(`${idLocal}-`);
|
||||
}
|
||||
|
||||
/** A wildcard identity for the domain of `from` (RFC 8621 §6, "*@example.com"). */
|
||||
function identityEmailIsWildcardFor(identityEmail: string | undefined, from: string): boolean {
|
||||
if (!identityEmail) return false;
|
||||
const [idLocal, idDomain] = identityEmail.toLowerCase().split('@');
|
||||
return idLocal === '*' && !!idDomain && idDomain === from.split('@')[1];
|
||||
}
|
||||
|
||||
export class JMAPClient implements IJMAPClient {
|
||||
private static readonly RATE_LIMIT_TOAST_THROTTLE_MS = 10_000;
|
||||
/**
|
||||
@@ -5413,9 +5415,11 @@ export class JMAPClient implements IJMAPClient {
|
||||
* through the member's account and its Sent copy landed there.
|
||||
*
|
||||
* Order: an account named exactly `fromEmail`; then an account that owns an
|
||||
* identity for the address - exact before sub-address/wildcard, and a
|
||||
* identity for the address - exact, then sub-address, then wildcard, and a
|
||||
* delegated (group/shared) account before the primary one, since the
|
||||
* primary may merely mirror the group's identity; else the primary account.
|
||||
* A sub-address of the member's own address stays theirs even when a
|
||||
* shared catch-all account has a wildcard identity for the domain.
|
||||
*/
|
||||
async resolveSendAccountId(fromEmail?: string): Promise<string> {
|
||||
if (!fromEmail) return this.accountId;
|
||||
@@ -5456,7 +5460,7 @@ export class JMAPClient implements IJMAPClient {
|
||||
}
|
||||
}
|
||||
|
||||
for (const matches of [identityEmailMatchesExactly, identityEmailCovers]) {
|
||||
for (const matches of [identityEmailMatchesExactly, identityEmailIsSubAddressOf, identityEmailIsWildcardFor]) {
|
||||
const hit = identitiesByAccount.find(([, list]) => list.some(identity => matches(identity.email, from)));
|
||||
if (hit) return hit[0];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user