From a8dded2263019b6ce1410869643ac420532d5e1e Mon Sep 17 00:00:00 2001 From: roschaefer Date: Tue, 17 Sep 2019 17:35:56 +0200 Subject: [PATCH] Fix #1616 --- backend/src/schema/resolvers/registration.js | 46 ++++++++++----- .../src/schema/resolvers/registration.spec.js | 59 +++++++++++++++++-- backend/src/seed/factories/users.js | 10 +++- 3 files changed, 95 insertions(+), 20 deletions(-) diff --git a/backend/src/schema/resolvers/registration.js b/backend/src/schema/resolvers/registration.js index fa7262f43..72e499038 100644 --- a/backend/src/schema/resolvers/registration.js +++ b/backend/src/schema/resolvers/registration.js @@ -6,14 +6,30 @@ import encryptPassword from '../../helpers/encryptPassword' const instance = neode() -/* - * TODO: remove this function as soon type `User` has no `email` property - * anymore - */ -const checkEmailDoesNotExist = async ({ email }) => { +const alreadyExistingMail = async (_parent, args, context) => { + let { email } = args email = email.toLowerCase() - const emails = await instance.all('EmailAddress', { email }) - if (emails.length > 0) throw new UserInputError('User account with this email already exists.') + const cypher = ` + MATCH (email:EmailAddress {email: $email}) + OPTIONAL MATCH (email)-[:PRIMARY_EMAIL]-(user) + RETURN email, user + ` + let transactionRes + const session = context.driver.session() + try { + transactionRes = await session.run(cypher, { email }) + } finally { + session.close() + } + const [result] = transactionRes.records.map(record => { + return { + alreadyExistingEmail: record.get('email').properties, + user: record.get('user') && record.get('user').properties, + } + }) + const { alreadyExistingEmail, user } = result || {} + if (user) throw new UserInputError('User account with this email already exists.') + return alreadyExistingEmail } export default { @@ -37,22 +53,24 @@ export default { } return response }, - Signup: async (_parent, args, _context, _resolveInfo) => { + Signup: async (_parent, args, context) => { const nonce = uuid().substring(0, 6) args.nonce = nonce - await checkEmailDoesNotExist({ email: args.email }) + let emailAddress = await alreadyExistingMail(_parent, args, context) + if (emailAddress) return emailAddress try { - const emailAddress = await instance.create('EmailAddress', args) + emailAddress = await instance.create('EmailAddress', args) return emailAddress.toJson() } catch (e) { throw new UserInputError(e.message) } }, - SignupByInvitation: async (_parent, args, _context, _resolveInfo) => { + SignupByInvitation: async (_parent, args, context) => { const { token } = args const nonce = uuid().substring(0, 6) args.nonce = nonce - await checkEmailDoesNotExist({ email: args.email }) + let emailAddress = await alreadyExistingMail(_parent, args, context) + if (emailAddress) return emailAddress try { const result = await instance.cypher( ` @@ -69,14 +87,14 @@ export default { ) if (!validInvitationCode) throw new UserInputError('Invitation code already used or does not exist.') - const emailAddress = await instance.create('EmailAddress', args) + emailAddress = await instance.create('EmailAddress', args) await validInvitationCode.relateTo(emailAddress, 'activated') return emailAddress.toJson() } catch (e) { throw new UserInputError(e) } }, - SignupVerification: async (_parent, args, _context, _resolveInfo) => { + SignupVerification: async (_parent, args) => { const { termsAndConditionsAgreedVersion } = args const regEx = new RegExp(/^[0-9]+\.[0-9]+\.[0-9]+$/g) if (!regEx.test(termsAndConditionsAgreedVersion)) { diff --git a/backend/src/schema/resolvers/registration.spec.js b/backend/src/schema/resolvers/registration.spec.js index ca19f03c4..531eabea1 100644 --- a/backend/src/schema/resolvers/registration.spec.js +++ b/backend/src/schema/resolvers/registration.spec.js @@ -310,11 +310,60 @@ describe('Signup', () => { }) }) - it('creates a Signup with a cryptographic `nonce`', async () => { - await mutate({ mutation, variables }) - let emailAddress = await neode.first('EmailAddress', { email: 'someuser@example.org' }) - emailAddress = await emailAddress.toJson() - expect(emailAddress.nonce).toEqual(expect.any(String)) + describe('creates a EmailAddress node', () => { + it('with `createdAt` attribute', async () => { + await mutate({ mutation, variables }) + let emailAddress = await neode.first('EmailAddress', { email: 'someuser@example.org' }) + emailAddress = await emailAddress.toJson() + expect(emailAddress.createdAt).toBeTruthy() + expect(Date.parse(emailAddress.createdAt)).toEqual(expect.any(Number)) + }) + + it('with a cryptographic `nonce`', async () => { + await mutate({ mutation, variables }) + let emailAddress = await neode.first('EmailAddress', { email: 'someuser@example.org' }) + emailAddress = await emailAddress.toJson() + expect(emailAddress.nonce).toEqual(expect.any(String)) + }) + + describe('if the email already exists', () => { + let email + beforeEach(async () => { + email = await factory.create('EmailAddress', { + email: 'someuser@example.org', + verifiedAt: null, + }) + }) + + describe('and the user has registered already', () => { + beforeEach(async () => { + await factory.create('User', { email }) + }) + + it('throws UserInputError error because of unique constraint violation', async () => { + await expect(mutate({ mutation, variables })).resolves.toMatchObject({ + errors: [{ message: 'User account with this email already exists.' }], + }) + }) + }) + + describe('but the user has not yet registered', () => { + it('resolves with the already existing email', async () => { + await expect(mutate({ mutation, variables })).resolves.toMatchObject({ + data: { Signup: { email: 'someuser@example.org' } }, + }) + }) + + it('creates no additional `EmailAddress` node', async () => { + // admin account and the already existing user + await expect(neode.all('EmailAddress')).resolves.toHaveLength(2) + await expect(mutate({ mutation, variables })).resolves.toMatchObject({ + data: { Signup: { email: 'someuser@example.org' } }, + }) + await expect(neode.all('EmailAddress')).resolves.toHaveLength(2) + }) + }) + }) }) }) }) diff --git a/backend/src/seed/factories/users.js b/backend/src/seed/factories/users.js index e0ec04fc0..962f92781 100644 --- a/backend/src/seed/factories/users.js +++ b/backend/src/seed/factories/users.js @@ -24,7 +24,15 @@ export default function create() { } args = await encryptPassword(args) const user = await neodeInstance.create('User', args) - const email = await factoryInstance.create('EmailAddress', { email: args.email }) + + let email + if (typeof args.email === 'object') { + // probably a neode node + email = args.email + } else { + email = await factoryInstance.create('EmailAddress', { email: args.email }) + } + await user.relateTo(email, 'primaryEmail') await email.relateTo(user, 'belongsTo') return user