[feature] Notification service added

This commit is contained in:
Georgii Petrov
2024-04-23 17:08:02 +03:00
parent d2895a5500
commit 44e4d28b55
6 changed files with 234 additions and 20 deletions

View File

@ -16,13 +16,38 @@
"visibilityTimeout": 300,
"retentionPeriod": 900
},
"mail": {
"smtpConnectionConfiguration": {
"email": {
"smtpServerConfiguration": {
"host": "smtp.ethereal.email",
"port": 587,
"auth": {
"user": "onlyoffice",
"pass": "onlyoffice"
}
},
"messageDefaults": {
"from": "some.mail@server.com",
"connectionConfiguration": {
"disableFileAccess": false,
"disableUrlAccess": false
},
"contactDefaults": {
"from": "from.mail@server.com"
}
},
"notification": {
"rules": {
"licenseExpired": {
"transportType": [
"email",
"telegram"
],
"template": {
"title": "License",
"body": "license expires after %s!!!"
},
"policies": {
"repeatInterval": "1d"
}
}
}
},
"storage": {

View File

@ -915,6 +915,13 @@
"@smithy/util-retry": "^1.0.4",
"tslib": "^2.5.0",
"uuid": "^8.3.2"
},
"dependencies": {
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
}
}
},
"@smithy/middleware-serde": {
@ -1980,9 +1987,9 @@
}
},
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="
},
"verror": {
"version": "1.10.0",

View File

@ -25,6 +25,7 @@
"request-filtering-agent": "1.0.5",
"rhea": "1.0.24",
"uri-js": "4.2.2",
"uuid": "9.0.1",
"win-ca": "3.5.0"
}
}

View File

