Files
outline/server/emails/mailer.tsx
Tom Moor 728790e38f feat: Validate Google, Azure, OIDC SSO access (#3590)
* chore: Store expiresAt on UserAuthentications. This represents the time that the accessToken is no longer valid and should be exchanged using the refreshToken

* feat: Check and expire Google SSO

* fix: Better handling of multiple auth methods
Added more docs

* fix: Retry access validation with network errors

* Small refactor, add Azure token validation support

* doc

* test

* lint

* OIDC refresh support

* CheckSSOAccessTask -> ValidateSSOAccessTask
Added lastValidatedAt column
Skip checks if validated within 5min
Some edge cases around encrypted columns
2022-06-05 13:18:51 -07:00

135 lines
3.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import nodemailer, { Transporter } from "nodemailer";
import Oy from "oy-vey";
import * as React from "react";
import env from "@server/env";
import Logger from "@server/logging/Logger";
import { APM } from "@server/logging/tracing";
import { baseStyles } from "./templates/components/EmailLayout";
const useTestEmailService =
env.ENVIRONMENT === "development" && !env.SMTP_USERNAME;
type SendMailOptions = {
to: string;
replyTo?: string;
subject: string;
previewText?: string;
text: string;
component: React.ReactNode;
headCSS?: string;
};
/**
* Mailer class to send emails.
*/
@APM.trace({
spanName: "mailer",
})
export class Mailer {
transporter: Transporter | undefined;
constructor() {
if (env.SMTP_HOST) {
this.transporter = nodemailer.createTransport(this.getOptions());
}
if (useTestEmailService) {
Logger.info(
"email",
"SMTP_USERNAME not provided, generating test account…"
);
this.getTestTransportOptions().then((options) => {
if (!options) {
Logger.info(
"email",
"Couldn't generate a test account with ethereal.email at this time emails will not be sent."
);
return;
}
this.transporter = nodemailer.createTransport(options);
});
}
}
sendMail = async (data: SendMailOptions): Promise<void> => {
const { transporter } = this;
if (!transporter) {
Logger.info(
"email",
`Attempted to send email "${data.subject}" to ${data.to} but no transport configured.`
);
return;
}
const html = Oy.renderTemplate(data.component, {
title: data.subject,
headCSS: [baseStyles, data.headCSS].join(" "),
previewText: data.previewText,
});
try {
Logger.info("email", `Sending email "${data.subject}" to ${data.to}`);
const info = await transporter.sendMail({
from: env.SMTP_FROM_EMAIL,
replyTo: data.replyTo ?? env.SMTP_REPLY_EMAIL ?? env.SMTP_FROM_EMAIL,
to: data.to,
subject: data.subject,
html,
text: data.text,
});
if (useTestEmailService) {
Logger.info(
"email",
`Preview Url: ${nodemailer.getTestMessageUrl(info)}`
);
}
} catch (err) {
Logger.error(`Error sending email to ${data.to}`, err);
throw err; // Re-throw for queue to re-try
}
};
private getOptions() {
return {
host: env.SMTP_HOST,
port: env.SMTP_PORT,
secure: env.SMTP_SECURE ?? env.ENVIRONMENT === "production",
auth: env.SMTP_USERNAME
? {
user: env.SMTP_USERNAME,
pass: env.SMTP_PASSWORD,
}
: undefined,
tls: env.SMTP_TLS_CIPHERS
? {
ciphers: env.SMTP_TLS_CIPHERS,
}
: undefined,
};
}
private async getTestTransportOptions() {
try {
const testAccount = await nodemailer.createTestAccount();
return {
host: "smtp.ethereal.email",
port: 587,
secure: false,
auth: {
user: testAccount.user,
pass: testAccount.pass,
},
};
} catch (err) {
return undefined;
}
}
}
const mailer = new Mailer();
export default mailer;