Send login codes via SMTP using Mailpit-compatible transport
This commit is contained in:
69
src/mailer.js
Normal file
69
src/mailer.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import nodemailer from 'nodemailer';
|
||||
|
||||
let cachedTransporter = null;
|
||||
|
||||
function getSmtpConfig() {
|
||||
const host = process.env.SMTP_HOST;
|
||||
const portRaw = process.env.SMTP_PORT;
|
||||
const from = process.env.SMTP_FROM;
|
||||
const secureRaw = process.env.SMTP_SECURE ?? 'false';
|
||||
const user = process.env.SMTP_USER;
|
||||
const pass = process.env.SMTP_PASS;
|
||||
|
||||
if (!host) {
|
||||
throw new Error('SMTP_HOST is required for email login code delivery.');
|
||||
}
|
||||
if (!portRaw) {
|
||||
throw new Error('SMTP_PORT is required for email login code delivery.');
|
||||
}
|
||||
if (!from) {
|
||||
throw new Error('SMTP_FROM is required for email login code delivery.');
|
||||
}
|
||||
|
||||
const port = Number(portRaw);
|
||||
if (!Number.isInteger(port) || port <= 0) {
|
||||
throw new Error('SMTP_PORT must be a valid integer port number.');
|
||||
}
|
||||
|
||||
const secure = String(secureRaw).toLowerCase() === 'true';
|
||||
const hasUser = Boolean(user);
|
||||
const hasPass = Boolean(pass);
|
||||
|
||||
if (hasUser !== hasPass) {
|
||||
throw new Error('SMTP_USER and SMTP_PASS must be set together.');
|
||||
}
|
||||
|
||||
return {
|
||||
from,
|
||||
transport: {
|
||||
host,
|
||||
port,
|
||||
secure,
|
||||
...(hasUser && hasPass ? { auth: { user, pass } } : {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getTransporter() {
|
||||
if (cachedTransporter) {
|
||||
return cachedTransporter;
|
||||
}
|
||||
|
||||
const config = getSmtpConfig();
|
||||
cachedTransporter = nodemailer.createTransport(config.transport);
|
||||
return cachedTransporter;
|
||||
}
|
||||
|
||||
export async function sendLoginCodeEmail({ to, code, expiresAt }) {
|
||||
const { from } = getSmtpConfig();
|
||||
const transporter = getTransporter();
|
||||
const expiresText = new Date(expiresAt).toLocaleString('ru-RU', { hour12: false });
|
||||
|
||||
await transporter.sendMail({
|
||||
from,
|
||||
to,
|
||||
subject: 'Код входа в личный кабинет Fregat',
|
||||
text: `Код входа: ${code}\nДействует до: ${expiresText}`,
|
||||
html: `<p>Код входа: <b>${code}</b></p><p>Действует до: ${expiresText}</p>`,
|
||||
});
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
maskAuthDestination,
|
||||
verifyLoginChallengeCode,
|
||||
} from './auth.js';
|
||||
import { sendLoginCodeEmail } from './mailer.js';
|
||||
import { dispatchToUserConnections, sendMessengerMessage } from './messenger.js';
|
||||
import { dateTimeScalar, jsonScalar } from './scalars.js';
|
||||
|
||||
@@ -287,7 +288,11 @@ export const resolvers = {
|
||||
});
|
||||
|
||||
const code = getStaticAuthCode();
|
||||
console.info(`[auth] login code for ${destination}: ${code}`);
|
||||
await sendLoginCodeEmail({
|
||||
to: destination,
|
||||
code,
|
||||
expiresAt: challenge.expiresAt,
|
||||
});
|
||||
|
||||
return {
|
||||
challengeToken: challenge.challengeToken,
|
||||
|
||||
Reference in New Issue
Block a user