From 711061b0c06500085cabecfc34026391444dc07e Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Thu, 10 Apr 2025 20:17:51 +0200 Subject: [PATCH] fix(backend): error when there is an abandoned email (#8315) * fix error when there is an abandoned email We check beforehand if an email has an user attached, but we never delete an email without user. This will violate a unique constraint blocking that particular email from ever being used again. * fix tests --- backend/src/schema/resolvers/emails.spec.ts | 22 +++++++++++++++++++-- backend/src/schema/resolvers/emails.ts | 2 ++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/backend/src/schema/resolvers/emails.spec.ts b/backend/src/schema/resolvers/emails.spec.ts index c594f99f7..63141a3fc 100644 --- a/backend/src/schema/resolvers/emails.spec.ts +++ b/backend/src/schema/resolvers/emails.spec.ts @@ -294,9 +294,9 @@ describe('VerifyEmailAddress', () => { await expect(email).toBe(false) }) - describe('Edge case: In the meantime someone created an `EmailAddress` node with the given email', () => { + describe('Edge case: In the meantime someone created an `EmailAddress` node with the given email belonging to a user', () => { beforeEach(async () => { - await Factory.build('emailAddress', { email: 'to-be-verified@example.org' }) + await Factory.build('user', { id: '568' }, { email: 'to-be-verified@example.org' }) }) it('throws UserInputError because of unique constraints', async () => { @@ -306,6 +306,24 @@ describe('VerifyEmailAddress', () => { }) }) }) + + describe('Edge case: We have an abandoned `EmailAddress` node with the given email', () => { + beforeEach(async () => { + await Factory.build('emailAddress', { email: 'to-be-verified@example.org' }) + }) + + it('connects the new `EmailAddress` as PRIMARY', async () => { + await mutate({ mutation, variables }) + const result = await neode.cypher(` + MATCH(u:User {id: "567"})-[:PRIMARY_EMAIL]->(e:EmailAddress {email: "to-be-verified@example.org"}) + RETURN e + `) + const email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress')) + await expect(email.toJson()).resolves.toMatchObject({ + email: 'to-be-verified@example.org', + }) + }) + }) }) }) }) diff --git a/backend/src/schema/resolvers/emails.ts b/backend/src/schema/resolvers/emails.ts index d4de9c87b..0638ec634 100644 --- a/backend/src/schema/resolvers/emails.ts +++ b/backend/src/schema/resolvers/emails.ts @@ -87,6 +87,8 @@ export default { ` MATCH (user:User {id: $userId})-[:PRIMARY_EMAIL]->(previous:EmailAddress) MATCH (user)<-[:BELONGS_TO]-(email:UnverifiedEmailAddress {email: $email, nonce: $nonce}) + OPTIONAL MATCH (abandonedEmail:EmailAddress{email: $email}) WHERE NOT EXISTS ((abandonedEmail)<-[]-()) + DELETE abandonedEmail MERGE (user)-[:PRIMARY_EMAIL]->(email) SET email:EmailAddress SET email.verifiedAt = toString(datetime())