mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-23 18:15:03 +02:00
UBERF-13120: Support SMTP mail for different accounts (#9674)
* UBERF-13120: Support SMTP mail for different accounts Signed-off-by: Artem Savchenko <armisav@gmail.com> * Potential fix for code scanning alert no. 219: Use of password hash with insufficient computational effort Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Artyom Savchenko <armisav@gmail.com> * Update server/account/src/operations.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Artyom Savchenko <armisav@gmail.com> * UBERF-13120: Minor fixes Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-13120: Fix get secret Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-13120: Fix lru cache version Signed-off-by: Artem Savchenko <armisav@gmail.com> * UBERF-13120: Fix pod-mail Signed-off-by: Artem Savchenko <armisav@gmail.com> --------- Signed-off-by: Artem Savchenko <armisav@gmail.com> Signed-off-by: Artyom Savchenko <armisav@gmail.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Copilot
Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
parent
c4f9953d7d
commit
1316a8475a
Generated
+39
-2
@@ -1612,6 +1612,9 @@ importers:
|
||||
'@types/archiver':
|
||||
specifier: ^6.0.3
|
||||
version: 6.0.3
|
||||
'@types/bcrypt':
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0
|
||||
'@types/body-parser':
|
||||
specifier: ~1.19.2
|
||||
version: 1.19.5
|
||||
@@ -1783,6 +1786,9 @@ importers:
|
||||
base64-js:
|
||||
specifier: ^1.5.1
|
||||
version: 1.5.1
|
||||
bcrypt:
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0
|
||||
big-integer:
|
||||
specifier: ^1.6.51
|
||||
version: 1.6.52
|
||||
@@ -4906,7 +4912,7 @@ packages:
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/model-card@file:projects/model-card.tgz':
|
||||
resolution: {integrity: sha512-SsrZ+0AVeICDFwFdWZt9Zwr8b8/EFjnJcJcqkUKVqqvw7EaABvHV0Gl0/DfvV0xxnxoeJjO+2z/I/QoTB47g7w==, tarball: file:projects/model-card.tgz}
|
||||
resolution: {integrity: sha512-0Gm7Yjag3m6EvKtCWSke4phJ5+N8YcudQLvrphiDWlgxEoVl7FsDq4lVNKX5CZjPcldm79usxWrCJ8+GaAgDuQ==, tarball: file:projects/model-card.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/model-chat@file:projects/model-chat.tgz':
|
||||
@@ -5346,7 +5352,7 @@ packages:
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/pod-mail@file:projects/pod-mail.tgz':
|
||||
resolution: {integrity: sha512-WhDC0I0bu1HeY3HN/yz6xQHsanXYf0ZAWEToJuKb3IAuLTsq1k/um2url73TyBYmWT+XbzKDRMh+AWM5xkav+w==, tarball: file:projects/pod-mail.tgz}
|
||||
resolution: {integrity: sha512-0kGmfCDk+EBVRFn28ht0cb+WUUF2sl0BkednIhT99vM5q/g5hfTZeIn5OLBFhqwJyHkj99xA+5eha1nMgH81UA==, tarball: file:projects/pod-mail.tgz}
|
||||
version: 0.0.0
|
||||
|
||||
'@rush-temp/pod-media@file:projects/pod-media.tgz':
|
||||
@@ -6658,6 +6664,9 @@ packages:
|
||||
'@types/babel__traverse@7.20.5':
|
||||
resolution: {integrity: sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==}
|
||||
|
||||
'@types/bcrypt@6.0.0':
|
||||
resolution: {integrity: sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ==}
|
||||
|
||||
'@types/body-parser@1.19.5':
|
||||
resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==}
|
||||
|
||||
@@ -7730,6 +7739,10 @@ packages:
|
||||
batch@0.6.1:
|
||||
resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==}
|
||||
|
||||
bcrypt@6.0.0:
|
||||
resolution: {integrity: sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
before-after-hook@2.2.3:
|
||||
resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==}
|
||||
|
||||
@@ -11583,6 +11596,10 @@ packages:
|
||||
node-addon-api@6.1.0:
|
||||
resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==}
|
||||
|
||||
node-addon-api@8.5.0:
|
||||
resolution: {integrity: sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==}
|
||||
engines: {node: ^18 || ^20 || >= 21}
|
||||
|
||||
node-api-version@0.2.0:
|
||||
resolution: {integrity: sha512-fthTTsi8CxaBXMaBAD7ST2uylwvsnYxh2PfaScwpMhos6KlSFajXQPcM4ogNE1q2s3Lbz9GCGqeIHC+C6OZnKg==}
|
||||
|
||||
@@ -11620,6 +11637,10 @@ packages:
|
||||
resolution: {integrity: sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==}
|
||||
hasBin: true
|
||||
|
||||
node-gyp-build@4.8.4:
|
||||
resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
|
||||
hasBin: true
|
||||
|
||||
node-gyp@9.4.1:
|
||||
resolution: {integrity: sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==}
|
||||
engines: {node: ^12.13 || ^14.13 || >=16}
|
||||
@@ -24016,6 +24037,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@aws-sdk/client-ses': 3.738.0
|
||||
'@tsconfig/node16': 1.0.4
|
||||
'@types/bcrypt': 6.0.0
|
||||
'@types/cors': 2.8.17
|
||||
'@types/express': 4.17.21
|
||||
'@types/jest': 29.5.12
|
||||
@@ -24023,6 +24045,7 @@ snapshots:
|
||||
'@types/nodemailer': 6.4.17
|
||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.8.3))(eslint@8.56.0)(typescript@5.8.3)
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.8.3)
|
||||
bcrypt: 6.0.0
|
||||
cors: 2.8.5
|
||||
cross-env: 7.0.3
|
||||
dotenv: 16.0.3
|
||||
@@ -24035,6 +24058,7 @@ snapshots:
|
||||
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
|
||||
express: 4.21.2
|
||||
jest: 29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.8.3))
|
||||
lru-cache: 11.1.0
|
||||
nodemailer: 6.10.0
|
||||
prettier: 3.2.5
|
||||
ts-jest: 29.1.2(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.24.2)(jest@29.7.0(@types/node@22.15.29)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.8.3)))(typescript@5.8.3)
|
||||
@@ -30196,6 +30220,10 @@ snapshots:
|
||||
dependencies:
|
||||
'@babel/types': 7.23.9
|
||||
|
||||
'@types/bcrypt@6.0.0':
|
||||
dependencies:
|
||||
'@types/node': 22.15.29
|
||||
|
||||
'@types/body-parser@1.19.5':
|
||||
dependencies:
|
||||
'@types/connect': 3.4.38
|
||||
@@ -31546,6 +31574,11 @@ snapshots:
|
||||
|
||||
batch@0.6.1: {}
|
||||
|
||||
bcrypt@6.0.0:
|
||||
dependencies:
|
||||
node-addon-api: 8.5.0
|
||||
node-gyp-build: 4.8.4
|
||||
|
||||
before-after-hook@2.2.3: {}
|
||||
|
||||
big-integer@1.6.52: {}
|
||||
@@ -36172,6 +36205,8 @@ snapshots:
|
||||
|
||||
node-addon-api@6.1.0: {}
|
||||
|
||||
node-addon-api@8.5.0: {}
|
||||
|
||||
node-api-version@0.2.0:
|
||||
dependencies:
|
||||
semver: 7.6.3
|
||||
@@ -36199,6 +36234,8 @@ snapshots:
|
||||
|
||||
node-gyp-build@4.8.0: {}
|
||||
|
||||
node-gyp-build@4.8.4: {}
|
||||
|
||||
node-gyp@9.4.1:
|
||||
dependencies:
|
||||
env-paths: 2.2.1
|
||||
|
||||
@@ -42,6 +42,7 @@ import type {
|
||||
LoginInfoWithWorkspaces,
|
||||
MailboxInfo,
|
||||
MailboxOptions,
|
||||
MailboxSecret,
|
||||
OtpInfo,
|
||||
ProviderInfo,
|
||||
RegionInfo,
|
||||
@@ -126,6 +127,7 @@ export interface AccountClient {
|
||||
findFullSocialIdBySocialKey: (socialKey: string) => Promise<SocialId | undefined>
|
||||
findFullSocialIds: (socialIds: PersonId[]) => Promise<SocialId[]>
|
||||
getMailboxOptions: () => Promise<MailboxOptions>
|
||||
getMailboxSecret: (mailbox: string) => Promise<MailboxSecret | undefined>
|
||||
createMailbox: (name: string, domain: string) => Promise<{ mailbox: string, socialId: PersonId }>
|
||||
getMailboxes: () => Promise<MailboxInfo[]>
|
||||
deleteMailbox: (mailbox: string) => Promise<void>
|
||||
@@ -853,6 +855,15 @@ class AccountClientImpl implements AccountClient {
|
||||
return await this.rpc(request)
|
||||
}
|
||||
|
||||
async getMailboxSecret (mailbox: string): Promise<MailboxSecret | undefined> {
|
||||
const request = {
|
||||
method: 'getMailboxSecret' as const,
|
||||
params: { mailbox }
|
||||
}
|
||||
|
||||
return await this.rpc(request)
|
||||
}
|
||||
|
||||
async createMailbox (name: string, domain: string): Promise<{ mailbox: string, socialId: PersonId }> {
|
||||
const request = {
|
||||
method: 'createMailbox' as const,
|
||||
|
||||
@@ -91,6 +91,12 @@ export interface MailboxInfo {
|
||||
appPasswords: string[]
|
||||
}
|
||||
|
||||
export interface MailboxSecret {
|
||||
mailbox: string
|
||||
app?: string
|
||||
secret: string
|
||||
}
|
||||
|
||||
export interface Integration {
|
||||
socialId: PersonId
|
||||
kind: IntegrationKind // Integration kind. E.g. 'github', 'mail', 'telegram-bot', 'telegram' etc.
|
||||
|
||||
@@ -42,6 +42,7 @@ import { accountPlugin } from './plugin'
|
||||
import { type AccountServiceMethods, getServiceMethods } from './serviceOperations'
|
||||
import {
|
||||
AccountEventType,
|
||||
type MailboxSecret,
|
||||
type AccountDB,
|
||||
type AccountMethodHandler,
|
||||
type LoginInfo,
|
||||
@@ -1973,6 +1974,20 @@ async function getMailboxes (
|
||||
return await db.mailbox.find({ accountUuid: account })
|
||||
}
|
||||
|
||||
async function getMailboxSecret (
|
||||
ctx: MeasureContext,
|
||||
db: AccountDB,
|
||||
branding: Branding | null,
|
||||
token: string,
|
||||
params: {
|
||||
mailbox: string
|
||||
}
|
||||
): Promise<MailboxSecret | null> {
|
||||
const { extra } = decodeTokenVerbose(ctx, token)
|
||||
verifyAllowedServices(['huly-mail'], extra, false)
|
||||
return await db.mailboxSecret.findOne({ mailbox: params.mailbox })
|
||||
}
|
||||
|
||||
async function deleteMailbox (
|
||||
ctx: MeasureContext,
|
||||
db: AccountDB,
|
||||
@@ -2205,6 +2220,7 @@ export type AccountMethods =
|
||||
| 'getMailboxOptions'
|
||||
| 'createMailbox'
|
||||
| 'getMailboxes'
|
||||
| 'getMailboxSecret'
|
||||
| 'deleteMailbox'
|
||||
| 'getAccountInfo'
|
||||
| 'isReadOnlyGuest'
|
||||
@@ -2268,6 +2284,7 @@ export function getMethods (hasSignUp: boolean = true): Partial<Record<AccountMe
|
||||
findSocialIdBySocialKey: wrap(findSocialIdBySocialKey),
|
||||
getWorkspaceMembers: wrap(getWorkspaceMembers),
|
||||
getMailboxOptions: wrap(getMailboxOptions),
|
||||
getMailboxSecret: wrap(getMailboxSecret),
|
||||
getAccountInfo: wrap(getAccountInfo),
|
||||
isReadOnlyGuest: wrap(isReadOnlyGuest),
|
||||
|
||||
|
||||
@@ -244,14 +244,25 @@ export class MailWorker {
|
||||
(address) => address != null && address !== ''
|
||||
) as string[]
|
||||
|
||||
await sendEmail(this.ctx, {
|
||||
from: emailSocialId.value,
|
||||
to,
|
||||
subject,
|
||||
html,
|
||||
text,
|
||||
headers: getMailHeaders(HulyMessageType, message._id)
|
||||
})
|
||||
const email = emailSocialId.value
|
||||
const secret = (await this.accountClient.getMailboxSecret(email))?.secret
|
||||
if (secret === undefined) {
|
||||
this.ctx.error('Mailbox secret not found for email', { email })
|
||||
return
|
||||
}
|
||||
|
||||
await sendEmail(
|
||||
this.ctx,
|
||||
{
|
||||
from: email,
|
||||
to,
|
||||
subject,
|
||||
html,
|
||||
text,
|
||||
headers: getMailHeaders(HulyMessageType, message._id)
|
||||
},
|
||||
secret
|
||||
)
|
||||
} catch (err: any) {
|
||||
this.ctx.error('Failed to send message as email', {
|
||||
messageId: message.messageId,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { concatLink, MeasureContext } from '@hcengineering/core'
|
||||
import config from './config'
|
||||
import { MailMessage } from './types'
|
||||
|
||||
export async function sendEmail (ctx: MeasureContext, message: MailMessage): Promise<void> {
|
||||
export async function sendEmail (ctx: MeasureContext, message: MailMessage, secret: string): Promise<void> {
|
||||
const mailURL = config.mailUrl
|
||||
if (mailURL === undefined || mailURL === '') {
|
||||
ctx.error('Please provide email service url to enable email sending')
|
||||
@@ -19,6 +19,7 @@ export async function sendEmail (ctx: MeasureContext, message: MailMessage): Pro
|
||||
},
|
||||
body: JSON.stringify({
|
||||
...message,
|
||||
password: secret,
|
||||
apiKey: mailAuth
|
||||
})
|
||||
})
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
"@tsconfig/node16": "^1.0.4",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/bcrypt": "^6.0.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
@@ -62,6 +63,8 @@
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "~16.0.0",
|
||||
"express": "^4.21.2",
|
||||
"nodemailer": "^6.10.0"
|
||||
"lru-cache": "^11.1.0",
|
||||
"nodemailer": "^6.10.0",
|
||||
"bcrypt": "^6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,8 @@ describe('handleSendMail', () => {
|
||||
subject: 'Test Subject',
|
||||
text: 'Hello, world!'
|
||||
}),
|
||||
mockCtx
|
||||
mockCtx,
|
||||
undefined
|
||||
)
|
||||
})
|
||||
|
||||
@@ -121,7 +122,8 @@ describe('handleSendMail', () => {
|
||||
subject: 'Test Subject',
|
||||
text: 'Hello, world!'
|
||||
}),
|
||||
mockCtx
|
||||
mockCtx,
|
||||
undefined
|
||||
)
|
||||
})
|
||||
|
||||
@@ -136,7 +138,25 @@ describe('handleSendMail', () => {
|
||||
subject: 'Test Subject',
|
||||
text: 'Hello, world!'
|
||||
}),
|
||||
mockCtx
|
||||
mockCtx,
|
||||
undefined
|
||||
)
|
||||
})
|
||||
|
||||
it('should send email with credentials', async () => {
|
||||
req.body.to = ['test1@example.com', 'test2@example.com']
|
||||
req.body.password = 'test-password'
|
||||
await handleSendMail(mailClient, req, res, mockCtx)
|
||||
|
||||
expect(sendMailMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
from: 'noreply@example.com',
|
||||
to: ['test1@example.com', 'test2@example.com'], // Verify that multiple addresses are passed
|
||||
subject: 'Test Subject',
|
||||
text: 'Hello, world!'
|
||||
}),
|
||||
mockCtx,
|
||||
'test-password'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -13,23 +13,43 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
import { type SendMailOptions, type Transporter } from 'nodemailer'
|
||||
import { LRUCache } from 'lru-cache'
|
||||
import * as bcrypt from 'bcrypt'
|
||||
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import { MeasureContext } from '@hcengineering/core'
|
||||
|
||||
import config from './config'
|
||||
import { getTransport } from './transport'
|
||||
import { getDefaultTransport, getSmtpTransport } from './transport'
|
||||
|
||||
export class MailClient {
|
||||
private readonly transporter: Transporter
|
||||
private readonly transporterCache: LRUCache<string, Transporter>
|
||||
private static readonly cacheSalt: string = bcrypt.genSaltSync(12)
|
||||
|
||||
constructor () {
|
||||
this.transporter = getTransport(config)
|
||||
this.transporter = getDefaultTransport(config)
|
||||
|
||||
// Configure LRU cache for transporters
|
||||
this.transporterCache = new LRUCache<string, Transporter>({
|
||||
max: 50, // Maximum number of cached transporters
|
||||
ttl: 30 * 60 * 1000, // 30 minutes TTL
|
||||
dispose: (transporter: Transporter) => {
|
||||
// Close transporter connection when evicted from cache
|
||||
if ('close' in transporter && typeof transporter.close === 'function') {
|
||||
transporter.close()
|
||||
}
|
||||
},
|
||||
updateAgeOnGet: true, // Reset TTL on cache hit
|
||||
updateAgeOnHas: false
|
||||
})
|
||||
}
|
||||
|
||||
async sendMessage (message: SendMailOptions, ctx: MeasureContext): Promise<void> {
|
||||
this.transporter.sendMail(message, (err, info) => {
|
||||
const messageInfo = `(from: ${message.from as string}, to: ${message.to as string})`
|
||||
async sendMessage (message: SendMailOptions, ctx: MeasureContext, password?: string | undefined): Promise<void> {
|
||||
const from = message.from as string
|
||||
const transporter = this.getTransporter(from, password)
|
||||
transporter.sendMail(message, (err, info) => {
|
||||
const messageInfo = `(from: ${from}, to: ${message.to as string})`
|
||||
if (err !== null) {
|
||||
ctx.error(`Failed to send email ${messageInfo}: ${err.message}`)
|
||||
Analytics.handleError(err)
|
||||
@@ -38,4 +58,47 @@ export class MailClient {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getTransporter (email: string, password?: string): Transporter {
|
||||
if (config.smtpConfig !== undefined && password != null && password !== '') {
|
||||
return this.getCachedTransporter(email, password)
|
||||
}
|
||||
return this.transporter
|
||||
}
|
||||
|
||||
private getCachedTransporter (email: string, password: string): Transporter {
|
||||
const cacheKey = this.generateCacheKey(email, password)
|
||||
|
||||
// Check if transporter exists in cache
|
||||
const cachedTransporter = this.transporterCache.get(cacheKey)
|
||||
if (cachedTransporter !== undefined) {
|
||||
return cachedTransporter
|
||||
}
|
||||
|
||||
// Create new transporter and cache it
|
||||
if (config.smtpConfig === undefined) {
|
||||
throw new Error('SMTP config is required for custom transporter')
|
||||
}
|
||||
const newTransporter = getSmtpTransport(config.smtpConfig, email, password)
|
||||
this.transporterCache.set(cacheKey, newTransporter)
|
||||
|
||||
return newTransporter
|
||||
}
|
||||
|
||||
private generateCacheKey (email: string, password: string): string {
|
||||
const passwordHash = this.generateHash(password)
|
||||
return `${email}:${passwordHash}`
|
||||
}
|
||||
|
||||
private generateHash (input: string): string {
|
||||
return bcrypt.hashSync(input, MailClient.cacheSalt)
|
||||
}
|
||||
|
||||
close (): void {
|
||||
this.transporterCache.clear()
|
||||
|
||||
if (this.transporter?.close !== undefined) {
|
||||
this.transporter.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ export async function handleSendMail (
|
||||
res: Response,
|
||||
ctx: MeasureContext
|
||||
): Promise<void> {
|
||||
const { from, to, subject, text, html, attachments, headers, apiKey } = req.body
|
||||
const { from, to, subject, text, html, attachments, headers, apiKey, password } = req.body
|
||||
if (process.env.API_KEY !== undefined && process.env.API_KEY !== apiKey) {
|
||||
ctx.warn('Unauthorized access attempt to send email', {
|
||||
from,
|
||||
@@ -131,7 +131,7 @@ export async function handleSendMail (
|
||||
message.attachments = getAttachments(attachments)
|
||||
}
|
||||
try {
|
||||
await client.sendMessage(message, ctx)
|
||||
await client.sendMessage(message, ctx, password)
|
||||
} catch (err: any) {
|
||||
ctx.error(err.message)
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ function ses (config: SesConfig): Transporter {
|
||||
})
|
||||
}
|
||||
|
||||
export function getTransport (config: Config): Transporter {
|
||||
export function getDefaultTransport (config: Config): Transporter {
|
||||
if (config.smtpConfig !== undefined) {
|
||||
return smtp(config.smtpConfig)
|
||||
}
|
||||
@@ -61,3 +61,16 @@ export function getTransport (config: Config): Transporter {
|
||||
}
|
||||
throw new Error('No transport protocol is configured')
|
||||
}
|
||||
|
||||
export function getSmtpTransport (config: SmtpConfig, user: string, pass: string): Transporter {
|
||||
const auth = { user, pass }
|
||||
const tlsSettings = getTlsSettings(config)
|
||||
return nodemailer.createTransport({
|
||||
host: config.Host,
|
||||
port: config.Port,
|
||||
auth,
|
||||
logger: true,
|
||||
debug: config.DebugLog,
|
||||
...tlsSettings
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user