From f5a36ab53ab32489dd3a64c6669ed8bf004bbb21 Mon Sep 17 00:00:00 2001 From: Philipp Kunz Date: Fri, 16 Feb 2024 13:28:40 +0100 Subject: [PATCH] fix(core): update --- package.json | 7 +- pnpm-lock.yaml | 93 ++++++++- ts/00_commitinfo_data.ts | 2 +- ts/classes.platformservice.ts | 4 +- ts/classes.platformservicedb.ts | 2 +- ts/email/email.classes.apimanager.ts | 17 +- ts/email/email.classes.connector.mailgun.ts | 6 +- ...email.ts => email.classes.emailservice.ts} | 13 +- ts/email/email.classes.rulemanager.ts | 6 +- ts/email/email.logging.ts | 13 -- ts/email/email.plugins.ts | 43 ---- ts/email/index.ts | 4 +- ts/index.ts | 2 +- ts/logger.ts | 9 + ts/mta/index.ts | 8 + ts/mta/mta.classes.apimanager.ts | 7 + ts/mta/mta.classes.dkimcreator.ts | 119 +++++++++++ ts/mta/mta.classes.dkimverifier.ts | 35 ++++ ts/mta/mta.classes.dnsmanager.ts | 10 + ts/mta/mta.classes.email.ts | 36 ++++ ts/mta/mta.classes.emailsendjob.ts | 173 ++++++++++++++++ ts/mta/mta.classes.emailsignjob.ts | 69 +++++++ ts/mta/mta.classes.mta.ts | 66 ++++++ ts/mta/mta.classes.smtpserver.ts | 191 ++++++++++++++++++ ts/mta/paths.ts | 12 ++ ts/mta/plugins.ts | 67 ++++++ ts/{email/email.paths.ts => paths.ts} | 2 +- ts/platformservice.paths.ts | 6 - ts/platformservice.plugins.ts | 24 --- ts/plugins.ts | 34 ++++ ts/sms/index.ts | 1 + ts/sms/smsservice.ts | 89 ++++++++ 32 files changed, 1051 insertions(+), 119 deletions(-) rename ts/email/{email.classes.email.ts => email.classes.emailservice.ts} (76%) delete mode 100644 ts/email/email.logging.ts delete mode 100644 ts/email/email.plugins.ts create mode 100644 ts/logger.ts create mode 100644 ts/mta/index.ts create mode 100644 ts/mta/mta.classes.apimanager.ts create mode 100644 ts/mta/mta.classes.dkimcreator.ts create mode 100644 ts/mta/mta.classes.dkimverifier.ts create mode 100644 ts/mta/mta.classes.dnsmanager.ts create mode 100644 ts/mta/mta.classes.email.ts create mode 100644 ts/mta/mta.classes.emailsendjob.ts create mode 100644 ts/mta/mta.classes.emailsignjob.ts create mode 100644 ts/mta/mta.classes.mta.ts create mode 100644 ts/mta/mta.classes.smtpserver.ts create mode 100644 ts/mta/paths.ts create mode 100644 ts/mta/plugins.ts rename ts/{email/email.paths.ts => paths.ts} (72%) delete mode 100644 ts/platformservice.paths.ts delete mode 100644 ts/platformservice.plugins.ts create mode 100644 ts/plugins.ts create mode 100644 ts/sms/index.ts create mode 100644 ts/sms/smsservice.ts diff --git a/package.json b/package.json index 3024f5f..59d1952 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 01e4811..fc1f46d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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'} diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 187f128..a23af5e 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -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.' } diff --git a/ts/classes.platformservice.ts b/ts/classes.platformservice.ts index 3842e83..8343bb7 100644 --- a/ts/classes.platformservice.ts +++ b/ts/classes.platformservice.ts @@ -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 { diff --git a/ts/classes.platformservicedb.ts b/ts/classes.platformservicedb.ts index 35e20a7..4669b8a 100644 --- a/ts/classes.platformservicedb.ts +++ b/ts/classes.platformservicedb.ts @@ -1,4 +1,4 @@ -import * as plugins from './platformservice.plugins.js'; +import * as plugins from './plugins.js'; import { SzPlatformService } from './classes.platformservice.js'; diff --git a/ts/email/email.classes.apimanager.ts b/ts/email/email.classes.apimanager.ts index cad9fd0..e10de35 100644 --- a/ts/email/email.classes.apimanager.ts +++ b/ts/email/email.classes.apimanager.ts @@ -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( + this.emailRef.typedrouter.addTypedRouter(this.typedRouter); + this.typedRouter.addTypedHandler( 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' diff --git a/ts/email/email.classes.connector.mailgun.ts b/ts/email/email.classes.connector.mailgun.ts index 6dd32ac..89f0dc0 100644 --- a/ts/email/email.classes.connector.mailgun.ts +++ b/ts/email/email.classes.connector.mailgun.ts @@ -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'), diff --git a/ts/email/email.classes.email.ts b/ts/email/email.classes.emailservice.ts similarity index 76% rename from ts/email/email.classes.email.ts rename to ts/email/email.classes.emailservice.ts index 34adab9..541dcf5 100644 --- a/ts/email/email.classes.email.ts +++ b/ts/email/email.classes.emailservice.ts @@ -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(); diff --git a/ts/email/email.classes.rulemanager.ts b/ts/email/email.classes.rulemanager.ts index e7eb04a..5318eb6 100644 --- a/ts/email/email.classes.rulemanager.ts +++ b/ts/email/email.classes.rulemanager.ts @@ -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 >(); - constructor(emailRefArg: Email) { + constructor(emailRefArg: EmailService) { this.emailRef = emailRefArg; } diff --git a/ts/email/email.logging.ts b/ts/email/email.logging.ts deleted file mode 100644 index 185e956..0000000 --- a/ts/email/email.logging.ts +++ /dev/null @@ -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', -}); diff --git a/ts/email/email.plugins.ts b/ts/email/email.plugins.ts deleted file mode 100644 index c0088d5..0000000 --- a/ts/email/email.plugins.ts +++ /dev/null @@ -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, -}; diff --git a/ts/email/index.ts b/ts/email/index.ts index 6bc7ccc..7f9abc5 100644 --- a/ts/email/index.ts +++ b/ts/email/index.ts @@ -1,3 +1,3 @@ -import { Email } from './email.classes.email.js'; +import { EmailService } from './email.classes.emailservice.js'; -export { Email }; \ No newline at end of file +export { EmailService as Email }; \ No newline at end of file diff --git a/ts/index.ts b/ts/index.ts index ec989e7..84534ef 100644 --- a/ts/index.ts +++ b/ts/index.ts @@ -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 () => {} \ No newline at end of file diff --git a/ts/logger.ts b/ts/logger.ts new file mode 100644 index 0000000..8ce7308 --- /dev/null +++ b/ts/logger.ts @@ -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', + } +}); diff --git a/ts/mta/index.ts b/ts/mta/index.ts new file mode 100644 index 0000000..d4bf289 --- /dev/null +++ b/ts/mta/index.ts @@ -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'; diff --git a/ts/mta/mta.classes.apimanager.ts b/ts/mta/mta.classes.apimanager.ts new file mode 100644 index 0000000..b2ac0c6 --- /dev/null +++ b/ts/mta/mta.classes.apimanager.ts @@ -0,0 +1,7 @@ +import * as plugins from './plugins.js'; + +export class ApiManager { + public typedrouter = new plugins.typedrequest.TypedRouter(); + + +} diff --git a/ts/mta/mta.classes.dkimcreator.ts b/ts/mta/mta.classes.dkimcreator.ts new file mode 100644 index 0000000..f690595 --- /dev/null +++ b/ts/mta/mta.classes.dkimcreator.ts @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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, + }; + } +} diff --git a/ts/mta/mta.classes.dkimverifier.ts b/ts/mta/mta.classes.dkimverifier.ts new file mode 100644 index 0000000..237510f --- /dev/null +++ b/ts/mta/mta.classes.dkimverifier.ts @@ -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 { + 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 }; \ No newline at end of file diff --git a/ts/mta/mta.classes.dnsmanager.ts b/ts/mta/mta.classes.dnsmanager.ts new file mode 100644 index 0000000..f543fd7 --- /dev/null +++ b/ts/mta/mta.classes.dnsmanager.ts @@ -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; + } +} \ No newline at end of file diff --git a/ts/mta/mta.classes.email.ts b/ts/mta/mta.classes.email.ts new file mode 100644 index 0000000..38c932c --- /dev/null +++ b/ts/mta/mta.classes.email.ts @@ -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] + } +} diff --git a/ts/mta/mta.classes.emailsendjob.ts b/ts/mta/mta.classes.emailsendjob.ts new file mode 100644 index 0000000..2a5e059 --- /dev/null +++ b/ts/mta/mta.classes.emailsendjob.ts @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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!'); + } +} diff --git a/ts/mta/mta.classes.emailsignjob.ts b/ts/mta/mta.classes.emailsignjob.ts new file mode 100644 index 0000000..6b65131 --- /dev/null +++ b/ts/mta/mta.classes.emailsignjob.ts @@ -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 { + return plugins.fs.promises.readFile( + (await this.mtaRef.dkimCreator.getKeyPathsForDomain(this.jobOptions.domain)).privateKeyPath, + 'utf-8' + ); + } + + public async getSignatureHeader(emailMessage: string): Promise { + 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; + } +} diff --git a/ts/mta/mta.classes.mta.ts b/ts/mta/mta.classes.mta.ts new file mode 100644 index 0000000..7d3e2f0 --- /dev/null +++ b/ts/mta/mta.classes.mta.ts @@ -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('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 { + await this.dkimCreator.handleDKIMKeysForEmail(email); + const sendJob = new EmailSendJob(this, email); + await sendJob.send(); + } +} diff --git a/ts/mta/mta.classes.smtpserver.ts b/ts/mta/mta.classes.smtpserver.ts new file mode 100644 index 0000000..ba780e0 --- /dev/null +++ b/ts/mta/mta.classes.smtpserver.ts @@ -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; + + 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 .\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'); + }); + } +} diff --git a/ts/mta/paths.ts b/ts/mta/paths.ts new file mode 100644 index 0000000..fed1881 --- /dev/null +++ b/ts/mta/paths.ts @@ -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); diff --git a/ts/mta/plugins.ts b/ts/mta/plugins.ts new file mode 100644 index 0000000..45ded57 --- /dev/null +++ b/ts/mta/plugins.ts @@ -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, +} diff --git a/ts/email/email.paths.ts b/ts/paths.ts similarity index 72% rename from ts/email/email.paths.ts rename to ts/paths.ts index da8d4d9..1f23b1d 100644 --- a/ts/email/email.paths.ts +++ b/ts/paths.ts @@ -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), diff --git a/ts/platformservice.paths.ts b/ts/platformservice.paths.ts deleted file mode 100644 index d2ded2c..0000000 --- a/ts/platformservice.paths.ts +++ /dev/null @@ -1,6 +0,0 @@ -import * as plugins from './platformservice.plugins.js'; - -export const packageDir = plugins.path.join( - plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url), - '../' -); diff --git a/ts/platformservice.plugins.ts b/ts/platformservice.plugins.ts deleted file mode 100644 index 2762784..0000000 --- a/ts/platformservice.plugins.ts +++ /dev/null @@ -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 }; \ No newline at end of file diff --git a/ts/plugins.ts b/ts/plugins.ts new file mode 100644 index 0000000..f0032c9 --- /dev/null +++ b/ts/plugins.ts @@ -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 }; \ No newline at end of file diff --git a/ts/sms/index.ts b/ts/sms/index.ts new file mode 100644 index 0000000..de94b2b --- /dev/null +++ b/ts/sms/index.ts @@ -0,0 +1 @@ +export * from './smsservice.js'; \ No newline at end of file diff --git a/ts/sms/smsservice.ts b/ts/sms/smsservice.ts new file mode 100644 index 0000000..c3c7043 --- /dev/null +++ b/ts/sms/smsservice.ts @@ -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( + 'sendSms', + async (reqData) => { + await this.sendSms(reqData.toNumber, reqData.fromName, reqData.messageText); + return { + status: 'ok', + }; + } + ) + ); + this.typedrouter.addTypedHandler( + new plugins.typedrequest.TypedHandler( + '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() {} +}