diff --git a/backend/.env.dist b/backend/.env.dist index 41eeeaf58..db01cf4cc 100644 --- a/backend/.env.dist +++ b/backend/.env.dist @@ -1,4 +1,4 @@ -CONFIG_VERSION=v7.2022-06-15 +CONFIG_VERSION=v8.2022-06-20 # Server PORT=4000 diff --git a/backend/.env.template b/backend/.env.template index 284abc204..94bf41550 100644 --- a/backend/.env.template +++ b/backend/.env.template @@ -43,6 +43,7 @@ EMAIL_SMTP_URL=$EMAIL_SMTP_URL EMAIL_SMTP_PORT=587 EMAIL_LINK_VERIFICATION=$EMAIL_LINK_VERIFICATION EMAIL_LINK_SETPASSWORD=$EMAIL_LINK_SETPASSWORD +EMAIL_LINK_FORGOTPASSWORD=$EMAIL_LINK_FORGOTPASSWORD EMAIL_LINK_OVERVIEW=$EMAIL_LINK_OVERVIEW EMAIL_CODE_VALID_TIME=$EMAIL_CODE_VALID_TIME EMAIL_CODE_REQUEST_TIME=$EMAIL_CODE_REQUEST_TIME diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 4e6dd8099..a9cae6770 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -17,7 +17,7 @@ const constants = { LOG_LEVEL: process.env.LOG_LEVEL || 'info', CONFIG_VERSION: { DEFAULT: 'DEFAULT', - EXPECTED: 'v7.2022-06-15', + EXPECTED: 'v8.2022-06-20', CURRENT: '', }, } diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index 48fe667a9..a2a499224 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -11,6 +11,7 @@ import { LoginEmailOptIn } from '@entity/LoginEmailOptIn' import { User } from '@entity/User' import CONFIG from '@/config' import { sendAccountActivationEmail } from '@/mailer/sendAccountActivationEmail' +import { sendAccountMultiRegistrationEmail } from '@/mailer/sendAccountMultiRegistrationEmail' import { sendResetPasswordEmail } from '@/mailer/sendResetPasswordEmail' import { printTimeDuration, activationLink } from './UserResolver' import { contributionLinkFactory } from '@/seeds/factory/contributionLink' @@ -29,6 +30,13 @@ jest.mock('@/mailer/sendAccountActivationEmail', () => { } }) +jest.mock('@/mailer/sendAccountMultiRegistrationEmail', () => { + return { + __esModule: true, + sendAccountMultiRegistrationEmail: jest.fn(), + } +}) + jest.mock('@/mailer/sendResetPasswordEmail', () => { return { __esModule: true, @@ -156,14 +164,33 @@ describe('UserResolver', () => { }) describe('email already exists', () => { - it('throws and logs an error', async () => { - const mutation = await mutate({ mutation: createUser, variables }) + let mutation: User + beforeAll(async () => { + mutation = await mutate({ mutation: createUser, variables }) + }) + + it('logs an info', async () => { + expect(logger.info).toBeCalledWith('User already exists with this email=peter@lustig.de') + }) + + it('sends an account multi registration email', () => { + expect(sendAccountMultiRegistrationEmail).toBeCalledWith({ + firstName: 'Peter', + lastName: 'Lustig', + email: 'peter@lustig.de', + }) + }) + + it('results with partly faked user with random "id"', async () => { expect(mutation).toEqual( expect.objectContaining({ - errors: [new GraphQLError('User already exists.')], + data: { + createUser: { + id: expect.any(Number), + }, + }, }), ) - expect(logger.error).toBeCalledWith('User already exists with this email=peter@lustig.de') }) }) diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index 224834f17..0bde22ae6 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -7,6 +7,7 @@ import { getConnection } from '@dbTools/typeorm' import CONFIG from '@/config' import { User } from '@model/User' import { User as DbUser } from '@entity/User' +import { communityDbUser } from '@/util/communityUser' import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink' import { ContributionLink as dbContributionLink } from '@entity/ContributionLink' import { encode } from '@/auth/JWT' @@ -18,6 +19,7 @@ import { OptInType } from '@enum/OptInType' import { LoginEmailOptIn } from '@entity/LoginEmailOptIn' import { sendResetPasswordEmail as sendResetPasswordEmailMailer } from '@/mailer/sendResetPasswordEmail' import { sendAccountActivationEmail } from '@/mailer/sendAccountActivationEmail' +import { sendAccountMultiRegistrationEmail } from '@/mailer/sendAccountMultiRegistrationEmail' import { klicktippSignIn } from '@/apis/KlicktippController' import { RIGHTS } from '@/auth/RIGHTS' import { hasElopageBuys } from '@/util/hasElopageBuys' @@ -328,10 +330,35 @@ export class UserResolver { // TODO we cannot use repository.count(), since it does not allow to specify if you want to include the soft deletes const userFound = await DbUser.findOne({ email }, { withDeleted: true }) logger.info(`DbUser.findOne(email=${email}) = ${userFound}`) + if (userFound) { - logger.error('User already exists with this email=' + email) + logger.info('User already exists with this email=' + email) // TODO: this is unsecure, but the current implementation of the login server. This way it can be queried if the user with given EMail is existent. - throw new Error(`User already exists.`) + + const user = new User(communityDbUser) + user.id = sodium.randombytes_random() % (2048 * 16) // TODO: for a better faking derive id from email so that it will be always the same id when the same email comes in? + user.email = email + user.firstName = firstName + user.lastName = lastName + user.language = language + user.publisherId = publisherId + logger.debug('partly faked user=' + user) + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const emailSent = await sendAccountMultiRegistrationEmail({ + firstName, + lastName, + email, + }) + logger.info(`sendAccountMultiRegistrationEmail of ${firstName}.${lastName} to ${email}`) + /* uncomment this, when you need the activation link on the console */ + // In case EMails are disabled log the activation link for the user + if (!emailSent) { + logger.debug(`Email not send!`) + } + logger.info('createUser() faked and send multi registration mail...') + + return user } const passphrase = PassphraseGenerate() @@ -417,6 +444,7 @@ export class UserResolver { await queryRunner.release() } logger.info('createUser() successful...') + return new User(dbUser) } diff --git a/backend/src/mailer/sendAccountMultiRegistrationEmail.test.ts b/backend/src/mailer/sendAccountMultiRegistrationEmail.test.ts new file mode 100644 index 000000000..bb37a196e --- /dev/null +++ b/backend/src/mailer/sendAccountMultiRegistrationEmail.test.ts @@ -0,0 +1,31 @@ +import CONFIG from '@/config' +import { sendAccountMultiRegistrationEmail } from './sendAccountMultiRegistrationEmail' +import { sendEMail } from './sendEMail' + +jest.mock('./sendEMail', () => { + return { + __esModule: true, + sendEMail: jest.fn(), + } +}) + +describe('sendAccountMultiRegistrationEmail', () => { + beforeEach(async () => { + await sendAccountMultiRegistrationEmail({ + firstName: 'Peter', + lastName: 'Lustig', + email: 'peter@lustig.de', + }) + }) + + it('calls sendEMail', () => { + expect(sendEMail).toBeCalledWith({ + to: `Peter Lustig `, + subject: 'Gradido: Erneuter Registrierungsversuch mit deiner E-Mail', + text: + expect.stringContaining('Hallo Peter Lustig') && + expect.stringContaining(CONFIG.EMAIL_LINK_FORGOTPASSWORD) && + expect.stringContaining('https://gradido.net/de/contact/'), + }) + }) +}) diff --git a/backend/src/mailer/sendAccountMultiRegistrationEmail.ts b/backend/src/mailer/sendAccountMultiRegistrationEmail.ts new file mode 100644 index 000000000..18928770b --- /dev/null +++ b/backend/src/mailer/sendAccountMultiRegistrationEmail.ts @@ -0,0 +1,18 @@ +import { sendEMail } from './sendEMail' +import { accountMultiRegistration } from './text/accountMultiRegistration' +import CONFIG from '@/config' + +export const sendAccountMultiRegistrationEmail = (data: { + firstName: string + lastName: string + email: string +}): Promise => { + return sendEMail({ + to: `${data.firstName} ${data.lastName} <${data.email}>`, + subject: accountMultiRegistration.de.subject, + text: accountMultiRegistration.de.text({ + ...data, + resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD, + }), + }) +} diff --git a/backend/src/mailer/sendEMail.test.ts b/backend/src/mailer/sendEMail.test.ts index 8a13c027d..5746f1ead 100644 --- a/backend/src/mailer/sendEMail.test.ts +++ b/backend/src/mailer/sendEMail.test.ts @@ -31,6 +31,7 @@ describe('sendEMail', () => { beforeEach(async () => { result = await sendEMail({ to: 'receiver@mail.org', + cc: 'support@gradido.net', subject: 'Subject', text: 'Text text text', }) @@ -50,6 +51,7 @@ describe('sendEMail', () => { CONFIG.EMAIL = true result = await sendEMail({ to: 'receiver@mail.org', + cc: 'support@gradido.net', subject: 'Subject', text: 'Text text text', }) @@ -72,6 +74,7 @@ describe('sendEMail', () => { expect((createTransport as jest.Mock).mock.results[0].value.sendMail).toBeCalledWith({ from: `Gradido (nicht antworten) <${CONFIG.EMAIL_SENDER}>`, to: 'receiver@mail.org', + cc: 'support@gradido.net', subject: 'Subject', text: 'Text text text', }) diff --git a/backend/src/mailer/sendEMail.ts b/backend/src/mailer/sendEMail.ts index 640dd7f4c..746daa810 100644 --- a/backend/src/mailer/sendEMail.ts +++ b/backend/src/mailer/sendEMail.ts @@ -5,10 +5,15 @@ import CONFIG from '@/config' export const sendEMail = async (emailDef: { to: string + cc?: string subject: string text: string }): Promise => { - logger.info(`send Email: to=${emailDef.to}, subject=${emailDef.subject}, text=${emailDef.text}`) + logger.info( + `send Email: to=${emailDef.to}` + + (emailDef.cc ? `, cc=${emailDef.cc}` : '') + + `, subject=${emailDef.subject}, text=${emailDef.text}`, + ) if (!CONFIG.EMAIL) { logger.info(`Emails are disabled via config...`) diff --git a/backend/src/mailer/text/accountMultiRegistration.ts b/backend/src/mailer/text/accountMultiRegistration.ts new file mode 100644 index 000000000..c5b55bac5 --- /dev/null +++ b/backend/src/mailer/text/accountMultiRegistration.ts @@ -0,0 +1,25 @@ +export const accountMultiRegistration = { + de: { + subject: 'Gradido: Erneuter Registrierungsversuch mit deiner E-Mail', + text: (data: { + firstName: string + lastName: string + email: string + resendLink: string + }): string => + `Hallo ${data.firstName} ${data.lastName}, + +Deine E-Mail-Adresse wurde soeben erneut benutzt, um bei Gradido ein Konto zu registrieren. +Es existiert jedoch zu deiner E-Mail-Adresse schon ein Konto. + +Klicke bitte auf den folgenden Link, falls du dein Passwort vergessen haben solltest: +${data.resendLink} +oder kopiere den obigen Link in dein Browserfenster. + +Wenn du nicht derjenige bist, der sich versucht hat erneut zu registrieren, wende dich bitte an unseren support: +https://gradido.net/de/contact/ + +Mit freundlichen Grüßen, +dein Gradido-Team`, + }, +} diff --git a/deployment/bare_metal/.env.dist b/deployment/bare_metal/.env.dist index d9e159382..67c153661 100644 --- a/deployment/bare_metal/.env.dist +++ b/deployment/bare_metal/.env.dist @@ -26,7 +26,7 @@ COMMUNITY_REDEEM_CONTRIBUTION_URL=https://stage1.gradido.net/redeem/CL-{code} COMMUNITY_DESCRIPTION="Gradido Development Stage1 Test Community" # backend -BACKEND_CONFIG_VERSION=v7.2022-06-15 +BACKEND_CONFIG_VERSION=v8.2022-06-20 JWT_EXPIRES_IN=30m GDT_API_URL=https://gdt.gradido.net diff --git a/frontend/src/locales/de.json b/frontend/src/locales/de.json index df621ed0b..5d96e428a 100644 --- a/frontend/src/locales/de.json +++ b/frontend/src/locales/de.json @@ -57,8 +57,7 @@ "no-transactionlist": "Es gab leider einen Fehler. Es wurden keine Transaktionen vom Server übermittelt.", "no-user": "Kein Benutzer mit diesen Anmeldedaten.", "session-expired": "Die Sitzung wurde aus Sicherheitsgründen beendet.", - "unknown-error": "Unbekanter Fehler: ", - "user-already-exists": "Ein Benutzer mit diesen Daten existiert bereits." + "unknown-error": "Unbekannter Fehler: " }, "followUs": "folge uns:", "footer": { diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index c531f39f6..c297ec176 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -57,8 +57,7 @@ "no-transactionlist": "Unfortunately, there was an error. No transactions have been sent from the server.", "no-user": "No user with this credentials.", "session-expired": "The session was closed for security reasons.", - "unknown-error": "Unknown error: ", - "user-already-exists": "A user with this data already exists." + "unknown-error": "Unknown error: " }, "followUs": "follow us:", "footer": { diff --git a/frontend/src/pages/Register.spec.js b/frontend/src/pages/Register.spec.js index a9415de87..91e945c6a 100644 --- a/frontend/src/pages/Register.spec.js +++ b/frontend/src/pages/Register.spec.js @@ -139,24 +139,6 @@ describe('Register', () => { await flushPromises() } - describe('server sends back error "User already exists."', () => { - beforeEach(async () => { - await createError('GraphQL error: User already exists.') - }) - - it('shows no error message on the page', () => { - // don't show any error on the page! against boots - expect(wrapper.vm.showPageMessage).toBe(false) - expect(wrapper.find('.test-message-headline').exists()).toBe(false) - expect(wrapper.find('.test-message-subtitle').exists()).toBe(false) - expect(wrapper.find('.test-message-button').exists()).toBe(false) - }) - - it('toasts the error message', () => { - expect(toastErrorSpy).toBeCalledWith('error.user-already-exists') - }) - }) - describe('server sends back error "Unknown error"', () => { beforeEach(async () => { await createError(' – Unknown error.') diff --git a/frontend/src/pages/Register.vue b/frontend/src/pages/Register.vue index f1bfad6d9..0ae316ae9 100755 --- a/frontend/src/pages/Register.vue +++ b/frontend/src/pages/Register.vue @@ -122,9 +122,6 @@ export default { getValidationState({ dirty, validated, valid = null }) { return dirty || validated ? valid : null }, - commitStorePublisherId(val) { - this.$store.commit('publisherId', val) - }, async onSubmit() { this.$apollo .mutate({ @@ -142,16 +139,7 @@ export default { this.showPageMessage = true }) .catch((error) => { - let errorMessage - switch (error.message) { - case 'GraphQL error: User already exists.': - errorMessage = this.$t('error.user-already-exists') - break - default: - errorMessage = this.$t('error.unknown-error') + error.message - break - } - this.toastError(errorMessage) + this.toastError(this.$t('error.unknown-error') + error.message) }) }, },