diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index e50f96dd2..658c7e97c 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -1,8 +1,10 @@ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ + /* eslint-disable n/no-process-env */ import { config } from 'dotenv' +// eslint-disable-next-line import/no-namespace +import * as SMTPTransport from 'nodemailer/lib/smtp-pool' import emails from './emails' import metadata from './metadata' @@ -13,16 +15,17 @@ config() // Use Cypress env or process.env // eslint-disable-next-line @typescript-eslint/no-explicit-any declare let Cypress: any | undefined -const env = typeof Cypress !== 'undefined' ? Cypress.env() : process.env +const env = (typeof Cypress !== 'undefined' ? Cypress.env() : process.env) as typeof process.env const environment = { - NODE_ENV: env.NODE_ENV || process.env.NODE_ENV, + NODE_ENV: env.NODE_ENV ?? process.env.NODE_ENV, DEBUG: env.NODE_ENV !== 'production' && env.DEBUG, TEST: env.NODE_ENV === 'test', PRODUCTION: env.NODE_ENV === 'production', // used for staging enviroments if 'PRODUCTION=true' and 'PRODUCTION_DB_CLEAN_ALLOW=true' PRODUCTION_DB_CLEAN_ALLOW: env.PRODUCTION_DB_CLEAN_ALLOW === 'true' || false, // default = false - DISABLED_MIDDLEWARES: ['test', 'development'].includes(env.NODE_ENV as string) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + DISABLED_MIDDLEWARES: ['test', 'development'].includes(env.NODE_ENV!) ? (env.DISABLED_MIDDLEWARES?.split(',') ?? []) : [], SEND_MAIL: env.NODE_ENV !== 'test', @@ -35,32 +38,51 @@ const required = { } const server = { - CLIENT_URI: env.CLIENT_URI || 'http://localhost:3000', - GRAPHQL_URI: env.GRAPHQL_URI || 'http://localhost:4000', - JWT_EXPIRES: env.JWT_EXPIRES || '2y', + CLIENT_URI: env.CLIENT_URI ?? 'http://localhost:3000', + GRAPHQL_URI: env.GRAPHQL_URI ?? 'http://localhost:4000', + JWT_EXPIRES: env.JWT_EXPIRES ?? '2y', } -const hasDKIMData = env.SMTP_DKIM_DOMAINNAME && env.SMTP_DKIM_KEYSELECTOR && env.SMTP_DKIM_PRIVATKEY +const SMTP_HOST = env.SMTP_HOST +const SMTP_PORT = (env.SMTP_PORT && parseInt(env.SMTP_PORT)) || undefined +const SMTP_IGNORE_TLS = env.SMTP_IGNORE_TLS !== 'false' // default = true +const SMTP_SECURE = env.SMTP_SECURE === 'true' +const SMTP_USERNAME = env.SMTP_USERNAME +const SMTP_PASSWORD = env.SMTP_PASSWORD +const SMTP_DKIM_DOMAINNAME = env.SMTP_DKIM_DOMAINNAME +const SMTP_DKIM_KEYSELECTOR = env.SMTP_DKIM_KEYSELECTOR +// PEM format = https://docs.progress.com/bundle/datadirect-hybrid-data-pipeline-installation-46/page/PEM-file-format.html +const SMTP_DKIM_PRIVATKEY = env.SMTP_DKIM_PRIVATKEY?.replace(/\\n/g, '\n') // replace all "\n" in .env string by real line break +const SMTP_MAX_CONNECTIONS = (env.SMTP_MAX_CONNECTIONS && parseInt(env.SMTP_MAX_CONNECTIONS)) || 5 +const SMTP_MAX_MESSAGES = (env.SMTP_MAX_MESSAGES && parseInt(env.SMTP_MAX_MESSAGES)) || 100 -const smtp = { - SMTP_HOST: env.SMTP_HOST, - SMTP_PORT: env.SMTP_PORT, - SMTP_IGNORE_TLS: env.SMTP_IGNORE_TLS !== 'false', // default = true - SMTP_SECURE: env.SMTP_SECURE === 'true', - SMTP_USERNAME: env.SMTP_USERNAME, - SMTP_PASSWORD: env.SMTP_PASSWORD, - SMTP_DKIM_DOMAINNAME: hasDKIMData && env.SMTP_DKIM_DOMAINNAME, - SMTP_DKIM_KEYSELECTOR: hasDKIMData && env.SMTP_DKIM_KEYSELECTOR, - // PEM format: https://docs.progress.com/bundle/datadirect-hybrid-data-pipeline-installation-46/page/PEM-file-format.html - SMTP_DKIM_PRIVATKEY: hasDKIMData && env.SMTP_DKIM_PRIVATKEY.replace(/\\n/g, '\n'), // replace all "\n" in .env string by real line break - SMTP_MAX_CONNECTIONS: env.SMTP_MAX_CONNECTIONS || 5, - SMTP_MAX_MESSAGES: env.SMTP_MAX_MESSAGES || 100, +const nodemailerTransportOptions: SMTPTransport.Options = { + host: SMTP_HOST, + port: SMTP_PORT, + ignoreTLS: SMTP_IGNORE_TLS, + secure: SMTP_SECURE, // true for 465, false for other ports + pool: true, + maxConnections: SMTP_MAX_CONNECTIONS, + maxMessages: SMTP_MAX_MESSAGES, +} +if (SMTP_USERNAME && SMTP_PASSWORD) { + nodemailerTransportOptions.auth = { + user: SMTP_USERNAME, + pass: SMTP_PASSWORD, + } +} +if (SMTP_DKIM_DOMAINNAME && SMTP_DKIM_KEYSELECTOR && SMTP_DKIM_PRIVATKEY) { + nodemailerTransportOptions.dkim = { + domainName: SMTP_DKIM_DOMAINNAME, + keySelector: SMTP_DKIM_KEYSELECTOR, + privateKey: SMTP_DKIM_PRIVATKEY, + } } const neo4j = { - NEO4J_URI: env.NEO4J_URI || 'bolt://localhost:7687', - NEO4J_USERNAME: env.NEO4J_USERNAME || 'neo4j', - NEO4J_PASSWORD: env.NEO4J_PASSWORD || 'neo4j', + NEO4J_URI: env.NEO4J_URI ?? 'bolt://localhost:7687', + NEO4J_USERNAME: env.NEO4J_USERNAME ?? 'neo4j', + NEO4J_PASSWORD: env.NEO4J_PASSWORD ?? 'neo4j', } const sentry = { @@ -70,7 +92,7 @@ const sentry = { const redis = { REDIS_DOMAIN: env.REDIS_DOMAIN, - REDIS_PORT: env.REDIS_PORT, + REDIS_PORT: (env.REDIS_PORT && parseInt(env.REDIS_PORT)) || undefined, REDIS_PASSWORD: env.REDIS_PASSWORD, } @@ -110,10 +132,11 @@ export default { ...environment, ...server, ...required, - ...smtp, ...neo4j, ...sentry, ...redis, ...s3, ...options, } + +export { nodemailerTransportOptions } diff --git a/backend/src/context/pubsub.ts b/backend/src/context/pubsub.ts index 003347b16..3d99bba6d 100644 --- a/backend/src/context/pubsub.ts +++ b/backend/src/context/pubsub.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { RedisPubSub } from 'graphql-redis-subscriptions' import { PubSub } from 'graphql-subscriptions' import Redis from 'ioredis' @@ -6,14 +5,15 @@ import Redis from 'ioredis' import CONFIG from '@config/index' export default () => { - if (!CONFIG.REDIS_DOMAIN || CONFIG.REDIS_PORT || CONFIG.REDIS_PASSWORD) { + const { REDIS_DOMAIN, REDIS_PORT, REDIS_PASSWORD } = CONFIG + if (!(REDIS_DOMAIN && REDIS_PORT && REDIS_PASSWORD)) { return new PubSub() } const options = { - host: CONFIG.REDIS_DOMAIN, - port: CONFIG.REDIS_PORT, - password: CONFIG.REDIS_PASSWORD, + host: REDIS_DOMAIN, + port: REDIS_PORT, + password: REDIS_PASSWORD, retryStrategy: (times) => { return Math.min(times * 50, 2000) }, diff --git a/backend/src/db/migrations-examples/20200312140328-bulk_upload_to_s3.ts b/backend/src/db/migrations-examples/20200312140328-bulk_upload_to_s3.ts deleted file mode 100644 index 0307a2e6e..000000000 --- a/backend/src/db/migrations-examples/20200312140328-bulk_upload_to_s3.ts +++ /dev/null @@ -1,117 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable security/detect-non-literal-fs-filename */ -import https from 'https' -import { existsSync, createReadStream } from 'node:fs' -import path from 'node:path' - -import { S3 } from 'aws-sdk' -import mime from 'mime-types' - -import s3Configs from '@config/index' -import { getDriver } from '@db/neo4j' - -export const description = ` -Upload all image files to a S3 compatible object storage in order to reduce -load on our backend. -` - -export async function up(next) { - const driver = getDriver() - const session = driver.session() - const transaction = session.beginTransaction() - const agent = new https.Agent({ - maxSockets: 5, - }) - - const { - AWS_ENDPOINT: endpoint, - AWS_REGION: region, - AWS_BUCKET: Bucket, - S3_CONFIGURED, - } = s3Configs - - if (!S3_CONFIGURED) { - // eslint-disable-next-line no-console - console.log('No S3 given, cannot upload image files') - return - } - - const s3 = new S3({ region, endpoint, httpOptions: { agent } }) - try { - // Implement your migration here. - const { records } = await transaction.run('MATCH (image:Image) RETURN image.url as url') - let urls = records.map((r) => r.get('url')) - urls = urls.filter((url) => url.startsWith('/uploads')) - const locations = await Promise.all( - urls - .map((url) => { - return async () => { - const { pathname } = new URL(url, 'http://example.org') - const fileLocation = path.join(__dirname, `../../../public/${pathname}`) - const s3Location = `original${pathname}` - // eslint-disable-next-line n/no-sync - if (existsSync(fileLocation)) { - const mimeType = mime.lookup(fileLocation) - const params = { - Bucket, - Key: s3Location, - ACL: 'public-read', - ContentType: mimeType || 'image/jpeg', - Body: createReadStream(fileLocation), - } - - const data = await s3.upload(params).promise() - const { Location: spacesUrl } = data - - const updatedRecord = await transaction.run( - 'MATCH (image:Image {url: $url}) SET image.url = $spacesUrl RETURN image.url as url', - { url, spacesUrl }, - ) - const [updatedUrl] = updatedRecord.records.map((record) => record.get('url')) - return updatedUrl - } - } - }) - .map((p) => p()), - ) - // eslint-disable-next-line no-console - console.log('this is locations', locations) - await transaction.commit() - next() - } catch (error) { - // eslint-disable-next-line no-console - console.log(error) - await transaction.rollback() - // eslint-disable-next-line no-console - console.log('rolled back') - throw new Error(error) - } finally { - await session.close() - } -} - -export async function down(next) { - const driver = getDriver() - const session = driver.session() - const transaction = session.beginTransaction() - - try { - // Implement your migration here. - await transaction.run(``) - await transaction.commit() - next() - // eslint-disable-next-line no-catch-all/no-catch-all - } catch (error) { - // eslint-disable-next-line no-console - console.log(error) - await transaction.rollback() - // eslint-disable-next-line no-console - console.log('rolled back') - } finally { - await session.close() - } -} diff --git a/backend/src/db/neo4j.ts b/backend/src/db/neo4j.ts index 5d084099a..ecaef68b5 100644 --- a/backend/src/db/neo4j.ts +++ b/backend/src/db/neo4j.ts @@ -1,6 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unsafe-argument */ - -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable import/no-named-as-default-member */ import neo4j, { Driver } from 'neo4j-driver' import Neode from 'neode' diff --git a/backend/src/emails/sendEmail.ts b/backend/src/emails/sendEmail.ts index 7b7ea76b3..8e5d8e83c 100644 --- a/backend/src/emails/sendEmail.ts +++ b/backend/src/emails/sendEmail.ts @@ -7,17 +7,14 @@ import path from 'node:path' import Email from 'email-templates' import { createTransport } from 'nodemailer' + // import type Email as EmailType from '@types/email-templates' -import CONFIG from '@config/index' +import CONFIG, { nodemailerTransportOptions } from '@config/index' import logosWebapp from '@config/logos' import metadata from '@config/metadata' import { UserDbProperties } from '@db/types/User' -const hasAuthData = CONFIG.SMTP_USERNAME && CONFIG.SMTP_PASSWORD -const hasDKIMData = - CONFIG.SMTP_DKIM_DOMAINNAME && CONFIG.SMTP_DKIM_KEYSELECTOR && CONFIG.SMTP_DKIM_PRIVATKEY - const welcomeImageUrl = new URL(logosWebapp.LOGO_WELCOME_PATH, CONFIG.CLIENT_URI) const settingsUrl = new URL('/settings/notifications', CONFIG.CLIENT_URI) @@ -31,24 +28,7 @@ const defaultParams = { renderSettingsUrl: true, } -export const transport = createTransport({ - host: CONFIG.SMTP_HOST, - port: CONFIG.SMTP_PORT, - ignoreTLS: CONFIG.SMTP_IGNORE_TLS, - secure: CONFIG.SMTP_SECURE, // true for 465, false for other ports - pool: true, - maxConnections: CONFIG.SMTP_MAX_CONNECTIONS, - maxMessages: CONFIG.SMTP_MAX_MESSAGES, - auth: hasAuthData && { - user: CONFIG.SMTP_USERNAME, - pass: CONFIG.SMTP_PASSWORD, - }, - dkim: hasDKIMData && { - domainName: CONFIG.SMTP_DKIM_DOMAINNAME, - keySelector: CONFIG.SMTP_DKIM_KEYSELECTOR, - privateKey: CONFIG.SMTP_DKIM_PRIVATKEY, - }, -}) +const transport = createTransport(nodemailerTransportOptions) const email = new Email({ message: { diff --git a/backend/src/graphql/resolvers/images/images.ts b/backend/src/graphql/resolvers/images/images.ts index 2e76a7889..217d26553 100644 --- a/backend/src/graphql/resolvers/images/images.ts +++ b/backend/src/graphql/resolvers/images/images.ts @@ -138,6 +138,9 @@ const s3Upload = async ({ createReadStream, uniqueFilename, mimetype }) => { const s3 = new S3({ region, endpoint }) const s3Location = `original/${uniqueFilename}` + if (!Bucket) { + throw new Error('AWS_BUCKET is undefined') + } const params = { Bucket, Key: s3Location, @@ -160,6 +163,9 @@ const s3Delete = async (url) => { const s3 = new S3({ region, endpoint }) let { pathname } = new URL(url, 'http://example.org') // dummy domain to avoid invalid URL error pathname = pathname.substring(1) // remove first character '/' + if (!Bucket) { + throw new Error('AWS_BUCKET is undefined') + } const params = { Bucket, Key: pathname, diff --git a/backend/src/index.ts b/backend/src/index.ts index 3da4ec7ae..612141733 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/restrict-template-expressions */ -/* eslint-disable @typescript-eslint/no-unsafe-argument */ + import CONFIG from './config' import createServer from './server' diff --git a/backend/src/middleware/helpers/email/sendMail.ts b/backend/src/middleware/helpers/email/sendMail.ts deleted file mode 100644 index fc50107fb..000000000 --- a/backend/src/middleware/helpers/email/sendMail.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* eslint-disable @typescript-eslint/require-await */ -/* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/restrict-plus-operands */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -import { createTransport } from 'nodemailer' -import { htmlToText } from 'nodemailer-html-to-text' - -import CONFIG from '@config/index' -import { cleanHtml } from '@middleware/helpers/cleanHtml' - -const hasEmailConfig = CONFIG.SMTP_HOST && CONFIG.SMTP_PORT -const hasAuthData = CONFIG.SMTP_USERNAME && CONFIG.SMTP_PASSWORD -const hasDKIMData = - CONFIG.SMTP_DKIM_DOMAINNAME && CONFIG.SMTP_DKIM_KEYSELECTOR && CONFIG.SMTP_DKIM_PRIVATKEY - -const transporter = createTransport({ - host: CONFIG.SMTP_HOST, - port: CONFIG.SMTP_PORT, - ignoreTLS: CONFIG.SMTP_IGNORE_TLS, - secure: CONFIG.SMTP_SECURE, // true for 465, false for other ports - pool: true, - maxConnections: CONFIG.SMTP_MAX_CONNECTIONS, - maxMessages: CONFIG.SMTP_MAX_MESSAGES, - auth: hasAuthData && { - user: CONFIG.SMTP_USERNAME, - pass: CONFIG.SMTP_PASSWORD, - }, - dkim: hasDKIMData && { - domainName: CONFIG.SMTP_DKIM_DOMAINNAME, - keySelector: CONFIG.SMTP_DKIM_KEYSELECTOR, - privateKey: CONFIG.SMTP_DKIM_PRIVATKEY, - }, -}) - -// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-empty-function -let sendMailCallback: any = async () => {} -if (!hasEmailConfig) { - if (!CONFIG.TEST) { - // eslint-disable-next-line no-console - console.log('Warning: Middlewares will not try to send mails.') - // TODO: disable e-mail logging on database seeding? - // TODO: implement general logging like 'log4js', see Gradido project: https://github.com/gradido/gradido/blob/master/backend/log4js-config.json - sendMailCallback = async (templateArgs) => { - // eslint-disable-next-line no-console - console.log('--- Log Unsend E-Mail ---') - // eslint-disable-next-line no-console - console.log('To: ' + templateArgs.to) - // eslint-disable-next-line no-console - console.log('From: ' + templateArgs.from) - // eslint-disable-next-line no-console - console.log('Subject: ' + templateArgs.subject) - // eslint-disable-next-line no-console - console.log('Content:') - // eslint-disable-next-line no-console - console.log( - cleanHtml(templateArgs.html, 'dummyKey', { - allowedTags: ['a'], - allowedAttributes: { a: ['href'] }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any).replace(/&/g, '&'), - ) - } - } -} else { - sendMailCallback = async (templateArgs) => { - transporter.use( - 'compile', - htmlToText({ - ignoreImage: true, - wordwrap: false, - }), - ) - - await transporter.sendMail(templateArgs) - } -} - -export const sendMail = sendMailCallback diff --git a/backend/src/middleware/helpers/email/templateBuilder.spec.ts b/backend/src/middleware/helpers/email/templateBuilder.spec.ts deleted file mode 100644 index 85608b55a..000000000 --- a/backend/src/middleware/helpers/email/templateBuilder.spec.ts +++ /dev/null @@ -1,283 +0,0 @@ -/* eslint-disable @typescript-eslint/require-await */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-argument */ -import CONFIG from '@config/index' -import logosWebapp from '@config/logos' - -import { - signupTemplate, - emailVerificationTemplate, - resetPasswordTemplate, - wrongAccountTemplate, - notificationTemplate, - chatMessageTemplate, -} from './templateBuilder' - -const englishHint = 'English version below!' -const welcomeImageUrl = new URL(logosWebapp.LOGO_WELCOME_PATH, CONFIG.CLIENT_URI) -const supportUrl = CONFIG.SUPPORT_URL.toString() -let actionUrl, name, settingsUrl - -const signupTemplateData = () => ({ - email: 'test@example.org', - variables: { - nonce: '12345', - inviteCode: 'AAAAAA', - }, -}) -const emailVerificationTemplateData = () => ({ - email: 'test@example.org', - variables: { - nonce: '12345', - name: 'Mr Example', - }, -}) -const resetPasswordTemplateData = () => ({ - email: 'test@example.org', - variables: { - nonce: '12345', - name: 'Mr Example', - }, -}) -const chatMessageTemplateData = { - email: 'test@example.org', - variables: { - senderUser: { - name: 'Sender', - }, - recipientUser: { - name: 'Recipient', - }, - }, -} -const wrongAccountTemplateData = () => ({ - email: 'test@example.org', - variables: {}, -}) -const notificationTemplateData = (locale) => ({ - email: 'test@example.org', - variables: { - notification: { - to: { name: 'Mr Example', locale }, - }, - }, -}) -const textsStandard = [ - { - templPropName: 'from', - isContaining: false, - text: CONFIG.EMAIL_DEFAULT_SENDER, - }, - { - templPropName: 'to', - isContaining: false, - text: 'test@example.org', - }, - // is contained in html - welcomeImageUrl.toString(), - CONFIG.ORGANIZATION_URL, - CONFIG.APPLICATION_NAME, -] -const testEmailData = (emailTemplate, templateBuilder, templateData, texts) => { - if (!emailTemplate) { - emailTemplate = templateBuilder(templateData) - } - texts.forEach((element) => { - if (typeof element === 'object') { - if (element.isContaining) { - expect(emailTemplate[element.templPropName]).toEqual(expect.stringContaining(element.text)) - } else { - expect(emailTemplate[element.templPropName]).toEqual(element.text) - } - } else { - expect(emailTemplate.html).toEqual(expect.stringContaining(element)) - } - }) - return emailTemplate -} - -describe('templateBuilder', () => { - describe('signupTemplate', () => { - describe('multi language', () => { - it('e-mail is build with all data', () => { - const subject = `Willkommen, Bienvenue, Welcome to ${CONFIG.APPLICATION_NAME}!` - const actionUrl = new URL('/registration', CONFIG.CLIENT_URI).toString() - const theSignupTemplateData = signupTemplateData() - const enContent = "Thank you for joining our cause – it's awesome to have you on board." - const deContent = - 'Danke, dass Du dich angemeldet hast – wir freuen uns, Dich dabei zu haben.' - testEmailData(null, signupTemplate, theSignupTemplateData, [ - ...textsStandard, - { - templPropName: 'subject', - isContaining: false, - text: subject, - }, - englishHint, - actionUrl, - theSignupTemplateData.variables.nonce, - theSignupTemplateData.variables.inviteCode, - enContent, - deContent, - supportUrl, - ]) - }) - }) - }) - - describe('emailVerificationTemplate', () => { - describe('multi language', () => { - it('e-mail is build with all data', () => { - const subject = 'Neue E-Mail Adresse | New E-Mail Address' - const actionUrl = new URL('/settings/my-email-address/verify', CONFIG.CLIENT_URI).toString() - const theEmailVerificationTemplateData = emailVerificationTemplateData() - const enContent = 'So, you want to change your e-mail? No problem!' - const deContent = 'Du möchtest also deine E-Mail ändern? Kein Problem!' - testEmailData(null, emailVerificationTemplate, theEmailVerificationTemplateData, [ - ...textsStandard, - { - templPropName: 'subject', - isContaining: false, - text: subject, - }, - englishHint, - actionUrl, - theEmailVerificationTemplateData.variables.nonce, - theEmailVerificationTemplateData.variables.name, - enContent, - deContent, - supportUrl, - ]) - }) - }) - }) - - describe('resetPasswordTemplate', () => { - describe('multi language', () => { - it('e-mail is build with all data', () => { - const subject = 'Neues Passwort | Reset Password' - const actionUrl = new URL('/password-reset/change-password', CONFIG.CLIENT_URI).toString() - const theResetPasswordTemplateData = resetPasswordTemplateData() - const enContent = 'So, you forgot your password? No problem!' - const deContent = 'Du hast also dein Passwort vergessen? Kein Problem!' - testEmailData(null, resetPasswordTemplate, theResetPasswordTemplateData, [ - ...textsStandard, - { - templPropName: 'subject', - isContaining: false, - text: subject, - }, - englishHint, - actionUrl, - theResetPasswordTemplateData.variables.nonce, - theResetPasswordTemplateData.variables.name, - enContent, - deContent, - supportUrl, - ]) - }) - }) - }) - - describe('chatMessageTemplate', () => { - describe('multi language', () => { - it('e-mail is build with all data', () => { - const subject = `Neue Chat-Nachricht | New chat message - ${chatMessageTemplateData.variables.senderUser.name}` - const actionUrl = new URL('/chat', CONFIG.CLIENT_URI).toString() - const enContent = `You have received a new chat message from ${chatMessageTemplateData.variables.senderUser.name}.` - const deContent = `Du hast eine neue Chat-Nachricht von ${chatMessageTemplateData.variables.senderUser.name} erhalten.` - testEmailData(null, chatMessageTemplate, chatMessageTemplateData, [ - ...textsStandard, - { - templPropName: 'subject', - isContaining: false, - text: subject, - }, - englishHint, - actionUrl, - chatMessageTemplateData.variables.senderUser, - chatMessageTemplateData.variables.recipientUser, - enContent, - deContent, - supportUrl, - ]) - }) - }) - }) - - describe('wrongAccountTemplate', () => { - describe('multi language', () => { - it('e-mail is build with all data', () => { - const subject = 'Falsche Mailadresse? | Wrong E-mail?' - const actionUrl = new URL('/password-reset/request', CONFIG.CLIENT_URI).toString() - const theWrongAccountTemplateData = wrongAccountTemplateData() - const enContent = - "You requested a password reset but unfortunately we couldn't find an account associated with your e-mail address." - const deContent = - 'Du hast bei uns ein neues Passwort angefordert – leider haben wir aber keinen Account mit Deiner E-Mailadresse gefunden.' - testEmailData(null, wrongAccountTemplate, theWrongAccountTemplateData, [ - ...textsStandard, - { - templPropName: 'subject', - isContaining: false, - text: subject, - }, - englishHint, - actionUrl, - enContent, - deContent, - supportUrl, - ]) - }) - }) - }) - - describe('notificationTemplate', () => { - beforeEach(() => { - actionUrl = new URL('/notifications', CONFIG.CLIENT_URI).toString() - name = notificationTemplateData('en').variables.notification.to.name - settingsUrl = new URL('/settings/notifications', CONFIG.CLIENT_URI) - }) - - describe('en', () => { - it('e-mail is build with all data', () => { - const subject = `${CONFIG.APPLICATION_NAME} – Notification` - const content = 'You received at least one notification. Click on this button to view them:' - testEmailData(null, notificationTemplate, notificationTemplateData('en'), [ - ...textsStandard, - { - templPropName: 'subject', - isContaining: false, - text: subject, - }, - actionUrl, - name, - content, - settingsUrl, - ]) - }) - }) - - describe('de', () => { - it('e-mail is build with all data', async () => { - const subject = `${CONFIG.APPLICATION_NAME} – Benachrichtigung` - const content = `Du hast mindestens eine Benachrichtigung erhalten. Klick auf diesen Button, um sie anzusehen:` - testEmailData(null, notificationTemplate, notificationTemplateData('de'), [ - ...textsStandard, - { - templPropName: 'subject', - isContaining: false, - text: subject, - }, - actionUrl, - name, - content, - settingsUrl, - ]) - }) - }) - }) -}) diff --git a/backend/src/middleware/helpers/email/templateBuilder.ts b/backend/src/middleware/helpers/email/templateBuilder.ts deleted file mode 100644 index ffceb49f6..000000000 --- a/backend/src/middleware/helpers/email/templateBuilder.ts +++ /dev/null @@ -1,140 +0,0 @@ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable import/no-namespace */ -import mustache from 'mustache' - -import CONFIG from '@config/index' -import logosWebapp from '@config/logos' -import metadata from '@config/metadata' - -import * as templates from './templates' -import * as templatesDE from './templates/de' -import * as templatesEN from './templates/en' - -const from = CONFIG.EMAIL_DEFAULT_SENDER -const welcomeImageUrl = new URL(logosWebapp.LOGO_WELCOME_PATH, CONFIG.CLIENT_URI) - -const defaultParams = { - welcomeImageUrl, - APPLICATION_NAME: CONFIG.APPLICATION_NAME, - ORGANIZATION_NAME: metadata.ORGANIZATION_NAME, - ORGANIZATION_URL: CONFIG.ORGANIZATION_URL, - supportUrl: CONFIG.SUPPORT_URL, -} -const englishHint = 'English version below!' - -export const signupTemplate = ({ email, variables: { nonce, inviteCode = null } }) => { - const subject = `Willkommen, Bienvenue, Welcome to ${CONFIG.APPLICATION_NAME}!` - // dev format example: http://localhost:3000/registration?method=invite-mail&email=huss%40pjannto.com&nonce=64853 - const actionUrl = new URL('/registration', CONFIG.CLIENT_URI) - actionUrl.searchParams.set('email', email) - actionUrl.searchParams.set('nonce', nonce) - if (inviteCode) { - actionUrl.searchParams.set('inviteCode', inviteCode) - actionUrl.searchParams.set('method', 'invite-code') - } else { - actionUrl.searchParams.set('method', 'invite-mail') - } - const renderParams = { ...defaultParams, englishHint, actionUrl, nonce, subject } - - return { - from, - to: email, - subject, - html: mustache.render(templates.layout, renderParams, { content: templates.signup }), - } -} - -export const emailVerificationTemplate = ({ email, variables: { nonce, name } }) => { - const subject = 'Neue E-Mail Adresse | New E-Mail Address' - const actionUrl = new URL('/settings/my-email-address/verify', CONFIG.CLIENT_URI) - actionUrl.searchParams.set('email', email) - actionUrl.searchParams.set('nonce', nonce) - const renderParams = { ...defaultParams, englishHint, actionUrl, name, nonce, subject } - - return { - from, - to: email, - subject, - html: mustache.render(templates.layout, renderParams, { content: templates.emailVerification }), - } -} - -export const resetPasswordTemplate = ({ email, variables: { nonce, name } }) => { - const subject = 'Neues Passwort | Reset Password' - const actionUrl = new URL('/password-reset/change-password', CONFIG.CLIENT_URI) - actionUrl.searchParams.set('nonce', nonce) - actionUrl.searchParams.set('email', email) - const renderParams = { ...defaultParams, englishHint, actionUrl, name, nonce, subject } - - return { - from, - to: email, - subject, - html: mustache.render(templates.layout, renderParams, { content: templates.passwordReset }), - } -} - -export const chatMessageTemplate = ({ email, variables: { senderUser, recipientUser } }) => { - const subject = `Neue Chat-Nachricht | New chat message - ${senderUser.name}` - const actionUrl = new URL('/chat', CONFIG.CLIENT_URI) - const renderParams = { - ...defaultParams, - subject, - englishHint, - actionUrl, - senderUser, - recipientUser, - } - - return { - from, - to: email, - subject, - html: mustache.render(templates.layout, renderParams, { content: templates.chatMessage }), - } -} - -export const wrongAccountTemplate = ({ email, _variables = {} }) => { - const subject = 'Falsche Mailadresse? | Wrong E-mail?' - const actionUrl = new URL('/password-reset/request', CONFIG.CLIENT_URI) - const renderParams = { ...defaultParams, englishHint, actionUrl } - - return { - from, - to: email, - subject, - html: mustache.render(templates.layout, renderParams, { content: templates.wrongAccount }), - } -} - -export const notificationTemplate = ({ email, variables: { notification } }) => { - const actionUrl = new URL('/notifications', CONFIG.CLIENT_URI) - const settingsUrl = new URL('/settings/notifications', CONFIG.CLIENT_URI) - const renderParams = { ...defaultParams, name: notification.to.name, settingsUrl, actionUrl } - let content - switch (notification.to.locale) { - case 'de': - content = templatesDE.notification - break - case 'en': - content = templatesEN.notification - break - - default: - content = templatesEN.notification - break - } - const subjectUnrendered = content.split('\n')[0].split('"')[1] - const subject = mustache.render(subjectUnrendered, renderParams, {}) - - return { - from, - to: email, - subject, - html: mustache.render(templates.layout, renderParams, { content }), - } -} diff --git a/backend/src/middleware/helpers/email/templates/chatMessage.html b/backend/src/middleware/helpers/email/templates/chatMessage.html deleted file mode 100644 index 49fc69bf2..000000000 --- a/backend/src/middleware/helpers/email/templates/chatMessage.html +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/backend/src/middleware/helpers/email/templates/de/index.ts b/backend/src/middleware/helpers/email/templates/de/index.ts deleted file mode 100644 index 4aa323b9f..000000000 --- a/backend/src/middleware/helpers/email/templates/de/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable security/detect-non-literal-fs-filename */ -import fs from 'node:fs' -import path from 'node:path' - -// eslint-disable-next-line n/no-sync -const readFile = (fileName) => fs.readFileSync(path.join(__dirname, fileName), 'utf-8') - -export const notification = readFile('./notification.html') diff --git a/backend/src/middleware/helpers/email/templates/de/notification.html b/backend/src/middleware/helpers/email/templates/de/notification.html deleted file mode 100644 index a54943310..000000000 --- a/backend/src/middleware/helpers/email/templates/de/notification.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/backend/src/middleware/helpers/email/templates/emailVerification.html b/backend/src/middleware/helpers/email/templates/emailVerification.html deleted file mode 100644 index 35ce27e5a..000000000 --- a/backend/src/middleware/helpers/email/templates/emailVerification.html +++ /dev/null @@ -1,186 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/backend/src/middleware/helpers/email/templates/en/index.ts b/backend/src/middleware/helpers/email/templates/en/index.ts deleted file mode 100644 index 4aa323b9f..000000000 --- a/backend/src/middleware/helpers/email/templates/en/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable security/detect-non-literal-fs-filename */ -import fs from 'node:fs' -import path from 'node:path' - -// eslint-disable-next-line n/no-sync -const readFile = (fileName) => fs.readFileSync(path.join(__dirname, fileName), 'utf-8') - -export const notification = readFile('./notification.html') diff --git a/backend/src/middleware/helpers/email/templates/en/notification.html b/backend/src/middleware/helpers/email/templates/en/notification.html deleted file mode 100644 index 168b21864..000000000 --- a/backend/src/middleware/helpers/email/templates/en/notification.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/backend/src/middleware/helpers/email/templates/index.ts b/backend/src/middleware/helpers/email/templates/index.ts deleted file mode 100644 index 9a64192ce..000000000 --- a/backend/src/middleware/helpers/email/templates/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable security/detect-non-literal-fs-filename */ -import fs from 'node:fs' -import path from 'node:path' - -// eslint-disable-next-line n/no-sync -const readFile = (fileName) => fs.readFileSync(path.join(__dirname, fileName), 'utf-8') - -export const signup = readFile('./signup.html') -export const passwordReset = readFile('./resetPassword.html') -export const wrongAccount = readFile('./wrongAccount.html') -export const emailVerification = readFile('./emailVerification.html') -export const chatMessage = readFile('./chatMessage.html') - -export const layout = readFile('./layout.html') diff --git a/backend/src/middleware/helpers/email/templates/layout.html b/backend/src/middleware/helpers/email/templates/layout.html deleted file mode 100644 index 0c68d6309..000000000 --- a/backend/src/middleware/helpers/email/templates/layout.html +++ /dev/null @@ -1,197 +0,0 @@ - - - - - - - - - - {{ subject }} - - - - - - - - - - - - - - - - - -
- - -
- - -

