Files
qmi-cloud/qmi-cloud-common/send-email.js
Manuel Romero 5a5d97dcaa fix
2025-05-12 00:39:30 +02:00

463 lines
19 KiB
JavaScript

'use strict';
const axios = require('axios');
const https = require("https");
const SMTP_EMAIL_SENDER = process.env.SMTP_EMAIL_SENDER;
const SMTP_EXECUTION_TOKEN = process.env.SMTP_EXECUTION_TOKEN;
const HOSTNAME_URL = process.env.HOSTNAME_URL || "https://qmicloud.qliktech.com";
const FOOTER = `<div style="color:#404040;font-size:16px;padding:30px 0px 50px 0px">
<p style="margin:0px">Check it out at <a href="${HOSTNAME_URL}">${HOSTNAME_URL}</a></p>
</div>`;
const getFooter = function(provision) {
return `<div style="color:#404040;font-size:16px;padding:30px 0px 50px 0px">
<p style="margin:0px">Check out your provision details <a href="${HOSTNAME_URL}/provision/${provision._id}">here</a>.</p>
</div>`;
}
async function _doSend(to, subject, htmlText) {
try {
console.log(`SendEmail (Qlik SMTP)# sending via : ` + SMTP_EMAIL_SENDER);
var body = {
"to": to,
"subject": subject,
"html": htmlText
};
var headers = {};
if ( SMTP_EXECUTION_TOKEN ) {
headers = {
'X-Execution-Token': SMTP_EXECUTION_TOKEN,
'Content-Type': 'application/json'
};
console.log(`SendEmail (Qlik SMTP)# sending with X-Execution-Token: ` + SMTP_EXECUTION_TOKEN);
}
await axios({
url: SMTP_EMAIL_SENDER,
method: "POST",
data: body,
headers: headers,
httpsAgent: new https.Agent({
rejectUnauthorized: false
})
});
console.log(`SendEmail (Qlik SMTP)# message sent to:` + to);
} catch (err) {
// Handle Error Here
console.log("SendEmail (Qlik SMTP) _doSend error: could not send the email to: " +to, err);
}
}
function _getCommonDetails(provision, scenario){
var description = decodeURI(scenario.description);
var externalAccess = provision.isExternalAccess? 'Yes' : 'No';
var schedule = "";
if ( !provision.schedule || provision.schedule.is24x7 ) {
schedule = "24x7";
} else if ( provision.schedule && !provision.schedule.is24x7 ) {
schedule = `from ${provision.schedule.localeStartupTime}h until ${provision.schedule.localeShutdownTime}h (${provision.schedule.localTimezone})`;
}
return `<div style="color:#404040;font-size:18px;margin:20px 0px">
<p style="margin:0px">Provision information:</p>
</div>
<div>
<span style="color:#404040">ID: </span> ${provision._id}
</div>
<div>
<span style="color:#404040">VMs Running schedule: </span> ${schedule}
</div>
<div>
<span style="color:#404040">Purpose: </span> ${provision.description}
</div>
<div>
<span style="color:#404040">Scenario: </span> ${scenario.title}
</div>
<div>
<span style="color:#404040">With external access: </span> ${externalAccess}
</div>
<div>
<span style="color:#404040">Description: </span> ${description}
</div>`;
}
function getHtmlScenarioDestroyIn24( provision, scenario, period, warningDays) {
var common = _getCommonDetails(provision,scenario);
return`<div style="width:600px;color:black!important;font-family:'Source Sans Pro',sans-serif;padding:50px">
<div style="background-color:white;height:100%;padding:20px 10px">
<div style="color:#404040;font-size:34px;text-align:center;margin:20px">
<p style="margin:0px">QMI Cloud</p>
</div>
<div style="color:#404040;font-size:22px;margin:20px 0px 40px 0px">
<p style="margin:0px">Provision '${scenario.title}' inactive more than ${period} days</p>
</div>
<div style="color:#404040;font-size:18px;margin:10px 0px">
<p style="margin:0px;color: #FF2020">This scenario will be automatically DESTROYED in ${(warningDays*24)} hours.</p>
</div>
<div style="color:#404040;font-size:16px;margin:30px 0px">
<p style="margin:0px">If you don't want this to happen, you've got ${(warningDays*24)} hours (from when this email was sent) as a grace period to get back at 'Running' status this provision.</p>
</div>
${common}
${FOOTER}
</div>
</div>`;
}
function getHtmlScenarioIndays( provision, scenario, period, warningDays) {
var common = _getCommonDetails(provision,scenario);
return`<div style="width:600px;color:black!important;font-family:'Source Sans Pro',sans-serif;padding:50px">
<div style="background-color:white;height:100%;padding:20px 10px">
<div style="color:#404040;font-size:34px;text-align:center;margin:20px">
<p style="margin:0px">QMI Cloud</p>
</div>
<div style="color:#404040;font-size:22px;margin:20px 0px 40px 0px">
<p style="margin:0px">Provision '${scenario.title}' running more than ${period} days</p>
</div>
<div style="color:#404040;font-size:18px;margin:10px 0px">
<p style="margin:0px;color: #FF2020">This scenario was meant to live active no more than 90 days. It's been 80 days since creation and it will be automatically DESTROYED in ${warningDays} days. Please, consider to backup it up manually if you don't want to lose data.</p>
</div>
${common}
${FOOTER}
</div>
</div>`;
}
function getHtmlScenarioVMsStopped( provision, scenario) {
var common = _getCommonDetails(provision,scenario);
return `<div style="width:600px;color:black!important;font-family:'Source Sans Pro',sans-serif;padding:50px">
<div style="background-color:white;height:100%;padding:20px 10px">
<div style="color:#404040;font-size:34px;text-align:center;margin:20px">
<p style="margin:0px">QMI Cloud</p>
</div>
<div style="color:#404040;font-size:22px;margin:20px 0px 40px 0px">
<p style="margin:0px">Provision '${scenario.title}'</p>
</div>
<div style="color:#404040;font-size:18px;margin:10px 0px">
<p style="margin:0px;color: #FF2020">All VMs for this provision <b>stopped</b> automatically.</p>
</div>
<div style="color:#404040;font-size:16px;margin:30px 0px">
<p style="margin:0px">You can start them up again from <a href="${HOSTNAME_URL}">${HOSTNAME_URL}</a></p>
</div>
${common}
${FOOTER}
</div>
</div>`;
}
function getHtmlSharedProvision( provision, scenario) {
var common = _getCommonDetails(provision,scenario);
return `<div style="width:600px;color:black!important;font-family:'Source Sans Pro',sans-serif;padding:50px">
<div style="background-color:white;height:100%;padding:20px 10px">
<div style="color:#404040;font-size:34px;text-align:center;margin:20px">
<p style="margin:0px">QMI Cloud</p>
</div>
<div style="color:#404040;font-size:22px;margin:20px 0px 40px 0px">
<p style="margin:0px">Provision '${scenario.title}'</p>
</div>
<div style="color:#404040;font-size:18px;margin:30px 0px">
<p style="margin:0px;color: #FF2020">'${provision.user.displayName}' is sharing with you!</p>
</div>
${common}
${FOOTER}
</div>
</div>`;
}
function getHtmlRotateKeyProvision(provision, scenario) {
var htmlint;
if ( provision && provision.outputs ) {
htmlint = `<div style="color:#404040;font-size:20px;padding: 10px 0px;">New Storage Account Details:</div>`;
htmlint += `<table style="width:100%" border="0">`;
} else {
htmlint = "";
}
for (let key in provision.outputs) {
if (key.includes("StorageAccount-")){
htmlint += `<tr>
<td style="padding-right: 15px"><b style="color:#404040">${key}</b></td>
<td><pre style="color:#404040;">${provision.outputs[key]}</pre></td>
</tr>`;
}
}
if ( provision && provision.outputs ) {
htmlint += `</table>`;
}
var myFooter = getFooter(provision);
var common = `<div style="color:#404040;font-size:18px;margin:20px 0px">
<p style="margin:0px">Provision information:</p>
</div>
<div>
<span style="color:#404040">Provision ID: </span> ${provision._id}
</div>
<div>
<span style="color:#404040">Purpose: </span> ${provision.description}
</div>
<div>
<span style="color:#404040">Scenario: </span> ${scenario.title}
</div>`;
return `<div style="width:600px;color:black!important;font-family:'Source Sans Pro',sans-serif;padding:50px">
<div style="background-color:white;height:100%;padding:20px 10px">
<div style="color:#404040;font-size:34px;text-align:center;margin:20px">
<p style="margin:0px">QMI Cloud</p>
</div>
<div style="color:#404040;font-size:22px;margin:20px 0px">
<p style="margin:0px">Scenario '${scenario.title}' - Storage Account Access Key!</p>
</div>
<div style="color:#404040;margin:20px 0px">
<p style="margin:0px">In order to improve security, your Storage Account Access Key in this QMI scenario provision has been rotated. You may need to update your Storage Account connections with bellow new details.</p>
</div>
${common}
<div style="margin: 30px 0px;">
${htmlint}
</div>
${myFooter}
</div>
</div>`;
}
function getHtmlRotateAWSKeyProvision(provision, scenario) {
var htmlint;
if ( provision && provision.outputs ) {
htmlint = `<div style="color:#404040;font-size:20px;padding: 10px 0px;">New S3 IAM User Details:</div>`;
htmlint += `<table style="width:100%" border="0">`;
} else {
htmlint = "";
}
for (let key in provision.outputs) {
if (key.includes("iam_") || key.includes("S3_") ){
htmlint += `<tr>
<td style="padding-right: 15px"><b style="color:#404040">${key}</b></td>
<td><pre style="color:#404040;">${provision.outputs[key]}</pre></td>
</tr>`;
}
}
if ( provision && provision.outputs ) {
htmlint += `</table>`;
}
var myFooter = getFooter(provision);
var common = `<div style="color:#404040;font-size:18px;margin:20px 0px">
<p style="margin:0px">Provision information:</p>
</div>
<div>
<span style="color:#404040">Provision ID: </span> ${provision._id}
</div>
<div>
<span style="color:#404040">Purpose: </span> ${provision.description}
</div>
<div>
<span style="color:#404040">Scenario: </span> ${scenario.title}
</div>`;
return `<div style="width:600px;color:black!important;font-family:'Source Sans Pro',sans-serif;padding:50px">
<div style="background-color:white;height:100%;padding:20px 10px">
<div style="color:#404040;font-size:34px;text-align:center;margin:20px">
<p style="margin:0px">QMI Cloud</p>
</div>
<div style="color:#404040;font-size:22px;margin:20px 0px">
<p style="margin:0px">Scenario '${scenario.title}' - New AWS IAM User Access Key!</p>
</div>
<div style="color:#404040;margin:20px 0px">
<p style="margin:0px">In order to improve security, your AWS IAM User Access Key in this QMI scenario provision has been rotated. You may need to update your AWS S3 connections with bellow new details.</p>
</div>
${common}
<div style="margin: 30px 0px;">
${htmlint}
</div>
${myFooter}
</div>
</div>`;
}
function getHtmlScenarioWillStopIn24( provision, scenario, period, warningDays ) {
var common = _getCommonDetails(provision,scenario);
return`<div style="width:600px;color:black!important;font-family:'Source Sans Pro',sans-serif;padding:50px">
<div style="background-color:white;height:100%;padding:20px 10px">
<div style="color:#404040;font-size:34px;text-align:center;margin:20px">
<p style="margin:0px">QMI Cloud</p>
</div>
<div style="color:#404040;font-size:22px;margin:20px 0px 40px 0px">
<p style="margin:0px">Provision '${scenario.title}' - VMs running for ${period} days</p>
</div>
<div style="color:#404040;font-size:18px;margin:10px 0px">
<p style="margin:0px;color: #FF2020">This scenario will automatically stop its VMs in ${warningDays*24} hours.</p>
</div>
<div style="color:#404040;font-size:18px;margin:20px 0px 10px 0px">
<p style="margin:0px;color: #FF2020">Take action and extend the period ${(period+warningDays)} extra days.</p>
</div>
<div style="color:#404040;font-size:16px;margin:30px 0px">
<p style="margin:0px">If you don't want the VMs to automatically stop, you've got ${warningDays*24} hours (from when this email was sent) as a grace period to extend this scenario's <b style="color: #009845">Running</b> VMs for ${(period+warningDays)} extra days.</p>
</div>
${common}
${FOOTER}
</div>
</div>`;
}
function getHtmlNewProvision(provision, scenario) {
var htmlint;
if ( provision && provision.outputs ) {
htmlint = `<div style="color:#404040;font-size:20px;padding: 10px 0px;">Connection resources</div>`;
htmlint += `<table style="width:100%" border="0">`;
} else {
htmlint = "";
}
for (let key in provision.outputs) {
htmlint += `<tr>
<td style="padding-right: 15px"><b style="color:#404040">${key}</b></td>
<td><pre style="color:#404040;">${provision.outputs[key]}</pre></td>
</tr>`;
}
if ( provision && provision.outputs ) {
htmlint += `</table>`;
}
var common = _getCommonDetails(provision, scenario);
return `<div style="width:600px;color:black!important;font-family:'Source Sans Pro',sans-serif;padding:50px">
<div style="background-color:white;height:100%;padding:20px 10px">
<div style="color:#404040;font-size:34px;text-align:center;margin:20px">
<p style="margin:0px">QMI Cloud</p>
</div>
<div style="color:#404040;font-size:22px;margin:20px 0px">
<p style="margin:0px">Scenario '${scenario.title}' successfully provisioned!</p>
</div>
${common}
<div style="margin: 30px 0px;">
${htmlint}
</div>
${FOOTER}
</div>
</div>`;
}
function getHtmlErrorProvision(provision, scenario) {
var common = _getCommonDetails(provision, scenario);
return`<div style="width:600px;color:black!important;font-family:'Source Sans Pro',sans-serif;padding:50px">
<div style="background-color:white;height:100%;padding:20px 10px">
<div style="color:#404040;font-size:34px;text-align:center;margin:20px">
<p style="margin:0px">QMI Cloud</p>
</div>
<div style="color:#404040;font-size:20px;margin:20px 0px">
<p style="margin:0px;color: #FF2020">Oops! Something didn't work.</p>
</div>
<div style="color:#404040;font-size:20px;margin:20px 0px 50px 0px">
<p style="margin:0px">Scenario '${scenario.title}' failed during provision.</p>
</div>
<div style="color:#404040;font-size:16px;margin:30px 0px">
<p style="margin:0px">Please, follow these steps:</p>
<ul>
<li>Reach out the person responsible for this scenario for support.</li>
<li>As soon as it's possible, consider destroy this provision since it's taking valuable cloud resources which might imply relevant cost.</li>
</ul>
</div>
${common}
${FOOTER}
</div>
</div>`;
}
function getHtmlDestroyProvision(provision, scenario) {
var common = _getCommonDetails(provision, scenario);
return`<div style="width:600px;color:black!important;font-family:'Source Sans Pro',sans-serif;padding:50px">
<div style="background-color:white;height:100%;padding:20px 10px">
<div style="color:#404040;font-size:34px;text-align:center;margin:20px">
<p style="margin:0px">QMI Cloud</p>
</div>
<div style="color:#404040;font-size:20px;margin:40px 0px">
<p style="margin:0px">Scenario '${scenario.title}' successfully destroyed!</p>
</div>
${common}
${FOOTER}
</div>
</div>`;
}
// async..await is not allowed in global scope, must use a wrapper
async function sendProvisionSuccess( provision, scenario ) {
const htmlText = getHtmlNewProvision(provision, scenario);
await _doSend(provision.user.upn, 'QMI Cloud - Provision finished successfully', htmlText);
}
async function sendProvisionError(provision, scenario ) {
const htmlText = getHtmlErrorProvision(provision, scenario);
await _doSend(provision.user.upn, 'QMI Cloud - Provision with errors', htmlText);
}
async function sendDestroyedSuccess(provision, scenario ) {
const htmlText = getHtmlDestroyProvision(provision, scenario);
await _doSend(provision.user.upn, 'QMI Cloud - Provision destroyed successfully', htmlText);
}
async function sendWillStopIn24( provision, scenario, period, warningDays ) {
const htmlText = getHtmlScenarioWillStopIn24( provision, scenario, period, warningDays);
await _doSend(provision.user.upn, `QMI Cloud - VMs will stop in ${warningDays*24} hours`, htmlText);
}
async function sendWillDestroyIn24( provision, scenario, period, warningDays ) {
const htmlText = getHtmlScenarioDestroyIn24( provision, scenario, period, warningDays);
await _doSend(provision.user.upn, `QMI Cloud - Provision will destroy in ${(warningDays*24)} hours`, htmlText);
}
async function sendWillDestroyInDays( provision, scenario, period, warningDays ) {
const htmlText = getHtmlScenarioIndays( provision, scenario, period, warningDays);
await _doSend(provision.user.upn, `QMI Cloud - Provision will destroy in ${warningDays} days`, htmlText);
}
async function sendVMsStopped( provision, scenario ) {
const htmlText = getHtmlScenarioVMsStopped( provision, scenario);
await _doSend(provision.user.upn, 'QMI Cloud - VMs stopped automatically', htmlText);
}
async function sendSharedProvision(provision, shareWithUser) {
const htmlText = getHtmlSharedProvision( provision, provision._scenarioDoc);
await _doSend(shareWithUser.upn, `${provision.user.displayName} shared a QMI Cloud provision with you.`, htmlText);
}
async function sendRotateKey(provision, user) {
const htmlText = getHtmlRotateKeyProvision( provision, provision._scenarioDoc);
await _doSend(user.upn, `QMI Cloud - NEW Storage Account Access Key`, htmlText);
}
async function sendRotateAWSKey(provision, user) {
const htmlText = getHtmlRotateAWSKeyProvision( provision, provision._scenarioDoc);
await _doSend(user.upn, `QMI Cloud - NEW S3 AWS IAM User Access Key`, htmlText);
}
module.exports.sendProvisionSuccess = sendProvisionSuccess;
module.exports.sendProvisionError = sendProvisionError;
module.exports.sendDestroyedSuccess = sendDestroyedSuccess;
module.exports.sendWillStopIn24 = sendWillStopIn24;
module.exports.sendWillDestroyInDays = sendWillDestroyInDays;
module.exports.sendVMsStopped = sendVMsStopped;
module.exports.sendWillDestroyIn24 = sendWillDestroyIn24;
module.exports.sendSharedProvision= sendSharedProvision;
module.exports.sendRotateKey = sendRotateKey;
module.exports.sendRotateAWSKey = sendRotateAWSKey;
module.exports._doSend = _doSend;