fix(email): make team org invites more transactional (#732)
* fix(email): make team org invites more transactional Use org-branded sender and subject instead of the default "Best from ClassroomIO" marketing identity. Include the inviter name in the body, remove promotional emojis, and thread optional subject overrides through the email job pipeline. Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * fix(email): escape invite fields and sanitize email headers Address Greptile review: escape inviter/org/role values in the team invite template, sanitize display names in buildEmailFromName, and strip control characters from dynamic subjects. Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * fix(email): use RFC 5322 backslash escaping in display names Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * test(email): add vitest coverage for email header helpers Cover escapeHtml, sanitizeEmailDisplayName, sanitizeEmailSubject, and buildEmailFromName. Collapse repeated whitespace when stripping control characters from header values. Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * fix(email): strip all ASCII control chars from email headers Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
This commit is contained in:
co-authored by
Rotimi Best
Cursor Agent
parent
1a9c240501
commit
c03c08fc84
@@ -1,6 +1,36 @@
|
||||
import { FromData } from '../types';
|
||||
import { EMAIL_FROM } from '../constants';
|
||||
|
||||
const EMAIL_HEADER_CONTROL_CHARS = /[\x00-\x1F\x7F]/g;
|
||||
|
||||
function stripEmailHeaderControlChars(value: string): string {
|
||||
return value.replace(EMAIL_HEADER_CONTROL_CHARS, ' ').replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
|
||||
/** Strip control chars and backslash-escape quotes for RFC 5322 quoted display names. */
|
||||
export function sanitizeEmailDisplayName(name: string): string {
|
||||
const normalized = stripEmailHeaderControlChars(name);
|
||||
if (!normalized) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return normalized.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
||||
}
|
||||
|
||||
/** Strip control chars that could break or inject email headers. */
|
||||
export function sanitizeEmailSubject(subject: string): string {
|
||||
return stripEmailHeaderControlChars(subject);
|
||||
}
|
||||
|
||||
export function escapeHtml(value: string): string {
|
||||
return value
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
// format: "ClassroomIO Developers (via ClassroomIO.com)" <notify@mail.classroomio.com>
|
||||
export function extractNameAndEmail(str: string): FromData | undefined {
|
||||
// Use regular expressions to match the name and email
|
||||
@@ -28,5 +58,10 @@ export function buildEmailFromName(name?: string): string {
|
||||
return EMAIL_FROM;
|
||||
}
|
||||
|
||||
return `"${name}" <${fromData.email}>`;
|
||||
const displayName = sanitizeEmailDisplayName(name);
|
||||
if (!displayName) {
|
||||
return EMAIL_FROM;
|
||||
}
|
||||
|
||||
return `"${displayName}" <${fromData.email}>`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user