@ -35,9 +35,11 @@
const config = require('config');
const nodemailer = require('nodemailer');
const cfgConnection = config.get('mail.smtpConnectionConfiguration');
const cfgMessageDefaults = config.get('mail.messageDefaults');
const operationContext = require('./operationContext');
const cfgConnection = config.get('email.connectionConfiguration');
const ctx = new operationContext.Context();
const connectionDefaultSettings = {
pool: true,
socketTimeout: 1000 * 60 * 2,
@ -48,7 +50,7 @@ const connectionDefaultSettings = {
const settings = Object.assign(connectionDefaultSettings, cfgConnection);
const smtpTransporters = new Map();
function createTransporter(ctx, host, port, auth, messageCommonParameters = {}) {
function createTransporter(host, port, auth, messageCommonParameters = {}) {
const server = {
host,
port,
@ -56,10 +58,13 @@ function createTransporter(ctx, host, port, auth, messageCommonParameters = {})
secure: port === 465
};
const transport = Object.assign({}, server, settings);
const mailDefaults = Object.assign({}, cfgMessageDefaults, messageCommonParameters);
try {
const transporter = nodemailer.createTransport(transport, mailDefaults);
if (smtpTransporters.has(`${host}:${auth.user}`)) {
return;
}
const transporter = nodemailer.createTransport(transport, messageCommonParameters);
smtpTransporters.set(`${host}:${auth.user}`, transporter);
} catch (error) {
ctx.logger.error('Mail service smtp transporter creation error: %o\nWith parameters: \n\thost - %s, \n\tport - %d, \n\tauth = %o', error.stack, host, port, auth);
@ -75,7 +80,7 @@ async function send(host, userLogin, mailObject) {
return transporter.sendMail(mailObject);
}
function deleteTransporter(ctx, host, userLogin) {
function deleteTransporter(host, userLogin) {
const transporter = smtpTransporters.get(`${host}:${userLogin}`);
if (!transporter) {
ctx.logger.error(`MailService: no transporter exists for host "${host}" and user "${userLogin}"`);
@ -91,6 +96,10 @@ function transportersRelease() {
smtpTransporters.clear();
}
function isCreated(host, user) {
return smtpTransporters
}
module.exports = {
createTransporter,
send,

View File

@ -0,0 +1,173 @@
/*
* (c) Copyright Ascensio System SIA 2010-2023
*
* This program is a free software product. You can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License (AGPL)
* version 3 as published by the Free Software Foundation. In accordance with
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
* that Ascensio System SIA expressly excludes the warranty of non-infringement
* of any third-party rights.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
*
* You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish
* street, Riga, Latvia, EU, LV-1050.
*
* The interactive user interfaces in modified source and object code versions
* of the Program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU AGPL version 3.
*
* Pursuant to Section 7(b) of the License you must retain the original Product
* logo when distributing the program. Pursuant to Section 7(e) we decline to
* grant you any rights under trademark law for use of our trademarks.
*
* All the Product's GUI elements, including illustrations and icon sets, as
* well as technical writing content are licensed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
*
*/
'use strict';
const config = require('config');
const ms = require('ms');
const uuid = require('uuid');
const mailService = require('./mailService');
const operationContext = require('./operationContext');
const cfgMailServer = config.get('email.smtpServerConfiguration');
const cfgMailMessageDefaults = config.get('email.contactDefaults');
const uuidNamespace = 'e071294c-e621-4195-b453-6da9344e5c72';
const ctx = new operationContext.Context();
const defaultLicenseRepeatInterval = 1000 * 60 * 60 * 24;
const recipients = new Map();
const notificationTypes = {
LICENSE_EXPIRED: 0,
};
class TransportInterface {
getRecipientId(messageParams) {}
async send(message) {}
contentGeneration(template, messageParams) {}
}
class MailTransport extends TransportInterface {
host = cfgMailServer.host;
port = cfgMailServer.port;
auth = cfgMailServer.auth;
constructor() {
super();
mailService.createTransporter(this.host, this.port, this.auth, cfgMailMessageDefaults);
}
getRecipientId(messageParams) {
if (!messageParams.to) {
return uuid.NIL;
}
return uuid.v5(messageParams.to, uuidNamespace);
}
send(message) {
ctx.logger.info('!!!!!!!!!!!!!!!!!!!!!SENDONG:', message);
return mailService.send(this.host, this.auth.user, message);
}
contentGeneration(template, messageParams) {
const messageBody = {
subject: template.title,
text: template.body
};
return Object.assign({}, messageBody, messageParams);
}
}
// TODO:
class TelegramTransport extends TransportInterface {
constructor() {
super();
}
}
class Transport {
transport = new TransportInterface();
constructor(transportName) {
this.name = transportName;
switch (transportName) {
case 'email':
this.transport = new MailTransport();
break;
case 'telegram':
this.transport = new TelegramTransport();
break
default:
ctx.logger.error(`Notification service error: transport method "${transportName}" not implemented`);
}
}
}
function getRecipientData(id, defaultPoliciesData) {
if (id === uuid.NIL) {
return;
}
const recipientData = recipients.get(id);
if (!recipientData) {
recipients.set(id, defaultPoliciesData);
return defaultPoliciesData;
}
return recipientData;
}
async function notify(notificationType, messageParams) {
ctx.logger.info('!!!!!!!!!!!!!!!!!!!!!HERE:', messageParams);
switch (notificationType) {
case notificationTypes.LICENSE_EXPIRED: {
licenseExpiredNotify(messageParams);
break;
}
}
}
function licenseExpiredNotify(messageParams) {
const cfgLicenseExpired = config.get('notification.rules.licenseExpired');
const transportObjects = cfgLicenseExpired.transportType.map(transport => new Transport(transport));
const { repeatInterval } = cfgLicenseExpired.policies;
const intervalMilliseconds = ms(repeatInterval) ?? defaultLicenseRepeatInterval;
const defaultPolices = {
repeatDate: Date.now(),
}
transportObjects.forEach(object => {
const recipientId = object.transport.getRecipientId(messageParams);
const data = getRecipientData(recipientId, defaultPolices);
if (!data) {
ctx.logger.error(`Notification service error, licenseExpiredNotify() transport "${object.name}": missing recipient data in message object \n${messageParams}`);
return;
}
const currentDateMs = Date.now();
if (data.repeatDate <= currentDateMs) {
data.repeatDate = currentDateMs + intervalMilliseconds;
const message = object.transport.contentGeneration(cfgLicenseExpired.template, messageParams);
object.transport.send(message);
}
});
}
module.exports = {
notificationTypes,
notify
};

View File

@ -1,20 +1,19 @@
const { describe, test, expect, afterAll} = require('@jest/globals');
const { describe, test, expect, afterAll } = require('@jest/globals');
const nodemailer = require('../../Common/node_modules/nodemailer');
const mailService = require('../../Common/sources/mailService');
const operationContext = require('../../Common/sources/operationContext');
const ctx = new operationContext.Context();
const defaultTestSMTPServer = {
host: 'smtp.ethereal.email',
port: 587
};
const testTimeout = 1000 * 10;
afterAll(function () {
mailService.transportersRelease();
})
describe('Mail service', function () {
console.log('!!!!!!!!!!!!!!!!', global.dev);
describe('SMTP', function () {
const { host, port } = defaultTestSMTPServer;
@ -23,7 +22,7 @@ describe('Mail service', function () {
// Ethereial is a special SMTP sever for mailing tests in collaboration with Nodemailer.
const accounts = await Promise.all([nodemailer.createTestAccount(), nodemailer.createTestAccount(), nodemailer.createTestAccount()]);
const auth = accounts.map(account => { return { user: account.user, pass: account.pass }});
auth.forEach(credential => mailService.createTransporter(ctx, host, port, credential, { from: 'some.mail@ethereal.com' }));
auth.forEach(credential => mailService.createTransporter(host, port, credential, { from: 'some.mail@ethereal.com' }));
for (let i = 0; i < auth.length; i++) {
const credentials = auth[i];
@ -37,7 +36,7 @@ describe('Mail service', function () {
}
const accountToBeDeleted = auth[1];
mailService.deleteTransporter(ctx, host, accountToBeDeleted.user);
mailService.deleteTransporter(host, accountToBeDeleted.user);
const errorPromise = mailService.send(
host,
@ -46,6 +45,6 @@ describe('Mail service', function () {
);
await expect(errorPromise).rejects.toEqual(`MailService: no transporter exists for host "${host}" and user "${accountToBeDeleted.user}"`);
});
}, testTimeout);
});
});