{{englishHint}}

- - {{> content}} - - - - - - -
-
- {{ORGANIZATION_NAME}} -
-
-
-
- - - -
- - -
- - - diff --git a/backend/src/middleware/helpers/email/templates/resetPassword.html b/backend/src/middleware/helpers/email/templates/resetPassword.html deleted file mode 100644 index 43c45455e..000000000 --- a/backend/src/middleware/helpers/email/templates/resetPassword.html +++ /dev/null @@ -1,185 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/backend/src/middleware/helpers/email/templates/signup.html b/backend/src/middleware/helpers/email/templates/signup.html deleted file mode 100644 index 4bf17fd61..000000000 --- a/backend/src/middleware/helpers/email/templates/signup.html +++ /dev/null @@ -1,214 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/backend/src/middleware/helpers/email/templates/wrongAccount.html b/backend/src/middleware/helpers/email/templates/wrongAccount.html deleted file mode 100644 index e8f71e9ea..000000000 --- a/backend/src/middleware/helpers/email/templates/wrongAccount.html +++ /dev/null @@ -1,185 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/backend/src/middleware/index.ts b/backend/src/middleware/index.ts index 896e5b33b..cc3af6bfc 100644 --- a/backend/src/middleware/index.ts +++ b/backend/src/middleware/index.ts @@ -1,7 +1,5 @@ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ + /* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { applyMiddleware, IMiddleware } from 'graphql-middleware' diff --git a/docker-compose.override.yml b/docker-compose.override.yml index e0f91c358..ae77abd5e 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -30,6 +30,8 @@ services: environment: - NODE_ENV="development" - DEBUG=true + - SMTP_PORT=1025 + - SMTP_HOST=mailserver volumes: - ./backend:/app