From 0856cf17d4ae1687dc7f8546dacfc2ac1a1f3e91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 18 Nov 2021 08:47:42 +0100 Subject: [PATCH 01/12] Use 'renderParams' in all e-mail templates --- .../helpers/email/templateBuilder.js | 28 ++++++------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/backend/src/middleware/helpers/email/templateBuilder.js b/backend/src/middleware/helpers/email/templateBuilder.js index 12624f5ac..3073c257c 100644 --- a/backend/src/middleware/helpers/email/templateBuilder.js +++ b/backend/src/middleware/helpers/email/templateBuilder.js @@ -31,16 +31,13 @@ export const signupTemplate = ({ email, nonce, inviteCode = null }) => { } else { actionUrl.searchParams.set('method', 'invite-mail') } + const renderParams = { ...defaultParams, englishHint, actionUrl, nonce, subject } return { from, to: email, subject, - html: mustache.render( - templates.layout, - { ...defaultParams, englishHint, actionUrl, nonce, subject }, - { content: templates.signup }, - ), + html: mustache.render(templates.layout, renderParams, { content: templates.signup }), } } @@ -49,16 +46,13 @@ export const emailVerificationTemplate = ({ email, nonce, name }) => { 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, - { ...defaultParams, englishHint, actionUrl, name, nonce, subject }, - { content: templates.emailVerification }, - ), + html: mustache.render(templates.layout, renderParams, { content: templates.emailVerification }), } } @@ -67,32 +61,26 @@ export const resetPasswordTemplate = ({ email, nonce, name }) => { 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, - { ...defaultParams, englishHint, actionUrl, name, nonce, subject }, - { content: templates.passwordReset }, - ), + html: mustache.render(templates.layout, renderParams, { content: templates.passwordReset }), } } export const wrongAccountTemplate = ({ email }) => { 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, - { ...defaultParams, englishHint, actionUrl }, - { content: templates.wrongAccount }, - ), + html: mustache.render(templates.layout, renderParams, { content: templates.wrongAccount }), } } From 6f4acaef61c5e9aa2342e9ce8d92de8dcbc30173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 18 Nov 2021 08:53:01 +0100 Subject: [PATCH 02/12] Test e-mail templates, just test file, first step --- .../helpers/email/templateBuilder.spec.js | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 backend/src/middleware/helpers/email/templateBuilder.spec.js diff --git a/backend/src/middleware/helpers/email/templateBuilder.spec.js b/backend/src/middleware/helpers/email/templateBuilder.spec.js new file mode 100644 index 000000000..459a87b3d --- /dev/null +++ b/backend/src/middleware/helpers/email/templateBuilder.spec.js @@ -0,0 +1,133 @@ +import CONFIG from '../../../config' +import logosWebapp from '../../../config/logos.js' +import { + signupTemplate, + // resetPasswordTemplate, + // wrongAccountTemplate, + // emailVerificationTemplate, + notificationTemplate, +} from './templateBuilder' + +const welcomeImageUrl = new URL(logosWebapp.LOGO_WELCOME_PATH, CONFIG.CLIENT_URI) +let actionUrl, name, settingsUrl + +const signupTemplateData = (_locale) => ({ + email: 'test@example.org', + variables: { + nonce: '12345', + inviteCode: 'AAAAAA', + }, +}) +const notificationTemplateData = (locale) => ({ + email: 'test@example.org', + variables: { + notification: { + to: { name: 'Mr Example', locale }, + }, + }, +}) +const testEmailData = (emailTemplate, templateBuilder, templateData, _locale, individuals) => { + if (!emailTemplate) { + emailTemplate = templateBuilder(templateData) + } + expect(emailTemplate.from).toEqual(CONFIG.EMAIL_DEFAULT_SENDER) + expect(emailTemplate.to).toEqual('test@example.org') + if (individuals.subject) { + expect(emailTemplate.subject).toEqual(individuals.subject) + } + expect(emailTemplate.html).toEqual(expect.stringContaining(welcomeImageUrl.toString())) + if (individuals.name) { + expect(emailTemplate.html).toEqual(expect.stringContaining(individuals.name)) + } + expect(emailTemplate.html).toEqual(expect.stringContaining(individuals.actionUrl.toString())) + expect(emailTemplate.html).toEqual(expect.stringContaining(CONFIG.ORGANIZATION_URL)) + expect(emailTemplate.html).toEqual(expect.stringContaining(CONFIG.APPLICATION_NAME)) + if (individuals.settingsUrl) { + expect(emailTemplate.html).toEqual(expect.stringContaining(individuals.settingsUrl.toString())) + } + expect(emailTemplate.html).toEqual(expect.stringContaining(individuals.content)) + return emailTemplate +} + +beforeAll(async () => { + // await cleanDatabase() +}) + +afterAll(async () => { + // await cleanDatabase() +}) + +describe('templateBuilder', () => { + describe('signupTemplate', () => { + describe('multi language', () => { + it('e-mail is build with all data', () => { + const actionUrl = new URL('/registration', CONFIG.CLIENT_URI) + const theSignupTemplateData = signupTemplateData() + + let content = "Thank you for joining our cause – it's awesome to have you on board." + const emailTemplate = testEmailData(null, signupTemplate, theSignupTemplateData, 'en', { + actionUrl, + content, + }) + + content = 'Danke, dass Du dich angemeldet hast – wir freuen uns, Dich dabei zu haben.' + testEmailData(emailTemplate, signupTemplate, theSignupTemplateData, 'de', { + actionUrl, + content, + }) + + expect(emailTemplate.html).toEqual( + expect.stringContaining(theSignupTemplateData.variables.nonce), + ) + expect(emailTemplate.html).toEqual( + expect.stringContaining(theSignupTemplateData.variables.inviteCode), + ) + }) + }) + }) + + // describe('XXX', () => { + // it('e-mail is build with all data', async () => { + // XXX({ + // email: 'test@example.org', + // XXX notification: notificationAdded, + // }) + // }) + // }) + + describe('notificationTemplate', () => { + beforeEach(() => { + actionUrl = new URL('/notifications', CONFIG.CLIENT_URI) + name = 'Mr Example' + 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'), 'en', { + subject, + actionUrl, + name, + settingsUrl, + content, + }) + }) + }) + + 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'), 'de', { + subject, + actionUrl, + name, + settingsUrl, + content, + }) + }) + }) + }) +}) From ce5161f9379ed403b9de0bb1939c49a579a7b04c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 18 Nov 2021 08:53:31 +0100 Subject: [PATCH 03/12] Test e-mail templates, remain files, first step --- backend/src/middleware/helpers/email/templateBuilder.js | 4 ++-- .../middleware/helpers/email/templates/de/notification.html | 3 +-- backend/src/middleware/login/loginMiddleware.js | 4 ++-- .../src/middleware/notifications/notificationsMiddleware.js | 1 - backend/src/middleware/slugifyMiddleware.spec.js | 4 ++-- backend/src/schema/resolvers/passwordReset.spec.js | 6 +++--- backend/src/schema/resolvers/registration.spec.js | 6 +++--- webapp/components/PasswordReset/ChangePassword.spec.js | 4 ++-- 8 files changed, 15 insertions(+), 17 deletions(-) diff --git a/backend/src/middleware/helpers/email/templateBuilder.js b/backend/src/middleware/helpers/email/templateBuilder.js index 3073c257c..5f615f01d 100644 --- a/backend/src/middleware/helpers/email/templateBuilder.js +++ b/backend/src/middleware/helpers/email/templateBuilder.js @@ -19,7 +19,7 @@ const defaultParams = { } const englishHint = 'English version below!' -export const signupTemplate = ({ email, nonce, inviteCode = null }) => { +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=wolle.huss%40pjannto.com&nonce=64853 const actionUrl = new URL('/registration', CONFIG.CLIENT_URI) @@ -84,7 +84,7 @@ export const wrongAccountTemplate = ({ email }) => { } } -export const notificationTemplate = ({ email, notification }) => { +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 } diff --git a/backend/src/middleware/helpers/email/templates/de/notification.html b/backend/src/middleware/helpers/email/templates/de/notification.html index d935424be..a54943310 100644 --- a/backend/src/middleware/helpers/email/templates/de/notification.html +++ b/backend/src/middleware/helpers/email/templates/de/notification.html @@ -25,8 +25,7 @@

Hallo {{ name }},

-

Du hast mindestens eine Benachrichtigung erhalten. Klick auf diesen Button, - um sie anzusehen:

+

Du hast mindestens eine Benachrichtigung erhalten. Klick auf diesen Button, um sie anzusehen:

diff --git a/backend/src/middleware/login/loginMiddleware.js b/backend/src/middleware/login/loginMiddleware.js index b7fe0239a..f2f1cde7f 100644 --- a/backend/src/middleware/login/loginMiddleware.js +++ b/backend/src/middleware/login/loginMiddleware.js @@ -11,9 +11,9 @@ const sendSignupMail = async (resolve, root, args, context, resolveInfo) => { const response = await resolve(root, args, context, resolveInfo) const { email, nonce } = response if (inviteCode) { - await sendMail(signupTemplate({ email, nonce, inviteCode })) + await sendMail(signupTemplate({ email, variables: { nonce, inviteCode } })) } else { - await sendMail(signupTemplate({ email, nonce })) + await sendMail(signupTemplate({ email, variables: { nonce } })) } delete response.nonce return response diff --git a/backend/src/middleware/notifications/notificationsMiddleware.js b/backend/src/middleware/notifications/notificationsMiddleware.js index 2bc53ab7c..5419771ea 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.js +++ b/backend/src/middleware/notifications/notificationsMiddleware.js @@ -41,7 +41,6 @@ const publishNotifications = async (context, promises) => { notifications.forEach((notificationAdded, index) => { pubsub.publish(NOTIFICATION_ADDED, { notificationAdded }) if (notificationAdded.to.sendNotificationEmails) { - // Wolle await sendMail( notificationTemplate({ email: notificationsEmailAddresses[index].email, diff --git a/backend/src/middleware/slugifyMiddleware.spec.js b/backend/src/middleware/slugifyMiddleware.spec.js index 9f1969c0c..7c6f18ab1 100644 --- a/backend/src/middleware/slugifyMiddleware.spec.js +++ b/backend/src/middleware/slugifyMiddleware.spec.js @@ -195,7 +195,7 @@ describe('slugifyMiddleware', () => { variables = { ...variables, name: 'I am a user', - nonce: '123456', + nonce: '12345', password: 'yo', email: '123@example.org', termsAndConditionsAgreedVersion: '0.0.1', @@ -206,7 +206,7 @@ describe('slugifyMiddleware', () => { beforeEach(async () => { await Factory.build('emailAddress', { email: '123@example.org', - nonce: '123456', + nonce: '12345', verifiedAt: null, }) }) diff --git a/backend/src/schema/resolvers/passwordReset.spec.js b/backend/src/schema/resolvers/passwordReset.spec.js index 0a66a7a64..3e55ee6fd 100644 --- a/backend/src/schema/resolvers/passwordReset.spec.js +++ b/backend/src/schema/resolvers/passwordReset.spec.js @@ -118,7 +118,7 @@ describe('passwordReset', () => { describe('resetPassword', () => { const setup = async (options = {}) => { - const { email = 'user@example.org', issuedAt = new Date(), nonce = 'abcdef' } = options + const { email = 'user@example.org', issuedAt = new Date(), nonce = 'abcde' } = options await createPasswordReset({ driver, email, issuedAt, nonce }) } @@ -148,7 +148,7 @@ describe('resetPassword', () => { describe('invalid email', () => { it('resolves to false', async () => { await setup() - variables = { ...variables, email: 'non-existent@example.org', nonce: 'abcdef' } + variables = { ...variables, email: 'non-existent@example.org', nonce: 'abcde' } await expect(mutate({ mutation, variables })).resolves.toMatchObject({ data: { resetPassword: false }, }) @@ -177,7 +177,7 @@ describe('resetPassword', () => { beforeEach(() => { variables = { ...variables, - nonce: 'abcdef', + nonce: 'abcde', } }) diff --git a/backend/src/schema/resolvers/registration.spec.js b/backend/src/schema/resolvers/registration.spec.js index 753639118..573af1d35 100644 --- a/backend/src/schema/resolvers/registration.spec.js +++ b/backend/src/schema/resolvers/registration.spec.js @@ -179,7 +179,7 @@ describe('SignupVerification', () => { beforeEach(async () => { variables = { ...variables, - nonce: '123456', + nonce: '12345', name: 'John Doe', password: '123', email: 'john@example.org', @@ -207,7 +207,7 @@ describe('SignupVerification', () => { describe('sending a valid nonce', () => { beforeEach(() => { - variables = { ...variables, nonce: '123456' } + variables = { ...variables, nonce: '12345' } }) it('rejects', async () => { @@ -222,7 +222,7 @@ describe('SignupVerification', () => { beforeEach(async () => { const args = { email: 'john@example.org', - nonce: '123456', + nonce: '12345', } await neode.model('EmailAddress').create(args) }) diff --git a/webapp/components/PasswordReset/ChangePassword.spec.js b/webapp/components/PasswordReset/ChangePassword.spec.js index d6f451604..cef110798 100644 --- a/webapp/components/PasswordReset/ChangePassword.spec.js +++ b/webapp/components/PasswordReset/ChangePassword.spec.js @@ -40,7 +40,7 @@ describe('ChangePassword ', () => { describe('given email and nonce', () => { beforeEach(() => { propsData.email = 'mail@example.org' - propsData.nonce = '123456' + propsData.nonce = '12345' }) describe('submitting new password', () => { @@ -57,7 +57,7 @@ describe('ChangePassword ', () => { it('delivers new password to backend', () => { const expected = expect.objectContaining({ - variables: { nonce: '123456', email: 'mail@example.org', password: 'supersecret' }, + variables: { nonce: '12345', email: 'mail@example.org', password: 'supersecret' }, }) expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected) }) From 7b0209b484ec43a4922edbe4fd659c9f993e0467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 18 Nov 2021 09:37:37 +0100 Subject: [PATCH 04/12] Test e-mail templates, finish signupTemplate --- .../helpers/email/templateBuilder.spec.js | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/backend/src/middleware/helpers/email/templateBuilder.spec.js b/backend/src/middleware/helpers/email/templateBuilder.spec.js index 459a87b3d..3761ccc8a 100644 --- a/backend/src/middleware/helpers/email/templateBuilder.spec.js +++ b/backend/src/middleware/helpers/email/templateBuilder.spec.js @@ -9,9 +9,10 @@ import { } from './templateBuilder' const welcomeImageUrl = new URL(logosWebapp.LOGO_WELCOME_PATH, CONFIG.CLIENT_URI) +const supportUrl = CONFIG.SUPPORT_URL let actionUrl, name, settingsUrl -const signupTemplateData = (_locale) => ({ +const signupTemplateData = () => ({ email: 'test@example.org', variables: { nonce: '12345', @@ -26,7 +27,7 @@ const notificationTemplateData = (locale) => ({ }, }, }) -const testEmailData = (emailTemplate, templateBuilder, templateData, _locale, individuals) => { +const testEmailData = (emailTemplate, templateBuilder, templateData, individuals) => { if (!emailTemplate) { emailTemplate = templateBuilder(templateData) } @@ -39,13 +40,18 @@ const testEmailData = (emailTemplate, templateBuilder, templateData, _locale, in if (individuals.name) { expect(emailTemplate.html).toEqual(expect.stringContaining(individuals.name)) } - expect(emailTemplate.html).toEqual(expect.stringContaining(individuals.actionUrl.toString())) + if (individuals.actionUrl) { + expect(emailTemplate.html).toEqual(expect.stringContaining(individuals.actionUrl.toString())) + } expect(emailTemplate.html).toEqual(expect.stringContaining(CONFIG.ORGANIZATION_URL)) expect(emailTemplate.html).toEqual(expect.stringContaining(CONFIG.APPLICATION_NAME)) if (individuals.settingsUrl) { expect(emailTemplate.html).toEqual(expect.stringContaining(individuals.settingsUrl.toString())) } expect(emailTemplate.html).toEqual(expect.stringContaining(individuals.content)) + if (individuals.supportUrl) { + expect(emailTemplate.html).toEqual(expect.stringContaining(individuals.supportUrl.toString())) + } return emailTemplate } @@ -63,19 +69,19 @@ describe('templateBuilder', () => { it('e-mail is build with all data', () => { const actionUrl = new URL('/registration', CONFIG.CLIENT_URI) const theSignupTemplateData = signupTemplateData() - + // locale: en let content = "Thank you for joining our cause – it's awesome to have you on board." - const emailTemplate = testEmailData(null, signupTemplate, theSignupTemplateData, 'en', { + const emailTemplate = testEmailData(null, signupTemplate, theSignupTemplateData, { actionUrl, content, + supportUrl, }) - + // locale: de content = 'Danke, dass Du dich angemeldet hast – wir freuen uns, Dich dabei zu haben.' - testEmailData(emailTemplate, signupTemplate, theSignupTemplateData, 'de', { - actionUrl, + testEmailData(emailTemplate, signupTemplate, theSignupTemplateData, { content, }) - + // test additional expect(emailTemplate.html).toEqual( expect.stringContaining(theSignupTemplateData.variables.nonce), ) @@ -106,7 +112,7 @@ describe('templateBuilder', () => { 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'), 'en', { + testEmailData(null, notificationTemplate, notificationTemplateData('en'), { subject, actionUrl, name, @@ -120,7 +126,7 @@ describe('templateBuilder', () => { 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'), 'de', { + testEmailData(null, notificationTemplate, notificationTemplateData('de'), { subject, actionUrl, name, From 8e1fda5e90c5b0b45aea9ffebf1beaf8840e2660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 18 Nov 2021 14:29:53 +0100 Subject: [PATCH 05/12] Refactor function testEmailData using an extendable array of tested strings --- .../helpers/email/templateBuilder.spec.js | 101 ++++++++++-------- 1 file changed, 57 insertions(+), 44 deletions(-) diff --git a/backend/src/middleware/helpers/email/templateBuilder.spec.js b/backend/src/middleware/helpers/email/templateBuilder.spec.js index 3761ccc8a..58fa5addd 100644 --- a/backend/src/middleware/helpers/email/templateBuilder.spec.js +++ b/backend/src/middleware/helpers/email/templateBuilder.spec.js @@ -9,7 +9,7 @@ import { } from './templateBuilder' const welcomeImageUrl = new URL(logosWebapp.LOGO_WELCOME_PATH, CONFIG.CLIENT_URI) -const supportUrl = CONFIG.SUPPORT_URL +const supportUrl = CONFIG.SUPPORT_URL.toString() let actionUrl, name, settingsUrl const signupTemplateData = () => ({ @@ -27,31 +27,37 @@ const notificationTemplateData = (locale) => ({ }, }, }) -const testEmailData = (emailTemplate, templateBuilder, templateData, individuals) => { +const textsStandard = [ + { + templPropName: 'from', + isContaining: false, + text: CONFIG.EMAIL_DEFAULT_SENDER, + }, + { + templPropName: 'to', + isContaining: false, + text: 'test@example.org', + }, + // is containing in html + welcomeImageUrl.toString(), + CONFIG.ORGANIZATION_URL, + CONFIG.APPLICATION_NAME, +] +const testEmailData = (emailTemplate, templateBuilder, templateData, texts) => { if (!emailTemplate) { emailTemplate = templateBuilder(templateData) } - expect(emailTemplate.from).toEqual(CONFIG.EMAIL_DEFAULT_SENDER) - expect(emailTemplate.to).toEqual('test@example.org') - if (individuals.subject) { - expect(emailTemplate.subject).toEqual(individuals.subject) - } - expect(emailTemplate.html).toEqual(expect.stringContaining(welcomeImageUrl.toString())) - if (individuals.name) { - expect(emailTemplate.html).toEqual(expect.stringContaining(individuals.name)) - } - if (individuals.actionUrl) { - expect(emailTemplate.html).toEqual(expect.stringContaining(individuals.actionUrl.toString())) - } - expect(emailTemplate.html).toEqual(expect.stringContaining(CONFIG.ORGANIZATION_URL)) - expect(emailTemplate.html).toEqual(expect.stringContaining(CONFIG.APPLICATION_NAME)) - if (individuals.settingsUrl) { - expect(emailTemplate.html).toEqual(expect.stringContaining(individuals.settingsUrl.toString())) - } - expect(emailTemplate.html).toEqual(expect.stringContaining(individuals.content)) - if (individuals.supportUrl) { - expect(emailTemplate.html).toEqual(expect.stringContaining(individuals.supportUrl.toString())) - } + 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 } @@ -67,27 +73,24 @@ describe('templateBuilder', () => { describe('signupTemplate', () => { describe('multi language', () => { it('e-mail is build with all data', () => { - const actionUrl = new URL('/registration', CONFIG.CLIENT_URI) + const actionUrl = new URL('/registration', CONFIG.CLIENT_URI).toString() const theSignupTemplateData = signupTemplateData() // locale: en let content = "Thank you for joining our cause – it's awesome to have you on board." - const emailTemplate = testEmailData(null, signupTemplate, theSignupTemplateData, { + const emailTemplate = testEmailData(null, signupTemplate, theSignupTemplateData, [ + ...textsStandard, actionUrl, content, supportUrl, - }) + theSignupTemplateData.variables.nonce, + theSignupTemplateData.variables.inviteCode, + ]) // locale: de content = 'Danke, dass Du dich angemeldet hast – wir freuen uns, Dich dabei zu haben.' - testEmailData(emailTemplate, signupTemplate, theSignupTemplateData, { + testEmailData(emailTemplate, signupTemplate, theSignupTemplateData, [ + // ...textsStandard, // tested at locale: en content, - }) - // test additional - expect(emailTemplate.html).toEqual( - expect.stringContaining(theSignupTemplateData.variables.nonce), - ) - expect(emailTemplate.html).toEqual( - expect.stringContaining(theSignupTemplateData.variables.inviteCode), - ) + ]) }) }) }) @@ -103,7 +106,7 @@ describe('templateBuilder', () => { describe('notificationTemplate', () => { beforeEach(() => { - actionUrl = new URL('/notifications', CONFIG.CLIENT_URI) + actionUrl = new URL('/notifications', CONFIG.CLIENT_URI).toString() name = 'Mr Example' settingsUrl = new URL('/settings/notifications', CONFIG.CLIENT_URI) }) @@ -112,13 +115,18 @@ describe('templateBuilder', () => { 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'), { - subject, + testEmailData(null, notificationTemplate, notificationTemplateData('en'), [ + ...textsStandard, + { + templPropName: 'subject', + isContaining: false, + text: subject, + }, actionUrl, name, - settingsUrl, content, - }) + settingsUrl, + ]) }) }) @@ -126,13 +134,18 @@ describe('templateBuilder', () => { 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'), { - subject, + testEmailData(null, notificationTemplate, notificationTemplateData('de'), [ + ...textsStandard, + { + templPropName: 'subject', + isContaining: false, + text: subject, + }, actionUrl, name, - settingsUrl, content, - }) + settingsUrl, + ]) }) }) }) From 36b621ca5b0894f46d5d6152eb2314c9a3b032d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 18 Nov 2021 16:15:04 +0100 Subject: [PATCH 06/12] Add remaining templates --- .../helpers/email/templateBuilder.js | 4 +- .../helpers/email/templateBuilder.spec.js | 161 ++++++++++++++++-- .../helpers/email/templates/wrongAccount.html | 8 +- 3 files changed, 155 insertions(+), 18 deletions(-) diff --git a/backend/src/middleware/helpers/email/templateBuilder.js b/backend/src/middleware/helpers/email/templateBuilder.js index 5f615f01d..1cbcca8b1 100644 --- a/backend/src/middleware/helpers/email/templateBuilder.js +++ b/backend/src/middleware/helpers/email/templateBuilder.js @@ -41,7 +41,7 @@ export const signupTemplate = ({ email, variables: { nonce, inviteCode = null } } } -export const emailVerificationTemplate = ({ email, nonce, name }) => { +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) @@ -56,7 +56,7 @@ export const emailVerificationTemplate = ({ email, nonce, name }) => { } } -export const resetPasswordTemplate = ({ email, nonce, name }) => { +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) diff --git a/backend/src/middleware/helpers/email/templateBuilder.spec.js b/backend/src/middleware/helpers/email/templateBuilder.spec.js index 58fa5addd..60683fab7 100644 --- a/backend/src/middleware/helpers/email/templateBuilder.spec.js +++ b/backend/src/middleware/helpers/email/templateBuilder.spec.js @@ -2,12 +2,13 @@ import CONFIG from '../../../config' import logosWebapp from '../../../config/logos.js' import { signupTemplate, - // resetPasswordTemplate, - // wrongAccountTemplate, - // emailVerificationTemplate, + emailVerificationTemplate, + resetPasswordTemplate, + wrongAccountTemplate, notificationTemplate, } 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 @@ -19,6 +20,24 @@ const signupTemplateData = () => ({ 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 wrongAccountTemplateData = () => ({ + email: 'test@example.org', + variables: {}, +}) const notificationTemplateData = (locale) => ({ email: 'test@example.org', variables: { @@ -61,29 +80,36 @@ const testEmailData = (emailTemplate, templateBuilder, templateData, texts) => { return emailTemplate } -beforeAll(async () => { - // await cleanDatabase() -}) +// beforeAll(async () => { +// await cleanDatabase() +// }) -afterAll(async () => { - // await cleanDatabase() -}) +// afterAll(async () => { +// await cleanDatabase() +// }) 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() // locale: en let content = "Thank you for joining our cause – it's awesome to have you on board." const emailTemplate = testEmailData(null, signupTemplate, theSignupTemplateData, [ ...textsStandard, + { + templPropName: 'subject', + isContaining: false, + text: subject, + }, + englishHint, actionUrl, - content, - supportUrl, theSignupTemplateData.variables.nonce, theSignupTemplateData.variables.inviteCode, + content, + supportUrl, ]) // locale: de content = 'Danke, dass Du dich angemeldet hast – wir freuen uns, Dich dabei zu haben.' @@ -95,6 +121,117 @@ describe('templateBuilder', () => { }) }) + 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() + // locale: en + let content = 'So, you want to change your e-mail? No problem!' + const emailTemplate = testEmailData( + null, + emailVerificationTemplate, + theEmailVerificationTemplateData, + [ + ...textsStandard, + { + templPropName: 'subject', + isContaining: false, + text: subject, + }, + englishHint, + actionUrl, + theEmailVerificationTemplateData.variables.nonce, + theEmailVerificationTemplateData.variables.name, + content, + supportUrl, + ], + ) + // locale: de + content = 'Du möchtest also deine E-Mail ändern? Kein Problem!' + testEmailData(emailTemplate, emailVerificationTemplate, theEmailVerificationTemplateData, [ + // ...textsStandard, // tested at locale: en + content, + ]) + }) + }) + }) + + 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() + // locale: en + let content = 'So, you forgot your password? No problem!' + const emailTemplate = testEmailData( + null, + resetPasswordTemplate, + theResetPasswordTemplateData, + [ + ...textsStandard, + { + templPropName: 'subject', + isContaining: false, + text: subject, + }, + englishHint, + actionUrl, + theResetPasswordTemplateData.variables.nonce, + theResetPasswordTemplateData.variables.name, + content, + supportUrl, + ], + ) + // locale: de + content = 'Du hast also dein Passwort vergessen? Kein Problem!' + testEmailData(emailTemplate, resetPasswordTemplate, theResetPasswordTemplateData, [ + // ...textsStandard, // tested at locale: en + content, + ]) + }) + }) + }) + + 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() + // locale: en + let content = + "You requested a password reset but unfortunately we couldn't find an account associated with your e-mail address." + const emailTemplate = testEmailData( + null, + wrongAccountTemplate, + theWrongAccountTemplateData, + [ + ...textsStandard, + { + templPropName: 'subject', + isContaining: false, + text: subject, + }, + englishHint, + actionUrl, + content, + supportUrl, + ], + ) + // locale: de + content = + 'Du hast bei uns ein neues Passwort angefordert – leider haben wir aber keinen Account mit Deiner E-Mailadresse gefunden.' + testEmailData(emailTemplate, wrongAccountTemplate, theWrongAccountTemplateData, [ + // ...textsStandard, // tested at locale: en + content, + ]) + }) + }) + }) + // describe('XXX', () => { // it('e-mail is build with all data', async () => { // XXX({ @@ -107,7 +244,7 @@ describe('templateBuilder', () => { describe('notificationTemplate', () => { beforeEach(() => { actionUrl = new URL('/notifications', CONFIG.CLIENT_URI).toString() - name = 'Mr Example' + name = notificationTemplateData('en').variables.notification.to.name settingsUrl = new URL('/settings/notifications', CONFIG.CLIENT_URI) }) diff --git a/backend/src/middleware/helpers/email/templates/wrongAccount.html b/backend/src/middleware/helpers/email/templates/wrongAccount.html index 8f17e3335..e8f71e9ea 100644 --- a/backend/src/middleware/helpers/email/templates/wrongAccount.html +++ b/backend/src/middleware/helpers/email/templates/wrongAccount.html @@ -24,8 +24,8 @@

Hallo!

-

Du hast bei uns ein neues Passwort angefordert – leider haben wir aber keinen - Account mit Deiner E-Mailadresse gefunden. Kann es sein, dass Du mit einer anderen Adresse bei uns +

Du hast bei uns ein neues Passwort angefordert – leider haben wir aber keinen Account mit Deiner E-Mailadresse gefunden. + Kann es sein, dass Du mit einer anderen Adresse bei uns angemeldet bist?

@@ -122,8 +122,8 @@

Hello!

-

You requested a password reset but unfortunately we couldn't find an account - associated with your e-mail address. Did you maybe use another one when you signed up?

+

You requested a password reset but unfortunately we couldn't find an account associated with your e-mail address. + Did you maybe use another one when you signed up?

From 0acd7f6501b14b48d44ace0b7cd7b8daed234c12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Mon, 22 Nov 2021 12:48:56 +0100 Subject: [PATCH 07/12] Remove 'wolle' from e-mail, because to many wolles in the code --- backend/src/middleware/helpers/email/templateBuilder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/middleware/helpers/email/templateBuilder.js b/backend/src/middleware/helpers/email/templateBuilder.js index 1cbcca8b1..0a95f6dd3 100644 --- a/backend/src/middleware/helpers/email/templateBuilder.js +++ b/backend/src/middleware/helpers/email/templateBuilder.js @@ -21,7 +21,7 @@ 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=wolle.huss%40pjannto.com&nonce=64853 + // 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) From a4df55072a67f7d329bc3d350948e3258c45c2f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Mon, 22 Nov 2021 13:10:10 +0100 Subject: [PATCH 08/12] Clean up --- .../src/middleware/helpers/email/templateBuilder.spec.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/backend/src/middleware/helpers/email/templateBuilder.spec.js b/backend/src/middleware/helpers/email/templateBuilder.spec.js index 60683fab7..bd9629d9f 100644 --- a/backend/src/middleware/helpers/email/templateBuilder.spec.js +++ b/backend/src/middleware/helpers/email/templateBuilder.spec.js @@ -232,15 +232,6 @@ describe('templateBuilder', () => { }) }) - // describe('XXX', () => { - // it('e-mail is build with all data', async () => { - // XXX({ - // email: 'test@example.org', - // XXX notification: notificationAdded, - // }) - // }) - // }) - describe('notificationTemplate', () => { beforeEach(() => { actionUrl = new URL('/notifications', CONFIG.CLIENT_URI).toString() From 78eb0fa97d1d41a4d9108202a1e15cef36ae07f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Fri, 6 May 2022 07:46:06 +0200 Subject: [PATCH 09/12] Refine test for e-mail templateBuilder --- .../helpers/email/templateBuilder.spec.js | 140 +++++++----------- 1 file changed, 53 insertions(+), 87 deletions(-) diff --git a/backend/src/middleware/helpers/email/templateBuilder.spec.js b/backend/src/middleware/helpers/email/templateBuilder.spec.js index bd9629d9f..7b4310483 100644 --- a/backend/src/middleware/helpers/email/templateBuilder.spec.js +++ b/backend/src/middleware/helpers/email/templateBuilder.spec.js @@ -57,7 +57,7 @@ const textsStandard = [ isContaining: false, text: 'test@example.org', }, - // is containing in html + // is contained in html welcomeImageUrl.toString(), CONFIG.ORGANIZATION_URL, CONFIG.APPLICATION_NAME, @@ -95,9 +95,10 @@ describe('templateBuilder', () => { const subject = `Willkommen, Bienvenue, Welcome to ${CONFIG.APPLICATION_NAME}!` const actionUrl = new URL('/registration', CONFIG.CLIENT_URI).toString() const theSignupTemplateData = signupTemplateData() - // locale: en - let content = "Thank you for joining our cause – it's awesome to have you on board." - const emailTemplate = testEmailData(null, signupTemplate, theSignupTemplateData, [ + 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', @@ -108,15 +109,10 @@ describe('templateBuilder', () => { actionUrl, theSignupTemplateData.variables.nonce, theSignupTemplateData.variables.inviteCode, - content, + enContent, + deContent, supportUrl, ]) - // locale: de - content = 'Danke, dass Du dich angemeldet hast – wir freuen uns, Dich dabei zu haben.' - testEmailData(emailTemplate, signupTemplate, theSignupTemplateData, [ - // ...textsStandard, // tested at locale: en - content, - ]) }) }) }) @@ -127,32 +123,22 @@ describe('templateBuilder', () => { 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() - // locale: en - let content = 'So, you want to change your e-mail? No problem!' - const emailTemplate = testEmailData( - null, - emailVerificationTemplate, - theEmailVerificationTemplateData, - [ - ...textsStandard, - { - templPropName: 'subject', - isContaining: false, - text: subject, - }, - englishHint, - actionUrl, - theEmailVerificationTemplateData.variables.nonce, - theEmailVerificationTemplateData.variables.name, - content, - supportUrl, - ], - ) - // locale: de - content = 'Du möchtest also deine E-Mail ändern? Kein Problem!' - testEmailData(emailTemplate, emailVerificationTemplate, theEmailVerificationTemplateData, [ - // ...textsStandard, // tested at locale: en - content, + 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, ]) }) }) @@ -164,32 +150,22 @@ describe('templateBuilder', () => { const subject = 'Neues Passwort | Reset Password' const actionUrl = new URL('/password-reset/change-password', CONFIG.CLIENT_URI).toString() const theResetPasswordTemplateData = resetPasswordTemplateData() - // locale: en - let content = 'So, you forgot your password? No problem!' - const emailTemplate = testEmailData( - null, - resetPasswordTemplate, - theResetPasswordTemplateData, - [ - ...textsStandard, - { - templPropName: 'subject', - isContaining: false, - text: subject, - }, - englishHint, - actionUrl, - theResetPasswordTemplateData.variables.nonce, - theResetPasswordTemplateData.variables.name, - content, - supportUrl, - ], - ) - // locale: de - content = 'Du hast also dein Passwort vergessen? Kein Problem!' - testEmailData(emailTemplate, resetPasswordTemplate, theResetPasswordTemplateData, [ - // ...textsStandard, // tested at locale: en - content, + 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, ]) }) }) @@ -201,32 +177,22 @@ describe('templateBuilder', () => { const subject = 'Falsche Mailadresse? | Wrong E-mail?' const actionUrl = new URL('/password-reset/request', CONFIG.CLIENT_URI).toString() const theWrongAccountTemplateData = wrongAccountTemplateData() - // locale: en - let content = + const enContent = "You requested a password reset but unfortunately we couldn't find an account associated with your e-mail address." - const emailTemplate = testEmailData( - null, - wrongAccountTemplate, - theWrongAccountTemplateData, - [ - ...textsStandard, - { - templPropName: 'subject', - isContaining: false, - text: subject, - }, - englishHint, - actionUrl, - content, - supportUrl, - ], - ) - // locale: de - content = + const deContent = 'Du hast bei uns ein neues Passwort angefordert – leider haben wir aber keinen Account mit Deiner E-Mailadresse gefunden.' - testEmailData(emailTemplate, wrongAccountTemplate, theWrongAccountTemplateData, [ - // ...textsStandard, // tested at locale: en - content, + testEmailData(null, wrongAccountTemplate, theWrongAccountTemplateData, [ + ...textsStandard, + { + templPropName: 'subject', + isContaining: false, + text: subject, + }, + englishHint, + actionUrl, + enContent, + deContent, + supportUrl, ]) }) }) From 5cd1b95907b8d0f9886ca0583f9e50bad27611d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 23 Jun 2022 15:51:36 +0200 Subject: [PATCH 10/12] Fix templates 'variables' argument --- backend/src/middleware/helpers/email/templateBuilder.js | 2 +- backend/src/middleware/login/loginMiddleware.js | 4 ++-- .../src/middleware/notifications/notificationsMiddleware.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/src/middleware/helpers/email/templateBuilder.js b/backend/src/middleware/helpers/email/templateBuilder.js index 0a95f6dd3..bf174ef48 100644 --- a/backend/src/middleware/helpers/email/templateBuilder.js +++ b/backend/src/middleware/helpers/email/templateBuilder.js @@ -71,7 +71,7 @@ export const resetPasswordTemplate = ({ email, variables: { nonce, name } }) => } } -export const wrongAccountTemplate = ({ email }) => { +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 } diff --git a/backend/src/middleware/login/loginMiddleware.js b/backend/src/middleware/login/loginMiddleware.js index f2f1cde7f..3cd2158c2 100644 --- a/backend/src/middleware/login/loginMiddleware.js +++ b/backend/src/middleware/login/loginMiddleware.js @@ -23,14 +23,14 @@ const sendPasswordResetMail = async (resolve, root, args, context, resolveInfo) const { email } = args const { email: userFound, nonce, name } = await resolve(root, args, context, resolveInfo) const template = userFound ? resetPasswordTemplate : wrongAccountTemplate - await sendMail(template({ email, nonce, name })) + await sendMail(template({ email, variables: { nonce, name } })) return true } const sendEmailVerificationMail = async (resolve, root, args, context, resolveInfo) => { const response = await resolve(root, args, context, resolveInfo) const { email, nonce, name } = response - await sendMail(emailVerificationTemplate({ email, nonce, name })) + await sendMail(emailVerificationTemplate({ email, variables: { nonce, name } })) delete response.nonce return response } diff --git a/backend/src/middleware/notifications/notificationsMiddleware.js b/backend/src/middleware/notifications/notificationsMiddleware.js index 5419771ea..a871605f7 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.js +++ b/backend/src/middleware/notifications/notificationsMiddleware.js @@ -44,7 +44,7 @@ const publishNotifications = async (context, promises) => { sendMail( notificationTemplate({ email: notificationsEmailAddresses[index].email, - notification: notificationAdded, + variables: { notification: notificationAdded }, }), ) } From 5abbf22b43dd9c4c71fc7e3dc9b37b54576d8d42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 23 Jun 2022 17:44:59 +0200 Subject: [PATCH 11/12] Implement 'inviteCode' and 'nonce' lengths via 'registration.js' in backend and webapp --- backend/src/constants/registration.js | 5 +++++ backend/src/schema/resolvers/emails.spec.js | 2 +- .../resolvers/helpers/generateInviteCode.js | 15 ++++++++++----- .../src/schema/resolvers/helpers/generateNonce.js | 12 +++++++++--- backend/src/schema/resolvers/inviteCodes.spec.js | 13 +++++++++++-- backend/src/schema/resolvers/passwordReset.js | 4 +++- .../src/schema/resolvers/passwordReset.spec.js | 11 ++++++----- .../Registration/RegistrationSlideInvite.vue | 9 ++++++--- .../Registration/RegistrationSlideNonce.vue | 10 +++++++--- webapp/constants/registration.js | 5 +++++ webapp/locales/de.json | 4 ++-- webapp/locales/en.json | 4 ++-- webapp/locales/es.json | 2 +- webapp/locales/fr.json | 2 +- webapp/locales/pl.json | 2 +- webapp/locales/pt.json | 2 +- webapp/locales/ru.json | 2 +- 17 files changed, 72 insertions(+), 32 deletions(-) create mode 100644 backend/src/constants/registration.js create mode 100644 webapp/constants/registration.js diff --git a/backend/src/constants/registration.js b/backend/src/constants/registration.js new file mode 100644 index 000000000..9e63e478e --- /dev/null +++ b/backend/src/constants/registration.js @@ -0,0 +1,5 @@ +// this file is duplicated in `backend/src/config/metadata.js` and `webapp/constants/metadata.js` +export default { + NONCE_LENGTH: 5, + INVITE_CODE_LENGTH: 6, +} diff --git a/backend/src/schema/resolvers/emails.spec.js b/backend/src/schema/resolvers/emails.spec.js index 60f5ae899..39b70ac0b 100644 --- a/backend/src/schema/resolvers/emails.spec.js +++ b/backend/src/schema/resolvers/emails.spec.js @@ -158,7 +158,7 @@ describe('VerifyEmailAddress', () => { ` beforeEach(() => { - variables = { ...variables, email: 'to-be-verified@example.org', nonce: '123456' } + variables = { ...variables, email: 'to-be-verified@example.org', nonce: '12345' } }) describe('unauthenticated', () => { diff --git a/backend/src/schema/resolvers/helpers/generateInviteCode.js b/backend/src/schema/resolvers/helpers/generateInviteCode.js index 70e122d26..99c752eb0 100644 --- a/backend/src/schema/resolvers/helpers/generateInviteCode.js +++ b/backend/src/schema/resolvers/helpers/generateInviteCode.js @@ -1,8 +1,13 @@ +import CONSTANTS_REGISTRATION from './../../../constants/registration' + export default function generateInviteCode() { // 6 random numbers in [ 0, 35 ] are 36 possible numbers (10 [0-9] + 26 [A-Z]) - return Array.from({ length: 6 }, (n = Math.floor(Math.random() * 36)) => { - // n > 9: it is a letter (ASCII 65 is A) -> 10 + 55 = 65 - // else: it is a number (ASCII 48 is 0) -> 0 + 48 = 48 - return String.fromCharCode(n > 9 ? n + 55 : n + 48) - }).join('') + return Array.from( + { length: CONSTANTS_REGISTRATION.INVITE_CODE_LENGTH }, + (n = Math.floor(Math.random() * 36)) => { + // n > 9: it is a letter (ASCII 65 is A) -> 10 + 55 = 65 + // else: it is a number (ASCII 48 is 0) -> 0 + 48 = 48 + return String.fromCharCode(n > 9 ? n + 55 : n + 48) + }, + ).join('') } diff --git a/backend/src/schema/resolvers/helpers/generateNonce.js b/backend/src/schema/resolvers/helpers/generateNonce.js index 6da40b5c2..50aa8489e 100644 --- a/backend/src/schema/resolvers/helpers/generateNonce.js +++ b/backend/src/schema/resolvers/helpers/generateNonce.js @@ -1,5 +1,11 @@ +import CONSTANTS_REGISTRATION from './../../../constants/registration' + +// TODO: why this is not used in resolver 'requestPasswordReset'? export default function generateNonce() { - return Array.from({ length: 5 }, (n = Math.floor(Math.random() * 10)) => { - return String.fromCharCode(n + 48) - }).join('') + return Array.from( + { length: CONSTANTS_REGISTRATION.NONCE_LENGTH }, + (n = Math.floor(Math.random() * 10)) => { + return String.fromCharCode(n + 48) + }, + ).join('') } diff --git a/backend/src/schema/resolvers/inviteCodes.spec.js b/backend/src/schema/resolvers/inviteCodes.spec.js index 094716871..72e2a2492 100644 --- a/backend/src/schema/resolvers/inviteCodes.spec.js +++ b/backend/src/schema/resolvers/inviteCodes.spec.js @@ -3,6 +3,7 @@ import { getDriver } from '../../db/neo4j' import { gql } from '../../helpers/jest' import createServer from '../../server' import { createTestClient } from 'apollo-server-testing' +import CONSTANTS_REGISTRATION from './../../constants/registration' let user let query @@ -107,7 +108,11 @@ describe('inviteCodes', () => { errors: undefined, data: { GenerateInviteCode: { - code: expect.stringMatching(/^[0-9A-Z]{6,6}$/), + code: expect.stringMatching( + new RegExp( + `^[0-9A-Z]{${CONSTANTS_REGISTRATION.INVITE_CODE_LENGTH},${CONSTANTS_REGISTRATION.INVITE_CODE_LENGTH}}$`, + ), + ), expiresAt: null, createdAt: expect.any(String), }, @@ -129,7 +134,11 @@ describe('inviteCodes', () => { errors: undefined, data: { GenerateInviteCode: { - code: expect.stringMatching(/^[0-9A-Z]{6,6}$/), + code: expect.stringMatching( + new RegExp( + `^[0-9A-Z]{${CONSTANTS_REGISTRATION.INVITE_CODE_LENGTH},${CONSTANTS_REGISTRATION.INVITE_CODE_LENGTH}}$`, + ), + ), expiresAt: nextWeek.toISOString(), createdAt: expect.any(String), }, diff --git a/backend/src/schema/resolvers/passwordReset.js b/backend/src/schema/resolvers/passwordReset.js index d1f49876b..6fea020dd 100644 --- a/backend/src/schema/resolvers/passwordReset.js +++ b/backend/src/schema/resolvers/passwordReset.js @@ -1,11 +1,13 @@ import { v4 as uuid } from 'uuid' import bcrypt from 'bcryptjs' +import CONSTANTS_REGISTRATION from './../../constants/registration' import createPasswordReset from './helpers/createPasswordReset' export default { Mutation: { requestPasswordReset: async (_parent, { email }, { driver }) => { - const nonce = uuid().substring(0, 6) + // TODO: why this is generated differntly from 'backend/src/schema/resolvers/helpers/generateNonce.js'? + const nonce = uuid().substring(0, CONSTANTS_REGISTRATION.NONCE_LENGTH) return createPasswordReset({ driver, nonce, email }) }, resetPassword: async (_parent, { email, nonce, newPassword }, { driver }) => { diff --git a/backend/src/schema/resolvers/passwordReset.spec.js b/backend/src/schema/resolvers/passwordReset.spec.js index 3e55ee6fd..bc2a48bc8 100644 --- a/backend/src/schema/resolvers/passwordReset.spec.js +++ b/backend/src/schema/resolvers/passwordReset.spec.js @@ -1,6 +1,7 @@ import Factory, { cleanDatabase } from '../../db/factories' import { gql } from '../../helpers/jest' import { getNeode, getDriver } from '../../db/neo4j' +import CONSTANTS_REGISTRATION from './../../constants/registration' import createPasswordReset from './helpers/createPasswordReset' import createServer from '../../server' import { createTestClient } from 'apollo-server-testing' @@ -109,7 +110,7 @@ describe('passwordReset', () => { const resets = await getAllPasswordResets() const [reset] = resets const { nonce } = reset.properties - expect(nonce).toHaveLength(6) + expect(nonce).toHaveLength(CONSTANTS_REGISTRATION.NONCE_LENGTH) }) }) }) @@ -118,7 +119,7 @@ describe('passwordReset', () => { describe('resetPassword', () => { const setup = async (options = {}) => { - const { email = 'user@example.org', issuedAt = new Date(), nonce = 'abcde' } = options + const { email = 'user@example.org', issuedAt = new Date(), nonce = '12345' } = options await createPasswordReset({ driver, email, issuedAt, nonce }) } @@ -148,7 +149,7 @@ describe('resetPassword', () => { describe('invalid email', () => { it('resolves to false', async () => { await setup() - variables = { ...variables, email: 'non-existent@example.org', nonce: 'abcde' } + variables = { ...variables, email: 'non-existent@example.org', nonce: '12345' } await expect(mutate({ mutation, variables })).resolves.toMatchObject({ data: { resetPassword: false }, }) @@ -162,7 +163,7 @@ describe('resetPassword', () => { describe('but invalid nonce', () => { beforeEach(() => { - variables = { ...variables, nonce: 'slkdjf' } + variables = { ...variables, nonce: 'slkdj' } }) it('resolves to false', async () => { @@ -177,7 +178,7 @@ describe('resetPassword', () => { beforeEach(() => { variables = { ...variables, - nonce: 'abcde', + nonce: '12345', } }) diff --git a/webapp/components/Registration/RegistrationSlideInvite.vue b/webapp/components/Registration/RegistrationSlideInvite.vue index 5a3aee777..f9ce15ee9 100644 --- a/webapp/components/Registration/RegistrationSlideInvite.vue +++ b/webapp/components/Registration/RegistrationSlideInvite.vue @@ -22,6 +22,7 @@