mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 16:27:46 +00:00
197 lines
7.3 KiB
TypeScript
197 lines
7.3 KiB
TypeScript
import { render } from "@react-email/components";
|
|
import { createTransport } from "nodemailer";
|
|
import SMTPTransport from "nodemailer/lib/smtp-transport";
|
|
import React from "react";
|
|
|
|
import { getConfig } from "@app/lib/config/env";
|
|
import { logger } from "@app/lib/logger";
|
|
|
|
import {
|
|
AccessApprovalRequestTemplate,
|
|
AccessApprovalRequestUpdatedTemplate,
|
|
AccountDeletionConfirmationTemplate,
|
|
EmailMfaTemplate,
|
|
EmailVerificationTemplate,
|
|
ExternalImportFailedTemplate,
|
|
ExternalImportStartedTemplate,
|
|
ExternalImportSucceededTemplate,
|
|
HealthAlertTemplate,
|
|
IntegrationSyncFailedTemplate,
|
|
NewDeviceLoginTemplate,
|
|
OAuthPasswordResetTemplate,
|
|
OrgAdminBreakglassAccessTemplate,
|
|
OrgAdminProjectGrantAccessTemplate,
|
|
OrganizationAssignmentTemplate,
|
|
OrganizationInvitationTemplate,
|
|
PasswordResetTemplate,
|
|
PasswordSetupTemplate,
|
|
PkiExpirationAlertTemplate,
|
|
ProjectAccessRequestTemplate,
|
|
ProjectInvitationTemplate,
|
|
ScimUserProvisionedTemplate,
|
|
SecretApprovalRequestBypassedTemplate,
|
|
SecretApprovalRequestNeedsReviewTemplate,
|
|
SecretLeakIncidentTemplate,
|
|
SecretReminderTemplate,
|
|
SecretRequestCompletedTemplate,
|
|
SecretRotationFailedTemplate,
|
|
SecretScanningScanFailedTemplate,
|
|
SecretScanningSecretsDetectedTemplate,
|
|
SecretSyncFailedTemplate,
|
|
ServiceTokenExpiryNoticeTemplate,
|
|
SignupEmailVerificationTemplate,
|
|
UnlockAccountTemplate
|
|
} from "./emails";
|
|
|
|
export type TSmtpConfig = SMTPTransport.Options;
|
|
export type TSmtpSendMail = {
|
|
template: SmtpTemplates;
|
|
subjectLine: string;
|
|
recipients: string[];
|
|
substitutions: object;
|
|
};
|
|
export type TSmtpService = ReturnType<typeof smtpServiceFactory>;
|
|
|
|
export enum SmtpTemplates {
|
|
SignupEmailVerification = "signupEmailVerification",
|
|
EmailVerification = "emailVerification",
|
|
SecretReminder = "secretReminder",
|
|
EmailMfa = "emailMfa",
|
|
UnlockAccount = "unlockAccount",
|
|
AccessApprovalRequest = "accessApprovalRequest",
|
|
AccessApprovalRequestUpdated = "accessApprovalRequestUpdated",
|
|
AccessSecretRequestBypassed = "accessSecretRequestBypassed",
|
|
SecretApprovalRequestNeedsReview = "secretApprovalRequestNeedsReview",
|
|
// HistoricalSecretList = "historicalSecretLeakIncident", not used anymore?
|
|
NewDeviceJoin = "newDevice",
|
|
OrgInvite = "organizationInvitation",
|
|
OrgAssignment = "organizationAssignment",
|
|
OAuthPasswordReset = "oAuthPasswordReset",
|
|
ResetPassword = "passwordReset",
|
|
SetupPassword = "passwordSetup",
|
|
SecretLeakIncident = "secretLeakIncident",
|
|
WorkspaceInvite = "workspaceInvitation",
|
|
ScimUserProvisioned = "scimUserProvisioned",
|
|
PkiExpirationAlert = "pkiExpirationAlert",
|
|
IntegrationSyncFailed = "integrationSyncFailed",
|
|
SecretSyncFailed = "secretSyncFailed",
|
|
ExternalImportSuccessful = "externalImportSuccessful",
|
|
ExternalImportFailed = "externalImportFailed",
|
|
ExternalImportStarted = "externalImportStarted",
|
|
SecretRequestCompleted = "secretRequestCompleted",
|
|
SecretRotationFailed = "secretRotationFailed",
|
|
ProjectAccessRequest = "projectAccess",
|
|
OrgAdminProjectDirectAccess = "orgAdminProjectGrantAccess",
|
|
OrgAdminBreakglassAccess = "orgAdminBreakglassAccess",
|
|
ServiceTokenExpired = "serviceTokenExpired",
|
|
SecretScanningV2ScanFailed = "secretScanningV2ScanFailed",
|
|
SecretScanningV2SecretsDetected = "secretScanningV2SecretsDetected",
|
|
AccountDeletionConfirmation = "accountDeletionConfirmation",
|
|
HealthAlert = "healthAlert"
|
|
}
|
|
|
|
export enum SmtpHost {
|
|
Sendgrid = "smtp.sendgrid.net",
|
|
Mailgun = "smtp.mailgun.org",
|
|
SocketLabs = "smtp.socketlabs.com",
|
|
Zohomail = "smtp.zoho.com",
|
|
Gmail = "smtp.gmail.com",
|
|
Office365 = "smtp.office365.com"
|
|
}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const EmailTemplateMap: Record<SmtpTemplates, React.FC<any>> = {
|
|
[SmtpTemplates.OrgInvite]: OrganizationInvitationTemplate,
|
|
[SmtpTemplates.OrgAssignment]: OrganizationAssignmentTemplate,
|
|
[SmtpTemplates.NewDeviceJoin]: NewDeviceLoginTemplate,
|
|
[SmtpTemplates.SignupEmailVerification]: SignupEmailVerificationTemplate,
|
|
[SmtpTemplates.EmailMfa]: EmailMfaTemplate,
|
|
[SmtpTemplates.AccessApprovalRequest]: AccessApprovalRequestTemplate,
|
|
[SmtpTemplates.AccessApprovalRequestUpdated]: AccessApprovalRequestUpdatedTemplate,
|
|
[SmtpTemplates.EmailVerification]: EmailVerificationTemplate,
|
|
[SmtpTemplates.ExternalImportFailed]: ExternalImportFailedTemplate,
|
|
[SmtpTemplates.ExternalImportStarted]: ExternalImportStartedTemplate,
|
|
[SmtpTemplates.ExternalImportSuccessful]: ExternalImportSucceededTemplate,
|
|
[SmtpTemplates.AccessSecretRequestBypassed]: SecretApprovalRequestBypassedTemplate,
|
|
[SmtpTemplates.IntegrationSyncFailed]: IntegrationSyncFailedTemplate,
|
|
[SmtpTemplates.OrgAdminBreakglassAccess]: OrgAdminBreakglassAccessTemplate,
|
|
[SmtpTemplates.SecretLeakIncident]: SecretLeakIncidentTemplate,
|
|
[SmtpTemplates.WorkspaceInvite]: ProjectInvitationTemplate,
|
|
[SmtpTemplates.ScimUserProvisioned]: ScimUserProvisionedTemplate,
|
|
[SmtpTemplates.SecretRequestCompleted]: SecretRequestCompletedTemplate,
|
|
[SmtpTemplates.UnlockAccount]: UnlockAccountTemplate,
|
|
[SmtpTemplates.ServiceTokenExpired]: ServiceTokenExpiryNoticeTemplate,
|
|
[SmtpTemplates.SecretReminder]: SecretReminderTemplate,
|
|
[SmtpTemplates.SecretRotationFailed]: SecretRotationFailedTemplate,
|
|
[SmtpTemplates.SecretSyncFailed]: SecretSyncFailedTemplate,
|
|
[SmtpTemplates.OrgAdminProjectDirectAccess]: OrgAdminProjectGrantAccessTemplate,
|
|
[SmtpTemplates.ProjectAccessRequest]: ProjectAccessRequestTemplate,
|
|
[SmtpTemplates.SecretApprovalRequestNeedsReview]: SecretApprovalRequestNeedsReviewTemplate,
|
|
[SmtpTemplates.OAuthPasswordReset]: OAuthPasswordResetTemplate,
|
|
[SmtpTemplates.ResetPassword]: PasswordResetTemplate,
|
|
[SmtpTemplates.SetupPassword]: PasswordSetupTemplate,
|
|
[SmtpTemplates.PkiExpirationAlert]: PkiExpirationAlertTemplate,
|
|
[SmtpTemplates.SecretScanningV2ScanFailed]: SecretScanningScanFailedTemplate,
|
|
[SmtpTemplates.SecretScanningV2SecretsDetected]: SecretScanningSecretsDetectedTemplate,
|
|
[SmtpTemplates.AccountDeletionConfirmation]: AccountDeletionConfirmationTemplate,
|
|
[SmtpTemplates.HealthAlert]: HealthAlertTemplate
|
|
};
|
|
|
|
export const smtpServiceFactory = (cfg: TSmtpConfig) => {
|
|
const smtp = createTransport(cfg);
|
|
const isSmtpOn = Boolean(cfg.host);
|
|
|
|
const sendMail = async ({ substitutions, recipients, template, subjectLine }: TSmtpSendMail) => {
|
|
const appCfg = getConfig();
|
|
|
|
const EmailTemplate = EmailTemplateMap[template];
|
|
|
|
if (!EmailTemplate) {
|
|
throw new Error(`Email template ${template} not found`);
|
|
}
|
|
|
|
const htmlToSend = await render(
|
|
React.createElement(EmailTemplate, {
|
|
...substitutions,
|
|
isCloud: appCfg.isCloud,
|
|
siteUrl: appCfg.SITE_URL
|
|
})
|
|
);
|
|
|
|
if (isSmtpOn) {
|
|
await smtp.sendMail({
|
|
from: cfg.from,
|
|
to: recipients.join(", "),
|
|
subject: subjectLine,
|
|
html: htmlToSend
|
|
});
|
|
} else {
|
|
logger.info("SMTP is not configured. Outputting it in terminal");
|
|
logger.info({
|
|
from: cfg.from,
|
|
to: recipients.join(", "),
|
|
subject: subjectLine,
|
|
html: htmlToSend
|
|
});
|
|
}
|
|
};
|
|
|
|
const verify = async () => {
|
|
const isConnected = smtp
|
|
.verify()
|
|
.then(async () => {
|
|
logger.info("SMTP connected");
|
|
return true;
|
|
})
|
|
.catch((err: Error) => {
|
|
logger.error("SMTP error");
|
|
logger.error(err);
|
|
return false;
|
|
});
|
|
|
|
return isConnected;
|
|
};
|
|
|
|
return { sendMail, verify };
|
|
};
|