fix(core): update
This commit is contained in:
parent
6bd396e2d6
commit
f5a36ab53a
@ -26,7 +26,12 @@
|
||||
"@push.rocks/projectinfo": "^5.0.1",
|
||||
"@push.rocks/qenv": "^6.0.5",
|
||||
"@push.rocks/smartdata": "^5.0.7",
|
||||
"@push.rocks/smartfile": "^11.0.4",
|
||||
"@push.rocks/smartlog": "^3.0.3",
|
||||
"@push.rocks/smartmail": "^1.0.24",
|
||||
"@push.rocks/smartpath": "^5.0.5",
|
||||
"@push.rocks/smartstate": "^2.0.0"
|
||||
"@push.rocks/smartrequest": "^2.0.21",
|
||||
"@push.rocks/smartstate": "^2.0.0",
|
||||
"@serve.zone/interfaces": "^1.0.34"
|
||||
}
|
||||
}
|
||||
|
93
pnpm-lock.yaml
generated
93
pnpm-lock.yaml
generated
@ -20,12 +20,27 @@ dependencies:
|
||||
'@push.rocks/smartdata':
|
||||
specifier: ^5.0.7
|
||||
version: 5.0.33
|
||||
'@push.rocks/smartfile':
|
||||
specifier: ^11.0.4
|
||||
version: 11.0.4
|
||||
'@push.rocks/smartlog':
|
||||
specifier: ^3.0.3
|
||||
version: 3.0.3
|
||||
'@push.rocks/smartmail':
|
||||
specifier: ^1.0.24
|
||||
version: 1.0.24
|
||||
'@push.rocks/smartpath':
|
||||
specifier: ^5.0.5
|
||||
version: 5.0.11
|
||||
'@push.rocks/smartrequest':
|
||||
specifier: ^2.0.21
|
||||
version: 2.0.21
|
||||
'@push.rocks/smartstate':
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.17
|
||||
'@serve.zone/interfaces':
|
||||
specifier: ^1.0.34
|
||||
version: 1.0.34
|
||||
|
||||
devDependencies:
|
||||
'@git.zone/tsbuild':
|
||||
@ -1321,6 +1336,17 @@ packages:
|
||||
dependencies:
|
||||
'@push.rocks/smartpromise': 4.0.3
|
||||
|
||||
/@push.rocks/smartdns@5.0.4:
|
||||
resolution: {integrity: sha512-DCeekCzAJEPn8gZOPZLnFGFZdgLKtCqb3YfwJpfkdSfPxwlSXzjs5rYmzmsg0BlQP+7pSR43fuLMERB4DGCn9w==}
|
||||
dependencies:
|
||||
'@pushrocks/smartdelay': 2.0.13
|
||||
'@pushrocks/smartenv': 5.0.5
|
||||
'@pushrocks/smartpromise': 3.1.10
|
||||
'@pushrocks/smartrequest': 2.0.15
|
||||
'@tsclass/tsclass': 4.0.51
|
||||
dns2: 2.1.0
|
||||
dev: false
|
||||
|
||||
/@push.rocks/smartenv@5.0.12:
|
||||
resolution: {integrity: sha512-tDEFwywzq0FNzRYc9qY2dRl2pgQuZG0G2/yml2RLWZWSW+Fn1EHshnKOGHz8o77W7zvu4hTgQQX42r/JY5XHTg==}
|
||||
dependencies:
|
||||
@ -1425,6 +1451,16 @@ packages:
|
||||
'@push.rocks/isounique': 1.0.5
|
||||
'@push.rocks/smartlog-interfaces': 3.0.0
|
||||
|
||||
/@push.rocks/smartmail@1.0.24:
|
||||
resolution: {integrity: sha512-UUIKQmMiCpcnkql4uoPp0kDRDnGl1jV5UbacRkoaKHh6PPd4trkZRVcVQadtpEqteFb6W0B9Z/YtMKpTPxUsxA==}
|
||||
dependencies:
|
||||
'@push.rocks/smartdns': 5.0.4
|
||||
'@push.rocks/smartfile': 10.0.41
|
||||
'@push.rocks/smartmustache': 3.0.2
|
||||
'@push.rocks/smartpath': 5.0.11
|
||||
'@push.rocks/smartrequest': 2.0.21
|
||||
dev: false
|
||||
|
||||
/@push.rocks/smartmanifest@2.0.2:
|
||||
resolution: {integrity: sha512-QGc5C9vunjfUbYsPGz5bynV/mVmPHkrQDkWp8ZO8VJtK1GZe+njgbrNyxn2SUHR0IhSAbSXl1j4JvBqYf5eTVg==}
|
||||
|
||||
@ -1457,6 +1493,12 @@ packages:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/@push.rocks/smartmustache@3.0.2:
|
||||
resolution: {integrity: sha512-G3LyRXoJhyM+iQhkvP/MR/2WYMvC9U7zc2J44JxUM5tPdkQ+o3++FbfRtnZj6rz5X/A7q03//vsxPitVQwoi2Q==}
|
||||
dependencies:
|
||||
handlebars: 4.7.8
|
||||
dev: false
|
||||
|
||||
/@push.rocks/smartnetwork@3.0.2:
|
||||
resolution: {integrity: sha512-s6CNGzQ1n/d/6cOKXbxeW6/tO//dr1woLqI01g7XhqTriw0nsm2G2kWaZh2J0VOguGNWBgQVCIpR0LjdRNWb3g==}
|
||||
dependencies:
|
||||
@ -1933,6 +1975,16 @@ packages:
|
||||
form-data: 4.0.0
|
||||
dev: false
|
||||
|
||||
/@pushrocks/smartrequest@2.0.15:
|
||||
resolution: {integrity: sha512-QDXXKhOwAIp+TNFrDglApBpbCTClHrf8pUM3w81q5+VtrMbqUrLINHhInXt3ZUSgTS7RD9HtJSIVSqAukcJo5A==}
|
||||
deprecated: This package has been deprecated in favour of the new package at @push.rocks/smartrequest
|
||||
dependencies:
|
||||
'@pushrocks/smartpromise': 4.0.2
|
||||
'@pushrocks/smarturl': 3.0.6
|
||||
agentkeepalive: 4.5.0
|
||||
form-data: 4.0.0
|
||||
dev: false
|
||||
|
||||
/@pushrocks/smartrx@2.0.27:
|
||||
resolution: {integrity: sha512-aFRpGxDZgHH1mpmkRBTFwuIVqFiDxk22n2vX2gW4hntV0nJGlt9M9dixMFFXGUjabwX9hHW7y60QPJm2rKaypA==}
|
||||
deprecated: This package has been deprecated in favour of the new package at @push.rocks/smartrx
|
||||
@ -2003,12 +2055,25 @@ packages:
|
||||
deprecated: This package has been deprecated in favour of the new package at @push.rocks/smarturl
|
||||
dev: false
|
||||
|
||||
/@pushrocks/smarturl@3.0.6:
|
||||
resolution: {integrity: sha512-YHWnYE1mm8WIJk1usSXg+kNM7s7ByM+PKApO9cgl0T/oGybjzAJiO3clGNDro4ysP0TD+WuvJuiVue02bErEBQ==}
|
||||
deprecated: This package has been deprecated in favour of the new package at @push.rocks/smarturl
|
||||
dev: false
|
||||
|
||||
/@rkusa/linebreak@1.0.0:
|
||||
resolution: {integrity: sha512-yCSm87XA1aYMgfcABSxcIkk3JtCw3AihNceHY+DnZGLvVP/g2z3UWZbi0xIoYpZWAJEVPr5Zt3QE37Q80wF1pA==}
|
||||
dependencies:
|
||||
unicode-trie: 0.3.1
|
||||
dev: true
|
||||
|
||||
/@serve.zone/interfaces@1.0.34:
|
||||
resolution: {integrity: sha512-5G340VReyWT8v4DNMUOfWQtZSDAA+sCnWwhMUaVcGglMSSMpPge6N3G7M7gbRknf87nqN13U/6+ZzVqRz3U52w==}
|
||||
dependencies:
|
||||
'@apiglobal/typedrequest-interfaces': 2.0.1
|
||||
'@push.rocks/smartlog-interfaces': 3.0.0
|
||||
'@tsclass/tsclass': 4.0.51
|
||||
dev: false
|
||||
|
||||
/@sindresorhus/is@5.6.0:
|
||||
resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
|
||||
engines: {node: '>=14.16'}
|
||||
@ -3731,6 +3796,10 @@ packages:
|
||||
dns-packet: 5.6.1
|
||||
dev: true
|
||||
|
||||
/dns2@2.1.0:
|
||||
resolution: {integrity: sha512-m27K11aQalRbmUs7RLaz6aPyceLjAoqjPRNTdE7qUouQpl+PC8Bi67O+i9SuJUPbQC8dxFrczAxfmTPuTKHNkw==}
|
||||
dev: false
|
||||
|
||||
/domexception@1.0.1:
|
||||
resolution: {integrity: sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==}
|
||||
dependencies:
|
||||
@ -4360,6 +4429,19 @@ packages:
|
||||
engines: {node: '>=4.x'}
|
||||
dev: true
|
||||
|
||||
/handlebars@4.7.8:
|
||||
resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==}
|
||||
engines: {node: '>=0.4.7'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
minimist: 1.2.8
|
||||
neo-async: 2.6.2
|
||||
source-map: 0.6.1
|
||||
wordwrap: 1.0.0
|
||||
optionalDependencies:
|
||||
uglify-js: 3.17.4
|
||||
dev: false
|
||||
|
||||
/has-bigints@1.0.2:
|
||||
resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==}
|
||||
dev: true
|
||||
@ -5190,7 +5272,6 @@ packages:
|
||||
|
||||
/minimist@1.2.8:
|
||||
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
||||
dev: true
|
||||
|
||||
/minipass@7.0.4:
|
||||
resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==}
|
||||
@ -5361,6 +5442,10 @@ packages:
|
||||
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
/neo-async@2.6.2:
|
||||
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
|
||||
dev: false
|
||||
|
||||
/new-find-package-json@2.0.0:
|
||||
resolution: {integrity: sha512-lDcBsjBSMlj3LXH2v/FW3txlh2pYTjmbOXPYJD93HI5EwuLzI11tdHSIpUMmfq/IOsldj4Ps8M8flhm+pCK4Ew==}
|
||||
engines: {node: '>=12.22.0'}
|
||||
@ -6131,7 +6216,6 @@ packages:
|
||||
/source-map@0.6.1:
|
||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/source-map@0.7.4:
|
||||
resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==}
|
||||
@ -6540,7 +6624,6 @@ packages:
|
||||
resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/unbox-primitive@1.0.2:
|
||||
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
|
||||
@ -6706,6 +6789,10 @@ packages:
|
||||
string-width: 2.1.1
|
||||
dev: true
|
||||
|
||||
/wordwrap@1.0.0:
|
||||
resolution: {integrity: sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=}
|
||||
dev: false
|
||||
|
||||
/wrap-ansi@5.1.0:
|
||||
resolution: {integrity: sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==}
|
||||
engines: {node: '>=6'}
|
||||
|
@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/platformservice',
|
||||
version: '1.0.2',
|
||||
version: '1.0.3',
|
||||
description: 'contains the platformservice container with mail, sms, letter, ai services.'
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as plugins from './platformservice.plugins.js';
|
||||
import * as paths from './platformservice.paths.js';
|
||||
import * as plugins from './plugins.js';
|
||||
import * as paths from './paths.js';
|
||||
import { PlatformServiceDb } from './classes.platformservicedb.js'
|
||||
|
||||
export class SzPlatformService {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import * as plugins from './platformservice.plugins.js';
|
||||
import * as plugins from './plugins.js';
|
||||
import { SzPlatformService } from './classes.platformservice.js';
|
||||
|
||||
|
||||
|
@ -1,17 +1,16 @@
|
||||
import * as plugins from './email.plugins.js';
|
||||
import { Email } from './email.classes.email.js';
|
||||
import { request } from 'http';
|
||||
import { logger } from './email.logging.js';
|
||||
import * as plugins from '../plugins.js';
|
||||
import { EmailService } from './email.classes.emailservice.js';
|
||||
import { logger } from '../logger.js';
|
||||
|
||||
export class ApiManager {
|
||||
public emailRef: Email;
|
||||
public emailRef: EmailService;
|
||||
|
||||
public typedRouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
constructor(emailRefArg: Email) {
|
||||
constructor(emailRefArg: EmailService) {
|
||||
this.emailRef = emailRefArg;
|
||||
this.emailRef.mainTypedRouter.addTypedRouter(this.typedRouter);
|
||||
this.typedRouter.addTypedHandler<plugins.lointEmail.IRequestSendEmail>(
|
||||
this.emailRef.typedrouter.addTypedRouter(this.typedRouter);
|
||||
this.typedRouter.addTypedHandler<plugins.servezoneInterfaces.platformservice.mta.IRequest_SendEmail>(
|
||||
new plugins.typedrequest.TypedHandler('sendEmail', async (requestData) => {
|
||||
const mailToSend = new plugins.smartmail.Smartmail({
|
||||
body: requestData.body,
|
||||
@ -22,7 +21,7 @@ export class ApiManager {
|
||||
if (requestData.attachments) {
|
||||
for (const attachment of requestData.attachments) {
|
||||
mailToSend.addAttachment(
|
||||
await plugins.smartfile.Smartfile.fromString(
|
||||
await plugins.smartfile.SmartFile.fromString(
|
||||
attachment.name,
|
||||
attachment.binaryAttachmentString,
|
||||
'binary'
|
||||
|
@ -1,11 +1,11 @@
|
||||
import * as plugins from './email.plugins.js';
|
||||
import { Email } from './email.classes.email.js';
|
||||
import { EmailService } from './email.classes.emailservice.js';
|
||||
|
||||
export class MailgunConnector {
|
||||
public emailRef: Email;
|
||||
public emailRef: EmailService;
|
||||
public mailgunAccount: plugins.mailgun.MailgunAccount;
|
||||
|
||||
constructor(emailRefArg: Email) {
|
||||
constructor(emailRefArg: EmailService) {
|
||||
this.emailRef = emailRefArg;
|
||||
this.mailgunAccount = new plugins.mailgun.MailgunAccount({
|
||||
apiToken: this.emailRef.qenv.getEnvVarOnDemand('MAILGUN_API_TOKEN'),
|
||||
|
@ -1,16 +1,16 @@
|
||||
import * as plugins from './email.plugins.js';
|
||||
import * as paths from './email.paths.js';
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as paths from '../paths.js';
|
||||
import { MailgunConnector } from './email.classes.connector.mailgun.js';
|
||||
import { RuleManager } from './email.classes.rulemanager.js';
|
||||
import { ApiManager } from './email.classes.apimanager.js';
|
||||
import { logger } from './email.logging.js';
|
||||
import { logger } from '../logger.js';
|
||||
import type { SzPlatformService } from '../classes.platformservice.js';
|
||||
|
||||
export class Email {
|
||||
export class EmailService {
|
||||
public platformServiceRef: SzPlatformService;
|
||||
|
||||
// typedrouter
|
||||
public mainTypedRouter = new plugins.typedrequest.TypedRouter();
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
// connectors
|
||||
public mailgunConnector: MailgunConnector;
|
||||
@ -22,11 +22,12 @@ export class Email {
|
||||
|
||||
constructor(platformServiceRefArg: SzPlatformService) {
|
||||
this.platformServiceRef = platformServiceRefArg;
|
||||
this.platformServiceRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||
this.mailgunConnector = new MailgunConnector(this);
|
||||
this.ruleManager = new RuleManager(this);
|
||||
this.platformServiceRef.typedserver.server.addRoute(
|
||||
'/mailgun-notify',
|
||||
new plugins.loleServiceserver.Handler('POST', async (req, res) => {
|
||||
new plugins.typedserver.servertools.Handler('POST', async (req, res) => {
|
||||
console.log('Got a mailgun email notification');
|
||||
res.status(200);
|
||||
res.end();
|
@ -1,14 +1,14 @@
|
||||
import * as plugins from './email.plugins.js';
|
||||
import { Email } from './email.classes.email.js';
|
||||
import { EmailService } from './email.classes.emailservice.js';
|
||||
import { logger } from './email.logging.js';
|
||||
|
||||
export class RuleManager {
|
||||
public emailRef: Email;
|
||||
public emailRef: EmailService;
|
||||
public smartruleInstance = new plugins.smartrule.SmartRule<
|
||||
plugins.smartmail.Smartmail<plugins.mailgun.IMailgunMessage>
|
||||
>();
|
||||
|
||||
constructor(emailRefArg: Email) {
|
||||
constructor(emailRefArg: EmailService) {
|
||||
this.emailRef = emailRefArg;
|
||||
}
|
||||
|
||||
|
@ -1,13 +0,0 @@
|
||||
import * as plugins from './email.plugins.js';
|
||||
import * as paths from './email.paths.js';
|
||||
|
||||
const projectInfoNpm = new plugins.projectinfo.ProjectinfoNpm(paths.packageDir);
|
||||
|
||||
export const logger = plugins.loleLog.createLoleLogger({
|
||||
companyUnit: 'lossless.cloud',
|
||||
containerName: 'email',
|
||||
containerVersion: projectInfoNpm.version,
|
||||
sentryAppName: 'email',
|
||||
sentryDsn: 'https://7037e86f36134ced85ae56a57daa1e5e@o169278.ingest.sentry.io/5280282',
|
||||
zone: 'servezone',
|
||||
});
|
@ -1,43 +0,0 @@
|
||||
// native scope
|
||||
import * as path from 'path';
|
||||
|
||||
export { path };
|
||||
|
||||
// @losslessone_private scope
|
||||
import * as loleLog from '@losslessone_private/lole-log';
|
||||
import * as loleServiceserver from '@losslessone_private/lole-serviceserver';
|
||||
|
||||
import * as lointEmail from '@losslessone_private/loint-email';
|
||||
|
||||
export { loleLog, loleServiceserver, lointEmail };
|
||||
|
||||
// @apiglobal scope
|
||||
import * as typedrequest from '@apiglobal/typedrequest';
|
||||
|
||||
export { typedrequest };
|
||||
|
||||
// @mojoio scope
|
||||
import * as mailgun from '@mojoio/mailgun';
|
||||
|
||||
export { mailgun };
|
||||
|
||||
// @pushrocks scope
|
||||
import * as projectinfo from '@pushrocks/projectinfo';
|
||||
import * as qenv from '@pushrocks/qenv';
|
||||
import * as smartfile from '@pushrocks/smartfile';
|
||||
import * as smartmail from '@pushrocks/smartmail';
|
||||
import * as smartpath from '@pushrocks/smartpath';
|
||||
import * as smartrequest from '@pushrocks/smartrequest';
|
||||
import * as smartrule from '@pushrocks/smartrule';
|
||||
import * as smartvalidator from '@pushrocks/smartvalidator';
|
||||
|
||||
export {
|
||||
projectinfo,
|
||||
qenv,
|
||||
smartfile,
|
||||
smartmail,
|
||||
smartpath,
|
||||
smartrequest,
|
||||
smartrule,
|
||||
smartvalidator,
|
||||
};
|
@ -1,3 +1,3 @@
|
||||
import { Email } from './email.classes.email.js';
|
||||
import { EmailService } from './email.classes.emailservice.js';
|
||||
|
||||
export { Email };
|
||||
export { EmailService as Email };
|
@ -1,4 +1,4 @@
|
||||
export * from './00_commitinfo_data.js';
|
||||
import { SzPlatformService } from './classes.platformservice.js'
|
||||
import { SzPlatformService } from './classes.platformservice.js';
|
||||
|
||||
export const runCli = async () => {}
|
9
ts/logger.ts
Normal file
9
ts/logger.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
export const logger = new plugins.smartlog.Smartlog({
|
||||
logContext: {
|
||||
environment: 'production',
|
||||
runtime: 'node',
|
||||
zone: 'serve.zone',
|
||||
}
|
||||
});
|
8
ts/mta/index.ts
Normal file
8
ts/mta/index.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export * from './mta.classes.dkimcreator.js';
|
||||
export * from './mta.classes.emailsignjob.js';
|
||||
export * from './mta.classes.dkimverifier.js';
|
||||
export * from './mta.classes.mta.js';
|
||||
export * from './mta.classes.smtpserver.js';
|
||||
export * from './mta.classes.emailsendjob.js';
|
||||
export * from './mta.classes.mta.js';
|
||||
export * from './mta.classes.email.js';
|
7
ts/mta/mta.classes.apimanager.ts
Normal file
7
ts/mta/mta.classes.apimanager.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
export class ApiManager {
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
|
||||
}
|
119
ts/mta/mta.classes.dkimcreator.ts
Normal file
119
ts/mta/mta.classes.dkimcreator.ts
Normal file
@ -0,0 +1,119 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as paths from './paths.js';
|
||||
|
||||
import { Email } from './mta.classes.email.js';
|
||||
import type { MTA } from './mta.classes.mta.js';
|
||||
|
||||
const readFile = plugins.util.promisify(plugins.fs.readFile);
|
||||
const writeFile = plugins.util.promisify(plugins.fs.writeFile);
|
||||
const generateKeyPair = plugins.util.promisify(plugins.crypto.generateKeyPair);
|
||||
|
||||
export interface IKeyPaths {
|
||||
privateKeyPath: string;
|
||||
publicKeyPath: string;
|
||||
}
|
||||
|
||||
export class DKIMCreator {
|
||||
private keysDir: string;
|
||||
|
||||
constructor(metaRef: MTA, keysDir = paths.keysDir) {
|
||||
this.keysDir = keysDir;
|
||||
}
|
||||
|
||||
public async getKeyPathsForDomain(domainArg: string): Promise<IKeyPaths> {
|
||||
return {
|
||||
privateKeyPath: plugins.path.join(this.keysDir, `${domainArg}-private.pem`),
|
||||
publicKeyPath: plugins.path.join(this.keysDir, `${domainArg}-public.pem`),
|
||||
};
|
||||
}
|
||||
|
||||
// Check if a DKIM key is present and creates one and stores it to disk otherwise
|
||||
public async handleDKIMKeysForDomain(domainArg: string): Promise<void> {
|
||||
try {
|
||||
await this.readDKIMKeys(domainArg);
|
||||
} catch (error) {
|
||||
console.log(`No DKIM keys found for ${domainArg}. Generating...`);
|
||||
await this.createAndStoreDKIMKeys(domainArg);
|
||||
const dnsValue = await this.getDNSRecordForDomain(domainArg);
|
||||
plugins.smartfile.fs.ensureDirSync(paths.dnsRecordsDir);
|
||||
plugins.smartfile.memory.toFsSync(JSON.stringify(dnsValue, null, 2), plugins.path.join(paths.dnsRecordsDir, `${domainArg}.dkimrecord.json`));
|
||||
}
|
||||
}
|
||||
|
||||
public async handleDKIMKeysForEmail(email: Email): Promise<void> {
|
||||
const domain = email.from.split('@')[1];
|
||||
await this.handleDKIMKeysForDomain(domain);
|
||||
}
|
||||
|
||||
// Read DKIM keys from disk
|
||||
public async readDKIMKeys(domainArg: string): Promise<{ privateKey: string; publicKey: string }> {
|
||||
const keyPaths = await this.getKeyPathsForDomain(domainArg);
|
||||
const [privateKeyBuffer, publicKeyBuffer] = await Promise.all([
|
||||
readFile(keyPaths.privateKeyPath),
|
||||
readFile(keyPaths.publicKeyPath),
|
||||
]);
|
||||
|
||||
// Convert the buffers to strings
|
||||
const privateKey = privateKeyBuffer.toString();
|
||||
const publicKey = publicKeyBuffer.toString();
|
||||
|
||||
return { privateKey, publicKey };
|
||||
}
|
||||
|
||||
// Create a DKIM key pair
|
||||
private async createDKIMKeys(): Promise<{ privateKey: string; publicKey: string }> {
|
||||
const { privateKey, publicKey } = await generateKeyPair('rsa', {
|
||||
modulusLength: 2048,
|
||||
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
||||
privateKeyEncoding: { type: 'pkcs1', format: 'pem' },
|
||||
});
|
||||
|
||||
return { privateKey, publicKey };
|
||||
}
|
||||
|
||||
// Store a DKIM key pair to disk
|
||||
private async storeDKIMKeys(
|
||||
privateKey: string,
|
||||
publicKey: string,
|
||||
privateKeyPath: string,
|
||||
publicKeyPath: string
|
||||
): Promise<void> {
|
||||
await Promise.all([writeFile(privateKeyPath, privateKey), writeFile(publicKeyPath, publicKey)]);
|
||||
}
|
||||
|
||||
// Create a DKIM key pair and store it to disk
|
||||
private async createAndStoreDKIMKeys(domain: string): Promise<void> {
|
||||
const { privateKey, publicKey } = await this.createDKIMKeys();
|
||||
const keyPaths = await this.getKeyPathsForDomain(domain);
|
||||
await this.storeDKIMKeys(
|
||||
privateKey,
|
||||
publicKey,
|
||||
keyPaths.privateKeyPath,
|
||||
keyPaths.publicKeyPath
|
||||
);
|
||||
console.log(`DKIM keys for ${domain} created and stored.`);
|
||||
}
|
||||
|
||||
private async getDNSRecordForDomain(domainArg: string): Promise<plugins.tsclass.network.IDnsRecord> {
|
||||
await this.handleDKIMKeysForDomain(domainArg);
|
||||
const keys = await this.readDKIMKeys(domainArg);
|
||||
|
||||
// Remove the PEM header and footer and newlines
|
||||
const pemHeader = '-----BEGIN PUBLIC KEY-----';
|
||||
const pemFooter = '-----END PUBLIC KEY-----';
|
||||
const keyContents = keys.publicKey
|
||||
.replace(pemHeader, '')
|
||||
.replace(pemFooter, '')
|
||||
.replace(/\n/g, '');
|
||||
|
||||
// Now generate the DKIM DNS TXT record
|
||||
const dnsRecordValue = `v=DKIM1; h=sha256; k=rsa; p=${keyContents}`;
|
||||
|
||||
return {
|
||||
name: `mta._domainkey.${domainArg}`,
|
||||
type: 'TXT',
|
||||
dnsSecEnabled: null,
|
||||
value: dnsRecordValue,
|
||||
};
|
||||
}
|
||||
}
|
35
ts/mta/mta.classes.dkimverifier.ts
Normal file
35
ts/mta/mta.classes.dkimverifier.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import { MTA } from './mta.classes.mta.js';
|
||||
|
||||
class DKIMVerifier {
|
||||
public mtaRef: MTA;
|
||||
|
||||
constructor(mtaRefArg: MTA) {
|
||||
this.mtaRef = mtaRefArg;
|
||||
}
|
||||
|
||||
async verify(email: string): Promise<boolean> {
|
||||
console.log('Trying to verify DKIM now...');
|
||||
|
||||
try {
|
||||
const verification = await plugins.mailauth.authenticate(email, {
|
||||
/* resolver: (...args) => {
|
||||
console.log(args);
|
||||
} */
|
||||
});
|
||||
console.log(verification);
|
||||
if (verification && verification.dkim.results[0].status.result === 'pass') {
|
||||
console.log('DKIM Verification result: pass');
|
||||
return true;
|
||||
} else {
|
||||
console.error('DKIM Verification failed:', verification?.error || 'Unknown error');
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('DKIM Verification failed:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { DKIMVerifier };
|
10
ts/mta/mta.classes.dnsmanager.ts
Normal file
10
ts/mta/mta.classes.dnsmanager.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import type { MTA } from './mta.classes.mta.js';
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
export class DNSManager {
|
||||
public mtaRef: MTA;
|
||||
|
||||
constructor(mtaRefArg: MTA) {
|
||||
this.mtaRef = mtaRefArg;
|
||||
}
|
||||
}
|
36
ts/mta/mta.classes.email.ts
Normal file
36
ts/mta/mta.classes.email.ts
Normal file
@ -0,0 +1,36 @@
|
||||
export interface IAttachment {
|
||||
filename: string;
|
||||
content: Buffer;
|
||||
contentType: string;
|
||||
}
|
||||
|
||||
export interface IEmailOptions {
|
||||
from: string;
|
||||
to: string;
|
||||
subject: string;
|
||||
text: string;
|
||||
attachments: IAttachment[];
|
||||
mightBeSpam?: boolean;
|
||||
}
|
||||
|
||||
export class Email {
|
||||
from: string;
|
||||
to: string;
|
||||
subject: string;
|
||||
text: string;
|
||||
attachments: IAttachment[];
|
||||
mightBeSpam: boolean;
|
||||
|
||||
constructor(options: IEmailOptions) {
|
||||
this.from = options.from;
|
||||
this.to = options.to;
|
||||
this.subject = options.subject;
|
||||
this.text = options.text;
|
||||
this.attachments = options.attachments;
|
||||
this.mightBeSpam = options.mightBeSpam || false;
|
||||
}
|
||||
|
||||
public getFromDomain() {
|
||||
return this.from.split('@')[1]
|
||||
}
|
||||
}
|
173
ts/mta/mta.classes.emailsendjob.ts
Normal file
173
ts/mta/mta.classes.emailsendjob.ts
Normal file
@ -0,0 +1,173 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as paths from './paths.js';
|
||||
import { Email } from './mta.classes.email.js';
|
||||
import { EmailSignJob } from './mta.classes.emailsignjob.js';
|
||||
import type { MTA } from './mta.classes.mta.js';
|
||||
|
||||
export class EmailSendJob {
|
||||
mtaRef: MTA;
|
||||
private email: Email;
|
||||
private socket: plugins.net.Socket | plugins.tls.TLSSocket = null;
|
||||
private mxRecord: string = null;
|
||||
|
||||
constructor(mtaRef: MTA, emailArg: Email) {
|
||||
this.email = emailArg;
|
||||
this.mtaRef = mtaRef;
|
||||
}
|
||||
|
||||
async send(): Promise<void> {
|
||||
const domain = this.email.to.split('@')[1];
|
||||
const addresses = await this.resolveMx(domain);
|
||||
addresses.sort((a, b) => a.priority - b.priority);
|
||||
this.mxRecord = addresses[0].exchange;
|
||||
|
||||
console.log(`Using ${this.mxRecord} as mail server for domain ${domain}`);
|
||||
|
||||
this.socket = plugins.net.connect(25, this.mxRecord);
|
||||
await this.processInitialResponse();
|
||||
await this.sendCommand(`EHLO ${this.email.from.split('@')[1]}\r\n`, '250');
|
||||
try {
|
||||
await this.sendCommand('STARTTLS\r\n', '220');
|
||||
this.socket = plugins.tls.connect({ socket: this.socket, rejectUnauthorized: false });
|
||||
await this.processTLSUpgrade(this.email.from.split('@')[1]);
|
||||
} catch (error) {
|
||||
console.log('Error sending STARTTLS command:', error);
|
||||
console.log('Continuing with unencrypted connection...');
|
||||
}
|
||||
|
||||
await this.sendMessage();
|
||||
}
|
||||
|
||||
private resolveMx(domain: string): Promise<plugins.dns.MxRecord[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
plugins.dns.resolveMx(domain, (err, addresses) => {
|
||||
if (err) {
|
||||
console.error('Error resolving MX:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(addresses);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private processInitialResponse(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.socket.once('data', (data) => {
|
||||
const response = data.toString();
|
||||
if (!response.startsWith('220')) {
|
||||
console.error('Unexpected initial server response:', response);
|
||||
reject(new Error(`Unexpected initial server response: ${response}`));
|
||||
} else {
|
||||
console.log('Received initial server response:', response);
|
||||
console.log('Connected to server, sending EHLO...');
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private processTLSUpgrade(domain: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.socket.once('secureConnect', async () => {
|
||||
console.log('TLS started successfully');
|
||||
try {
|
||||
await this.sendCommand(`EHLO ${domain}\r\n`, '250');
|
||||
resolve();
|
||||
} catch (err) {
|
||||
console.error('Error sending EHLO after TLS upgrade:', err);
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private sendCommand(command: string, expectedResponseCode?: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.socket.write(command, (error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!expectedResponseCode) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
this.socket.once('data', (data) => {
|
||||
const response = data.toString();
|
||||
if (response.startsWith('221')) {
|
||||
this.socket.destroy();
|
||||
resolve();
|
||||
}
|
||||
if (!response.startsWith(expectedResponseCode)) {
|
||||
reject(new Error(`Unexpected server response: ${response}`));
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async sendMessage(): Promise<void> {
|
||||
console.log('Preparing email message...');
|
||||
const messageId = `<${plugins.uuid.v4()}@${this.email.from.split('@')[1]}>`;
|
||||
|
||||
// Create a boundary for the email parts
|
||||
const boundary = '----=_NextPart_' + plugins.uuid.v4();
|
||||
|
||||
const headers = {
|
||||
From: this.email.from,
|
||||
To: this.email.to,
|
||||
Subject: this.email.subject,
|
||||
'Content-Type': `multipart/mixed; boundary="${boundary}"`,
|
||||
};
|
||||
|
||||
// Construct the body of the message
|
||||
let body = `--${boundary}\r\nContent-Type: text/html; charset=utf-8\r\n\r\n${this.email.text}\r\n`;
|
||||
|
||||
// Then, the attachments
|
||||
for (let attachment of this.email.attachments) {
|
||||
body += `--${boundary}\r\nContent-Type: ${attachment.contentType}; name="${attachment.filename}"\r\n`;
|
||||
body += 'Content-Transfer-Encoding: base64\r\n';
|
||||
body += `Content-Disposition: attachment; filename="${attachment.filename}"\r\n\r\n`;
|
||||
body += attachment.content.toString('base64') + '\r\n';
|
||||
}
|
||||
|
||||
// End of email
|
||||
body += `--${boundary}--\r\n`;
|
||||
|
||||
// Create an instance of DKIMSigner
|
||||
const dkimSigner = new EmailSignJob(this.mtaRef, {
|
||||
domain: this.email.getFromDomain(), // Replace with your domain
|
||||
selector: `mta`, // Replace with your DKIM selector
|
||||
headers: headers,
|
||||
body: body,
|
||||
});
|
||||
|
||||
// Construct the message with DKIM-Signature header
|
||||
let message = `Message-ID: ${messageId}\r\nFrom: ${this.email.from}\r\nTo: ${this.email.to}\r\nSubject: ${this.email.subject}\r\nContent-Type: multipart/mixed; boundary="${boundary}"\r\n\r\n`;
|
||||
message += body;
|
||||
|
||||
let signatureHeader = await dkimSigner.getSignatureHeader(message);
|
||||
message = `${signatureHeader}${message}`;
|
||||
|
||||
plugins.smartfile.fs.ensureDirSync(paths.sentEmailsDir);
|
||||
plugins.smartfile.memory.toFsSync(message, plugins.path.join(paths.sentEmailsDir, `${Date.now()}.eml`));
|
||||
|
||||
|
||||
// Adding necessary commands before sending the actual email message
|
||||
await this.sendCommand(`MAIL FROM:<${this.email.from}>\r\n`, '250');
|
||||
await this.sendCommand(`RCPT TO:<${this.email.to}>\r\n`, '250');
|
||||
await this.sendCommand(`DATA\r\n`, '354');
|
||||
|
||||
// Now send the message content
|
||||
await this.sendCommand(message);
|
||||
await this.sendCommand('\r\n.\r\n', '250');
|
||||
|
||||
await this.sendCommand('QUIT\r\n', '221');
|
||||
console.log('Email message sent successfully!');
|
||||
}
|
||||
}
|
69
ts/mta/mta.classes.emailsignjob.ts
Normal file
69
ts/mta/mta.classes.emailsignjob.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import type { MTA } from './mta.classes.mta.js';
|
||||
|
||||
interface Headers {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
interface IEmailSignJobOptions {
|
||||
domain: string;
|
||||
selector: string;
|
||||
headers: Headers;
|
||||
body: string;
|
||||
}
|
||||
|
||||
export class EmailSignJob {
|
||||
mtaRef: MTA;
|
||||
jobOptions: IEmailSignJobOptions;
|
||||
|
||||
constructor(mtaRefArg: MTA, options: IEmailSignJobOptions) {
|
||||
this.mtaRef = mtaRefArg;
|
||||
this.jobOptions = options;
|
||||
}
|
||||
|
||||
async loadPrivateKey(): Promise<string> {
|
||||
return plugins.fs.promises.readFile(
|
||||
(await this.mtaRef.dkimCreator.getKeyPathsForDomain(this.jobOptions.domain)).privateKeyPath,
|
||||
'utf-8'
|
||||
);
|
||||
}
|
||||
|
||||
public async getSignatureHeader(emailMessage: string): Promise<string> {
|
||||
const signResult = await plugins.dkimSign(emailMessage, {
|
||||
// Optional, default canonicalization, default is "relaxed/relaxed"
|
||||
canonicalization: 'relaxed/relaxed', // c=
|
||||
|
||||
// Optional, default signing and hashing algorithm
|
||||
// Mostly useful when you want to use rsa-sha1, otherwise no need to set
|
||||
algorithm: 'rsa-sha256',
|
||||
|
||||
// Optional, default is current time
|
||||
signTime: new Date(), // t=
|
||||
|
||||
// Keys for one or more signatures
|
||||
// Different signatures can use different algorithms (mostly useful when
|
||||
// you want to sign a message both with RSA and Ed25519)
|
||||
signatureData: [
|
||||
{
|
||||
signingDomain: this.jobOptions.domain, // d=
|
||||
selector: this.jobOptions.selector, // s=
|
||||
// supported key types: RSA, Ed25519
|
||||
privateKey: await this.loadPrivateKey(), // k=
|
||||
|
||||
// Optional algorithm, default is derived from the key.
|
||||
// Overrides whatever was set in parent object
|
||||
algorithm: 'rsa-sha256',
|
||||
|
||||
// Optional signature specifc canonicalization, overrides whatever was set in parent object
|
||||
canonicalization: 'relaxed/relaxed', // c=
|
||||
|
||||
// Maximum number of canonicalized body bytes to sign (eg. the "l=" tag).
|
||||
// Do not use though. This is available only for compatibility testing.
|
||||
// maxBodyLength: 12345
|
||||
},
|
||||
],
|
||||
});
|
||||
const signature = signResult.signatures;
|
||||
return signature;
|
||||
}
|
||||
}
|
66
ts/mta/mta.classes.mta.ts
Normal file
66
ts/mta/mta.classes.mta.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
import { Email } from './mta.classes.email.js';
|
||||
import { EmailSendJob } from './mta.classes.emailsendjob.js';
|
||||
import { DKIMCreator } from './mta.classes.dkimcreator.js';
|
||||
import { DKIMVerifier } from './mta.classes.dkimverifier.js';
|
||||
import { SMTPServer } from './mta.classes.smtpserver.js';
|
||||
import { DNSManager } from './mta.classes.dnsmanager.js';
|
||||
|
||||
export class MTA {
|
||||
public server: SMTPServer;
|
||||
public dkimCreator: DKIMCreator;
|
||||
public dkimVerifier: DKIMVerifier;
|
||||
public dnsManager: DNSManager;
|
||||
|
||||
constructor() {
|
||||
this.dkimCreator = new DKIMCreator(this);
|
||||
this.dkimVerifier = new DKIMVerifier(this);
|
||||
this.dnsManager = new DNSManager(this);
|
||||
}
|
||||
|
||||
public async start() {
|
||||
// lets get the certificate
|
||||
/**
|
||||
* gets a certificate for a domain used by a service
|
||||
* @param serviceNameArg
|
||||
* @param domainNameArg
|
||||
*/
|
||||
const typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
const typedsocketClient = await plugins.typedsocket.TypedSocket.createClient(
|
||||
typedrouter,
|
||||
'https://cloudly.lossless.one:443'
|
||||
);
|
||||
const getCertificateForDomainOverHttps = async (domainNameArg: string) => {
|
||||
const typedCertificateRequest =
|
||||
typedsocketClient.createTypedRequest<any>('getSslCertificate');
|
||||
const typedResponse = await typedCertificateRequest.fire({
|
||||
authToken: '', // do proper auth here
|
||||
requiredCertName: domainNameArg,
|
||||
});
|
||||
return typedResponse.certificate;
|
||||
};
|
||||
const certificate = await getCertificateForDomainOverHttps('mta.lossless.one');
|
||||
await typedsocketClient.stop();
|
||||
this.server = new SMTPServer(this, {
|
||||
port: 25,
|
||||
key: certificate.privateKey,
|
||||
cert: certificate.publicKey,
|
||||
});
|
||||
await this.server.start();
|
||||
}
|
||||
|
||||
public async stop() {
|
||||
if (!this.server) {
|
||||
console.error('Server is not running');
|
||||
return;
|
||||
}
|
||||
await this.server.stop();
|
||||
}
|
||||
|
||||
public async send(email: Email): Promise<void> {
|
||||
await this.dkimCreator.handleDKIMKeysForEmail(email);
|
||||
const sendJob = new EmailSendJob(this, email);
|
||||
await sendJob.send();
|
||||
}
|
||||
}
|
191
ts/mta/mta.classes.smtpserver.ts
Normal file
191
ts/mta/mta.classes.smtpserver.ts
Normal file
@ -0,0 +1,191 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as paths from './paths.js';
|
||||
import { Email } from './mta.classes.email.js';
|
||||
import type { MTA } from './mta.classes.mta.js';
|
||||
|
||||
export interface ISmtpServerOptions {
|
||||
port: number;
|
||||
key: string;
|
||||
cert: string;
|
||||
}
|
||||
|
||||
export class SMTPServer {
|
||||
public mtaRef: MTA;
|
||||
private smtpServerOptions: ISmtpServerOptions;
|
||||
private server: plugins.net.Server;
|
||||
private emailBufferStringMap: Map<plugins.net.Socket, string>;
|
||||
|
||||
constructor(mtaRefArg: MTA, optionsArg: ISmtpServerOptions) {
|
||||
console.log('SMTPServer instance is being created...');
|
||||
|
||||
this.mtaRef = mtaRefArg;
|
||||
this.smtpServerOptions = optionsArg;
|
||||
this.emailBufferStringMap = new Map();
|
||||
|
||||
this.server = plugins.net.createServer((socket) => {
|
||||
console.log('New connection established...');
|
||||
|
||||
socket.write('220 mta.lossless.one ESMTP Postfix\r\n');
|
||||
|
||||
socket.on('data', (data) => {
|
||||
this.processData(socket, data);
|
||||
});
|
||||
|
||||
socket.on('end', () => {
|
||||
console.log('Socket closed. Deleting related emailBuffer...');
|
||||
socket.destroy();
|
||||
this.emailBufferStringMap.delete(socket);
|
||||
});
|
||||
|
||||
socket.on('error', () => {
|
||||
console.error('Socket error occurred. Deleting related emailBuffer...');
|
||||
socket.destroy();
|
||||
this.emailBufferStringMap.delete(socket);
|
||||
});
|
||||
|
||||
socket.on('close', () => {
|
||||
console.log('Connection was closed by the client');
|
||||
socket.destroy();
|
||||
this.emailBufferStringMap.delete(socket);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private startTLS(socket: plugins.net.Socket) {
|
||||
const secureContext = plugins.tls.createSecureContext({
|
||||
key: this.smtpServerOptions.key,
|
||||
cert: this.smtpServerOptions.cert,
|
||||
});
|
||||
|
||||
const tlsSocket = new plugins.tls.TLSSocket(socket, {
|
||||
secureContext: secureContext,
|
||||
isServer: true,
|
||||
});
|
||||
|
||||
tlsSocket.on('secure', () => {
|
||||
console.log('Connection secured.');
|
||||
this.emailBufferStringMap.set(tlsSocket, this.emailBufferStringMap.get(socket) || '');
|
||||
this.emailBufferStringMap.delete(socket);
|
||||
});
|
||||
|
||||
// Use the same handler for the 'data' event as for the unsecured socket.
|
||||
tlsSocket.on('data', (data: Buffer) => {
|
||||
this.processData(tlsSocket, Buffer.from(data));
|
||||
});
|
||||
|
||||
tlsSocket.on('end', () => {
|
||||
console.log('TLS socket closed. Deleting related emailBuffer...');
|
||||
this.emailBufferStringMap.delete(tlsSocket);
|
||||
});
|
||||
|
||||
tlsSocket.on('error', (err) => {
|
||||
console.error('TLS socket error occurred. Deleting related emailBuffer...');
|
||||
this.emailBufferStringMap.delete(tlsSocket);
|
||||
});
|
||||
}
|
||||
|
||||
private processData(socket: plugins.net.Socket | plugins.tls.TLSSocket, data: Buffer) {
|
||||
const dataString = data.toString();
|
||||
console.log(`Received data:`);
|
||||
console.log(`${dataString}`)
|
||||
|
||||
if (dataString.startsWith('EHLO')) {
|
||||
socket.write('250-mta.lossless.one Hello\r\n250 STARTTLS\r\n');
|
||||
} else if (dataString.startsWith('MAIL FROM')) {
|
||||
socket.write('250 Ok\r\n');
|
||||
} else if (dataString.startsWith('RCPT TO')) {
|
||||
socket.write('250 Ok\r\n');
|
||||
} else if (dataString.startsWith('STARTTLS')) {
|
||||
socket.write('220 Ready to start TLS\r\n');
|
||||
this.startTLS(socket);
|
||||
} else if (dataString.startsWith('DATA')) {
|
||||
socket.write('354 End data with <CR><LF>.<CR><LF>\r\n');
|
||||
let emailBuffer = this.emailBufferStringMap.get(socket);
|
||||
if (!emailBuffer) {
|
||||
this.emailBufferStringMap.set(socket, '');
|
||||
}
|
||||
} else if (dataString.startsWith('QUIT')) {
|
||||
socket.write('221 Bye\r\n');
|
||||
console.log('Received QUIT command, closing the socket...');
|
||||
socket.destroy();
|
||||
this.parseEmail(socket);
|
||||
} else {
|
||||
let emailBuffer = this.emailBufferStringMap.get(socket);
|
||||
if (typeof emailBuffer === 'string') {
|
||||
emailBuffer += dataString;
|
||||
this.emailBufferStringMap.set(socket, emailBuffer);
|
||||
}
|
||||
socket.write('250 Ok\r\n');
|
||||
}
|
||||
|
||||
if (dataString.endsWith('\r\n.\r\n') ) { // End of data
|
||||
console.log('Received end of data.');
|
||||
}
|
||||
}
|
||||
|
||||
private async parseEmail(socket: plugins.net.Socket | plugins.tls.TLSSocket) {
|
||||
let emailData = this.emailBufferStringMap.get(socket);
|
||||
// lets strip the end sequence
|
||||
emailData = emailData?.replace(/\r\n\.\r\n$/, '');
|
||||
|
||||
plugins.smartfile.fs.ensureDirSync(paths.receivedEmailsDir);
|
||||
plugins.smartfile.memory.toFsSync(emailData, plugins.path.join(paths.receivedEmailsDir, `${Date.now()}.eml`));
|
||||
|
||||
|
||||
if (!emailData) {
|
||||
console.error('No email data found for socket.');
|
||||
return;
|
||||
}
|
||||
|
||||
let mightBeSpam = false;
|
||||
|
||||
// Verifying the email with DKIM
|
||||
try {
|
||||
const isVerified = await this.mtaRef.dkimVerifier.verify(emailData);
|
||||
mightBeSpam = !isVerified;
|
||||
} catch (error) {
|
||||
console.error('Failed to verify DKIM signature:', error);
|
||||
mightBeSpam = true;
|
||||
}
|
||||
|
||||
const parsedEmail = await plugins.mailparser.simpleParser(emailData);
|
||||
console.log(parsedEmail)
|
||||
const email = new Email({
|
||||
from: parsedEmail.from?.value[0].address || '',
|
||||
to:
|
||||
parsedEmail.to instanceof Array
|
||||
? parsedEmail.to[0].value[0].address
|
||||
: parsedEmail.to?.value[0].address,
|
||||
subject: parsedEmail.subject || '',
|
||||
text: parsedEmail.html || parsedEmail.text,
|
||||
attachments:
|
||||
parsedEmail.attachments?.map((attachment) => ({
|
||||
filename: attachment.filename || '',
|
||||
content: attachment.content,
|
||||
contentType: attachment.contentType,
|
||||
})) || [],
|
||||
mightBeSpam: mightBeSpam,
|
||||
});
|
||||
|
||||
console.log('mail received!');
|
||||
console.log(email);
|
||||
|
||||
this.emailBufferStringMap.delete(socket);
|
||||
}
|
||||
|
||||
public start() {
|
||||
this.server.listen(this.smtpServerOptions.port, () => {
|
||||
console.log(`SMTP Server is now running on port ${this.smtpServerOptions.port}`);
|
||||
});
|
||||
}
|
||||
|
||||
public stop() {
|
||||
this.server.getConnections((err, count) => {
|
||||
if (err) throw err;
|
||||
console.log('Number of active connections: ', count);
|
||||
});
|
||||
this.server.close(() => {
|
||||
console.log('SMTP Server is now stopped');
|
||||
});
|
||||
}
|
||||
}
|
12
ts/mta/paths.ts
Normal file
12
ts/mta/paths.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
export const cwd = plugins.path.join(
|
||||
plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
|
||||
'../'
|
||||
);
|
||||
export const assetsDir = plugins.path.join(cwd, './assets');
|
||||
export const keysDir = plugins.path.join(assetsDir, './keys');
|
||||
export const dnsRecordsDir = plugins.path.join(assetsDir, './dns-records');
|
||||
export const sentEmailsDir = plugins.path.join(assetsDir, './sent-emails');
|
||||
export const receivedEmailsDir = plugins.path.join(assetsDir, './received-emails');
|
||||
plugins.smartfile.fs.ensureDirSync(keysDir);
|
67
ts/mta/plugins.ts
Normal file
67
ts/mta/plugins.ts
Normal file
@ -0,0 +1,67 @@
|
||||
// node native
|
||||
import * as dns from 'dns';
|
||||
import * as fs from 'fs';
|
||||
import * as crypto from 'crypto';
|
||||
import * as net from 'net';
|
||||
import * as path from 'path';
|
||||
import * as tls from 'tls';
|
||||
import * as util from 'util';
|
||||
|
||||
export {
|
||||
dns,
|
||||
fs,
|
||||
crypto,
|
||||
net,
|
||||
path,
|
||||
tls,
|
||||
util,
|
||||
}
|
||||
|
||||
// @apiclient.xyz/cloudflare
|
||||
import * as cloudflare from '@apiclient.xyz/cloudflare';
|
||||
|
||||
export {
|
||||
cloudflare,
|
||||
}
|
||||
|
||||
// @apiglobal scope
|
||||
import * as typedrequest from '@apiglobal/typedrequest';
|
||||
import * as typedsocket from '@apiglobal/typedsocket';
|
||||
|
||||
export {
|
||||
typedrequest,
|
||||
typedsocket,
|
||||
}
|
||||
|
||||
// pushrocks scope
|
||||
import * as smartfile from '@pushrocks/smartfile';
|
||||
import * as smartpath from '@pushrocks/smartpath';
|
||||
import * as smartpromise from '@pushrocks/smartpromise';
|
||||
import * as smartrx from '@pushrocks/smartrx';
|
||||
|
||||
export {
|
||||
smartfile,
|
||||
smartpath,
|
||||
smartpromise,
|
||||
smartrx,
|
||||
}
|
||||
|
||||
// tsclass scope
|
||||
import * as tsclass from '@tsclass/tsclass';
|
||||
|
||||
export {
|
||||
tsclass,
|
||||
}
|
||||
|
||||
// third party
|
||||
import * as mailauth from 'mailauth';
|
||||
import { dkimSign } from 'mailauth/lib/dkim/sign.js';
|
||||
import mailparser from 'mailparser';
|
||||
import * as uuid from 'uuid';
|
||||
|
||||
export {
|
||||
mailauth,
|
||||
dkimSign,
|
||||
mailparser,
|
||||
uuid,
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import * as plugins from './email.plugins.js';
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
export const packageDir = plugins.path.join(
|
||||
plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
|
@ -1,6 +0,0 @@
|
||||
import * as plugins from './platformservice.plugins.js';
|
||||
|
||||
export const packageDir = plugins.path.join(
|
||||
plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
|
||||
'../'
|
||||
);
|
@ -1,24 +0,0 @@
|
||||
// node native
|
||||
import * as path from 'path';
|
||||
|
||||
export {
|
||||
path
|
||||
}
|
||||
|
||||
// @api.global scope
|
||||
import * as typedrequest from '@api.global/typedrequest';
|
||||
import * as typedserver from '@api.global/typedserver';
|
||||
|
||||
export {
|
||||
typedrequest,
|
||||
typedserver,
|
||||
}
|
||||
|
||||
// pushrocks scope
|
||||
// pushrocks scope
|
||||
import * as projectinfo from '@push.rocks/projectinfo';
|
||||
import * as qenv from '@push.rocks/qenv';
|
||||
import * as smartdata from '@push.rocks/smartdata';
|
||||
import * as smartpath from '@push.rocks/smartpath';
|
||||
|
||||
export { projectinfo, qenv, smartdata, smartpath };
|
34
ts/plugins.ts
Normal file
34
ts/plugins.ts
Normal file
@ -0,0 +1,34 @@
|
||||
// node native
|
||||
import * as path from 'path';
|
||||
|
||||
export {
|
||||
path
|
||||
}
|
||||
|
||||
// @serve.zone scope
|
||||
import * as servezoneInterfaces from '@serve.zone/interfaces';
|
||||
|
||||
export {
|
||||
servezoneInterfaces
|
||||
}
|
||||
|
||||
// @api.global scope
|
||||
import * as typedrequest from '@api.global/typedrequest';
|
||||
import * as typedserver from '@api.global/typedserver';
|
||||
|
||||
export {
|
||||
typedrequest,
|
||||
typedserver,
|
||||
}
|
||||
|
||||
// @push.rocks scope
|
||||
import * as projectinfo from '@push.rocks/projectinfo';
|
||||
import * as qenv from '@push.rocks/qenv';
|
||||
import * as smartdata from '@push.rocks/smartdata';
|
||||
import * as smartfile from '@push.rocks/smartfile';
|
||||
import * as smartlog from '@push.rocks/smartlog';
|
||||
import * as smartmail from '@push.rocks/smartmail';
|
||||
import * as smartpath from '@push.rocks/smartpath';
|
||||
import * as smartrequest from '@push.rocks/smartrequest';
|
||||
|
||||
export { projectinfo, qenv, smartdata, smartfile, smartlog, smartmail, smartpath, smartrequest };
|
1
ts/sms/index.ts
Normal file
1
ts/sms/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './smsservice.js';
|
89
ts/sms/smsservice.ts
Normal file
89
ts/sms/smsservice.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as paths from '../paths.js';
|
||||
import { logger } from '../logger.js';
|
||||
import type { SzPlatformService } from '../classes.platformservice.js';
|
||||
|
||||
export interface ISmsConstructorOptions {
|
||||
apiToken: string;
|
||||
}
|
||||
|
||||
export class SmsService {
|
||||
public platformServiceRef: SzPlatformService;
|
||||
public projectinfo: plugins.projectinfo.ProjectInfo;
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
public options: ISmsConstructorOptions;
|
||||
|
||||
constructor(platformServiceRefArg: SzPlatformService, optionsArg: ISmsConstructorOptions) {
|
||||
this.platformServiceRef = platformServiceRefArg;
|
||||
this.options = optionsArg;
|
||||
this.platformServiceRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||
}
|
||||
|
||||
/**
|
||||
* starts the financeflow instance
|
||||
*/
|
||||
public async start() {
|
||||
logger.log('info', `starting sms service`);
|
||||
this.projectinfo = new plugins.projectinfo.ProjectInfo(paths.packageDir);
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.platformservice.sms.IRequest_SendSms>(
|
||||
'sendSms',
|
||||
async (reqData) => {
|
||||
await this.sendSms(reqData.toNumber, reqData.fromName, reqData.messageText);
|
||||
return {
|
||||
status: 'ok',
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.platformservice.sms.IRequest_SendVerificationCode>(
|
||||
'sendVerificationCode',
|
||||
async (reqData) => {
|
||||
const verificationCode = (
|
||||
await this.sendVerificationCode(reqData.toNumber, reqData.fromName)
|
||||
).toString();
|
||||
return {
|
||||
status: 'ok',
|
||||
verificationCode,
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public async sendSms(toNumber: number, fromName: string, messageText: string) {
|
||||
const payload = {
|
||||
sender: fromName,
|
||||
message: messageText,
|
||||
recipients: [{ msisdn: toNumber }],
|
||||
};
|
||||
|
||||
const resp = await plugins.smartrequest.request('https://gatewayapi.com/rest/mtsms', {
|
||||
method: 'POST',
|
||||
requestBody: JSON.stringify(payload),
|
||||
headers: {
|
||||
Authorization: `Basic ${Buffer.from(`${this.options.apiToken}:`).toString('base64')}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const json = await resp.body;
|
||||
logger.log('info', `sent an sms to ${toNumber} with text '${messageText}'`, {
|
||||
eventType: 'sentSms',
|
||||
sms: {
|
||||
fromName: fromName,
|
||||
toNumber: toNumber.toString(),
|
||||
messageText: messageText,
|
||||
},
|
||||
});
|
||||
console.log(JSON.stringify(json, null, 2));
|
||||
}
|
||||
|
||||
public async sendVerificationCode(toNumber: number, fromName: string) {
|
||||
let verificationCode = Math.floor(100000 + Math.random() * 900000);
|
||||
await this.sendSms(toNumber, fromName, `Your verification code: ${verificationCode}`);
|
||||
return verificationCode;
|
||||
}
|
||||
|
||||
public async stop() {}
|
||||
}
|
Loading…
Reference in New Issue
Block a user