mirror of
https://codeberg.org/timelimit/timelimit-server.git
synced 2026-08-31 19:03:45 +02:00
Replace email-templates by nodemailer
This commit is contained in:
@@ -13,7 +13,7 @@
|
|||||||
- default value: 8080
|
- default value: 8080
|
||||||
- NODE_ENV
|
- NODE_ENV
|
||||||
- should be set to ``production`` in production
|
- should be set to ``production`` in production
|
||||||
- when using ``development``, then mails are not sent; instead they are written to a html file which is opened
|
- when using ``development``, then mails are not sent; instead they are print to the console
|
||||||
- GOOGLE_PLAY_PUBLIC_KEY
|
- GOOGLE_PLAY_PUBLIC_KEY
|
||||||
- key for validating purchases
|
- key for validating purchases
|
||||||
- purchases using google play don't work without it/ when it is not set
|
- purchases using google play don't work without it/ when it is not set
|
||||||
|
|||||||
Generated
+68
-2533
File diff suppressed because it is too large
Load Diff
+3
-2
@@ -32,11 +32,12 @@
|
|||||||
"@adobe/jsonschema2md": "^6.0.3",
|
"@adobe/jsonschema2md": "^6.0.3",
|
||||||
"@types/basic-auth": "^1.1.3",
|
"@types/basic-auth": "^1.1.3",
|
||||||
"@types/body-parser": "^1.19.0",
|
"@types/body-parser": "^1.19.0",
|
||||||
"@types/email-templates": "^8.0.0",
|
"@types/ejs": "^3.1.0",
|
||||||
"@types/express": "^4.17.9",
|
"@types/express": "^4.17.9",
|
||||||
"@types/http-errors": "^1.8.0",
|
"@types/http-errors": "^1.8.0",
|
||||||
"@types/lodash": "^4.14.166",
|
"@types/lodash": "^4.14.166",
|
||||||
"@types/node": "^14.14.16",
|
"@types/node": "^14.14.16",
|
||||||
|
"@types/nodemailer": "^6.4.4",
|
||||||
"@types/socket.io": "^2.1.12",
|
"@types/socket.io": "^2.1.12",
|
||||||
"@types/tokgen": "^1.0.0",
|
"@types/tokgen": "^1.0.0",
|
||||||
"@types/umzug": "^2.3.0",
|
"@types/umzug": "^2.3.0",
|
||||||
@@ -52,12 +53,12 @@
|
|||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
"ejs": "^3.1.5",
|
"ejs": "^3.1.5",
|
||||||
"email-addresses": "^3.1.0",
|
"email-addresses": "^3.1.0",
|
||||||
"email-templates": "^8.0.2",
|
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"http-errors": "^1.8.0",
|
"http-errors": "^1.8.0",
|
||||||
"iab_verifier": "^0.1.2",
|
"iab_verifier": "^0.1.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mariadb": "^2.5.2",
|
"mariadb": "^2.5.2",
|
||||||
|
"nodemailer": "^6.7.2",
|
||||||
"pg": "^8.5.1",
|
"pg": "^8.5.1",
|
||||||
"pg-hstore": "^2.3.3",
|
"pg-hstore": "^2.3.3",
|
||||||
"rate-limiter-flexible": "^2.1.15",
|
"rate-limiter-flexible": "^2.1.15",
|
||||||
|
|||||||
+90
-50
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* server component for the TimeLimit App
|
* server component for the TimeLimit App
|
||||||
* Copyright (C) 2019 - 2021 Jonas Lochmann
|
* Copyright (C) 2019 - 2022 Jonas Lochmann
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* it under the terms of the GNU Affero General Public License as
|
||||||
@@ -15,39 +15,84 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { compile } from 'ejs'
|
||||||
import { parseOneAddress } from 'email-addresses'
|
import { parseOneAddress } from 'email-addresses'
|
||||||
import * as Email from 'email-templates'
|
import { readFileSync } from 'fs'
|
||||||
import { join } from 'path'
|
import { createTransport } from 'nodemailer'
|
||||||
|
import { resolve } from 'path'
|
||||||
import { config } from '../config'
|
import { config } from '../config'
|
||||||
import { IllegalStateException } from '../exception'
|
import { IllegalStateException } from '../exception'
|
||||||
|
|
||||||
const mailimprint = process.env.MAIL_IMPRINT || 'not defined'
|
const mailimprint = process.env.MAIL_IMPRINT || 'not defined'
|
||||||
const mailServerBlacklist = (process.env.MAIL_SERVER_BLACKLIST || '').split(',').filter((item) => !!item)
|
const mailServerBlacklist = (process.env.MAIL_SERVER_BLACKLIST || '').split(',').filter((item) => !!item)
|
||||||
|
|
||||||
const email = new Email({
|
const mailSender = process.env.MAIL_SENDER || ''
|
||||||
message: {
|
const mailTransportConfig = JSON.parse(process.env.MAIL_TRANSPORT || 'null') || undefined
|
||||||
from: process.env.MAIL_SENDER || ''
|
const isDevMode = process.env.NODE_ENV === 'development'
|
||||||
},
|
|
||||||
transport: JSON.parse(process.env.MAIL_TRANSPORT || 'null') || undefined,
|
const mailTransport = isDevMode || mailTransportConfig !== undefined ?
|
||||||
views: {
|
createTransport(isDevMode ? {
|
||||||
options: {
|
jsonTransport: true
|
||||||
extension: 'ejs'
|
} : mailTransportConfig) :
|
||||||
|
null
|
||||||
|
|
||||||
|
function createMailTemplateSender (templateName: string) {
|
||||||
|
const compileTemplate = (filename: string) => compile(
|
||||||
|
readFileSync(resolve(__dirname, '../../other/mail', templateName, filename)).toString('utf8')
|
||||||
|
)
|
||||||
|
|
||||||
|
const subjectTemplate = compileTemplate('subject.ejs')
|
||||||
|
const textTemplate = compileTemplate('text.ejs')
|
||||||
|
const htmlTemplate = compileTemplate('html.ejs')
|
||||||
|
|
||||||
|
const sendMail = async ({ receiver, params }: {
|
||||||
|
receiver: string
|
||||||
|
params: object
|
||||||
|
}) => {
|
||||||
|
if (!mailTransport) {
|
||||||
|
throw new Error('can not send mails without mail config and without NODE_ENV=development')
|
||||||
}
|
}
|
||||||
},
|
|
||||||
juice: false
|
const subject = subjectTemplate(params).replace(/\n/g, ' ')
|
||||||
})
|
const text = textTemplate(params)
|
||||||
|
const html = htmlTemplate(params)
|
||||||
|
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
mailTransport.sendMail({
|
||||||
|
from: mailSender,
|
||||||
|
to: receiver,
|
||||||
|
subject,
|
||||||
|
text,
|
||||||
|
html
|
||||||
|
}, (err, info) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err)
|
||||||
|
} else {
|
||||||
|
if (isDevMode) {
|
||||||
|
const data = (info as any).message
|
||||||
|
|
||||||
|
console.log(JSON.stringify(JSON.parse(data), null, 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return { sendMail }
|
||||||
|
}
|
||||||
|
|
||||||
|
const loginMailSender = createMailTemplateSender('login')
|
||||||
|
|
||||||
export const sendAuthenticationMail = async ({
|
export const sendAuthenticationMail = async ({
|
||||||
receiver, code, locale, deviceName
|
receiver, code, locale, deviceName
|
||||||
}: {
|
}: {
|
||||||
receiver: string, code: string, locale: string, deviceName: string | null
|
receiver: string, code: string, locale: string, deviceName: string | null
|
||||||
}) => {
|
}) => {
|
||||||
await email.send({
|
await loginMailSender.sendMail({
|
||||||
template: join(__dirname, '../../other/mail/login'),
|
receiver,
|
||||||
message: {
|
params: {
|
||||||
to: receiver
|
|
||||||
},
|
|
||||||
locals: {
|
|
||||||
subject: locale === 'de' ? 'Anmeldung bei TimeLimit' : 'Sign in at TimeLimit',
|
subject: locale === 'de' ? 'Anmeldung bei TimeLimit' : 'Sign in at TimeLimit',
|
||||||
introtext: locale === 'de' ? 'Geben Sie zum Authentifizieren folgenden Code in TimeLimit ein' : 'To authenticate, enter the following code in TimeLimit',
|
introtext: locale === 'de' ? 'Geben Sie zum Authentifizieren folgenden Code in TimeLimit ein' : 'To authenticate, enter the following code in TimeLimit',
|
||||||
code,
|
code,
|
||||||
@@ -60,16 +105,15 @@ export const sendAuthenticationMail = async ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const manipulationMailSender = createMailTemplateSender('manipulation')
|
||||||
|
|
||||||
export const sendManipulationWarningMail = async ({ receiver, deviceName }: {
|
export const sendManipulationWarningMail = async ({ receiver, deviceName }: {
|
||||||
receiver: string
|
receiver: string
|
||||||
deviceName: string
|
deviceName: string
|
||||||
}) => {
|
}) => {
|
||||||
await email.send({
|
await manipulationMailSender.sendMail({
|
||||||
template: join(__dirname, '../../other/mail/manipulation'),
|
receiver,
|
||||||
message: {
|
params: {
|
||||||
to: receiver
|
|
||||||
},
|
|
||||||
locals: {
|
|
||||||
subject: 'TimeLimit@' + deviceName + ' - Manipulation',
|
subject: 'TimeLimit@' + deviceName + ' - Manipulation',
|
||||||
deviceName,
|
deviceName,
|
||||||
mailimprint
|
mailimprint
|
||||||
@@ -77,16 +121,15 @@ export const sendManipulationWarningMail = async ({ receiver, deviceName }: {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const uninstallMailSender = createMailTemplateSender('uninstall')
|
||||||
|
|
||||||
export const sendUninstallWarningMail = async ({ receiver, deviceName }: {
|
export const sendUninstallWarningMail = async ({ receiver, deviceName }: {
|
||||||
receiver: string
|
receiver: string
|
||||||
deviceName: string
|
deviceName: string
|
||||||
}) => {
|
}) => {
|
||||||
await email.send({
|
await uninstallMailSender.sendMail({
|
||||||
template: join(__dirname, '../../other/mail/uninstall'),
|
receiver,
|
||||||
message: {
|
params: {
|
||||||
to: receiver
|
|
||||||
},
|
|
||||||
locals: {
|
|
||||||
subject: 'TimeLimit removed from ' + deviceName,
|
subject: 'TimeLimit removed from ' + deviceName,
|
||||||
deviceName,
|
deviceName,
|
||||||
mailimprint
|
mailimprint
|
||||||
@@ -94,31 +137,29 @@ export const sendUninstallWarningMail = async ({ receiver, deviceName }: {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const taskDoneSender = createMailTemplateSender('taskdone')
|
||||||
|
|
||||||
export const sendTaskDoneMail = async ({ receiver, child, task }: {
|
export const sendTaskDoneMail = async ({ receiver, child, task }: {
|
||||||
receiver: string
|
receiver: string
|
||||||
child: string
|
child: string
|
||||||
task: string
|
task: string
|
||||||
}) => {
|
}) => {
|
||||||
await email.send({
|
await taskDoneSender.sendMail({
|
||||||
template: join(__dirname, '../../other/mail/taskdone'),
|
receiver,
|
||||||
message: {
|
params: { child, task, mailimprint }
|
||||||
to: receiver
|
|
||||||
},
|
|
||||||
locals: { child, task, mailimprint }
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deviceLinkedSender = createMailTemplateSender('device-linked-by-mail')
|
||||||
|
|
||||||
export const sendDeviceLinkedMail = async ({ receiver, deviceName, locale }: {
|
export const sendDeviceLinkedMail = async ({ receiver, deviceName, locale }: {
|
||||||
receiver: string
|
receiver: string
|
||||||
deviceName: string
|
deviceName: string
|
||||||
locale: string
|
locale: string
|
||||||
}) => {
|
}) => {
|
||||||
await email.send({
|
await deviceLinkedSender.sendMail({
|
||||||
template: join(__dirname, '../../other/mail/device-linked-by-mail'),
|
receiver,
|
||||||
message: {
|
params: {
|
||||||
to: receiver
|
|
||||||
},
|
|
||||||
locals: {
|
|
||||||
subject: locale === 'de' ? 'Gerät hinzugefügt' : 'Device added',
|
subject: locale === 'de' ? 'Gerät hinzugefügt' : 'Device added',
|
||||||
preText: locale === 'de' ? 'Soeben wurde das Gerät' : 'The device',
|
preText: locale === 'de' ? 'Soeben wurde das Gerät' : 'The device',
|
||||||
deviceName,
|
deviceName,
|
||||||
@@ -129,16 +170,15 @@ export const sendDeviceLinkedMail = async ({ receiver, deviceName, locale }: {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const passwordRecoveryUsedMailSender = createMailTemplateSender('password-recovery-used')
|
||||||
|
|
||||||
export const sendPasswordRecoveryUsedMail = async ({ receiver, locale }: {
|
export const sendPasswordRecoveryUsedMail = async ({ receiver, locale }: {
|
||||||
receiver: string
|
receiver: string
|
||||||
locale: string
|
locale: string
|
||||||
}) => {
|
}) => {
|
||||||
await email.send({
|
await passwordRecoveryUsedMailSender.sendMail({
|
||||||
template: join(__dirname, '../../other/mail/password-recovery-used'),
|
receiver,
|
||||||
message: {
|
params: {
|
||||||
to: receiver
|
|
||||||
},
|
|
||||||
locals: {
|
|
||||||
subject: locale === 'de' ? 'Passwort-Vergessen-Funktion verwendet' : 'Password reset',
|
subject: locale === 'de' ? 'Passwort-Vergessen-Funktion verwendet' : 'Password reset',
|
||||||
text: locale === 'de' ?
|
text: locale === 'de' ?
|
||||||
'Soeben wurde Ihr TimeLimit-Passwort mit der Passwort-Vergessen-Funktion geändert.' :
|
'Soeben wurde Ihr TimeLimit-Passwort mit der Passwort-Vergessen-Funktion geändert.' :
|
||||||
|
|||||||
Reference in New Issue
Block a user