From bcf06dff25eec69786a98f5a30aacfc1835c2e21 Mon Sep 17 00:00:00 2001 From: roschaefer Date: Mon, 4 Nov 2019 23:09:49 +0100 Subject: [PATCH] Implement backend lookup with `normalizeEmail` --- backend/package.json | 1 + backend/src/schema/resolvers/passwordReset.js | 29 +---------------- .../schema/resolvers/passwordReset.spec.js | 5 +-- .../passwordReset/createPasswordReset.js | 31 +++++++++++++++++++ .../passwordReset/createPasswordReset.spec.js | 31 +++++++++++++++++++ backend/yarn.lock | 5 +++ 6 files changed, 70 insertions(+), 32 deletions(-) create mode 100644 backend/src/schema/resolvers/passwordReset/createPasswordReset.js create mode 100644 backend/src/schema/resolvers/passwordReset/createPasswordReset.spec.js diff --git a/backend/package.json b/backend/package.json index 622a8313d..78dd26bc0 100644 --- a/backend/package.json +++ b/backend/package.json @@ -101,6 +101,7 @@ "slug": "~1.1.0", "trunc-html": "~1.1.2", "uuid": "~3.3.3", + "validator": "^12.0.0", "wait-on": "~3.3.0", "xregexp": "^4.2.4" }, diff --git a/backend/src/schema/resolvers/passwordReset.js b/backend/src/schema/resolvers/passwordReset.js index 3c5f4636c..e03378ec1 100644 --- a/backend/src/schema/resolvers/passwordReset.js +++ b/backend/src/schema/resolvers/passwordReset.js @@ -1,34 +1,7 @@ import uuid from 'uuid/v4' import bcrypt from 'bcryptjs' +import createPasswordReset from './passwordReset/createPasswordReset' -export async function createPasswordReset(options) { - const { driver, nonce, email, issuedAt = new Date() } = options - const session = driver.session() - let response = {} - try { - const cypher = ` - MATCH (u:User)-[:PRIMARY_EMAIL]->(e:EmailAddress {email:$email}) - CREATE(pr:PasswordReset {nonce: $nonce, issuedAt: datetime($issuedAt), usedAt: NULL}) - MERGE (u)-[:REQUESTED]->(pr) - RETURN e, pr, u - ` - const transactionRes = await session.run(cypher, { - issuedAt: issuedAt.toISOString(), - nonce, - email, - }) - const records = transactionRes.records.map(record => { - const { email } = record.get('e').properties - const { nonce } = record.get('pr').properties - const { name } = record.get('u').properties - return { email, nonce, name } - }) - response = records[0] || {} - } finally { - session.close() - } - return response -} export default { Mutation: { diff --git a/backend/src/schema/resolvers/passwordReset.spec.js b/backend/src/schema/resolvers/passwordReset.spec.js index fabee1c7e..03de77493 100644 --- a/backend/src/schema/resolvers/passwordReset.spec.js +++ b/backend/src/schema/resolvers/passwordReset.spec.js @@ -1,7 +1,7 @@ import Factory from '../../seed/factories' import { gql } from '../../jest/helpers' import { neode as getNeode, getDriver } from '../../bootstrap/neo4j' -import { createPasswordReset } from './passwordReset' +import { createPasswordReset } from './passwordReset/createPasswordReset' import createServer from '../../server' import { createTestClient } from 'apollo-server-testing' @@ -109,10 +109,7 @@ describe('passwordReset', () => { describe('resetPassword', () => { const setup = async (options = {}) => { const { email = 'user@example.org', issuedAt = new Date(), nonce = 'abcdef' } = options - - const session = driver.session() await createPasswordReset({ driver, email, issuedAt, nonce }) - session.close() } const mutation = gql` diff --git a/backend/src/schema/resolvers/passwordReset/createPasswordReset.js b/backend/src/schema/resolvers/passwordReset/createPasswordReset.js new file mode 100644 index 000000000..8d575abfc --- /dev/null +++ b/backend/src/schema/resolvers/passwordReset/createPasswordReset.js @@ -0,0 +1,31 @@ +import { normalizeEmail } from 'validator' + +export default async function createPasswordReset(options) { + const { driver, nonce, email, issuedAt = new Date() } = options + const normalizedEmail = normalizeEmail(email) + const session = driver.session() + let response = {} + try { + const cypher = ` + MATCH (u:User)-[:PRIMARY_EMAIL]->(e:EmailAddress {email:$email}) + CREATE(pr:PasswordReset {nonce: $nonce, issuedAt: datetime($issuedAt), usedAt: NULL}) + MERGE (u)-[:REQUESTED]->(pr) + RETURN e, pr, u + ` + const transactionRes = await session.run(cypher, { + issuedAt: issuedAt.toISOString(), + nonce, + email: normalizedEmail, + }) + const records = transactionRes.records.map(record => { + const { email } = record.get('e').properties + const { nonce } = record.get('pr').properties + const { name } = record.get('u').properties + return { email, nonce, name } + }) + response = records[0] || {} + } finally { + session.close() + } + return response +} diff --git a/backend/src/schema/resolvers/passwordReset/createPasswordReset.spec.js b/backend/src/schema/resolvers/passwordReset/createPasswordReset.spec.js new file mode 100644 index 000000000..a5c4d75a5 --- /dev/null +++ b/backend/src/schema/resolvers/passwordReset/createPasswordReset.spec.js @@ -0,0 +1,31 @@ +import createPasswordReset from './createPasswordReset' + +describe('createPasswordReset', () => { + const issuedAt = new Date() + const nonce = 'abcdef' + + describe('email lookup', () => { + let driver + let mockSession + beforeEach(() => { + mockSession = { + close() {}, + run: jest.fn().mockReturnValue({ + records: { + map: jest.fn(() => []) + } + }) + } + driver = { session: () => mockSession } + }) + + it('lowercases email address', async () => { + const email = 'stRaNGeCaSiNG@ExAmplE.ORG' + await createPasswordReset({ driver, email, issuedAt, nonce }) + expect(mockSession.run.mock.calls) + .toEqual([[expect.any(String), expect.objectContaining({ + email: 'strangecasing@example.org' + })]]) + }) + }) +}) diff --git a/backend/yarn.lock b/backend/yarn.lock index e6c662229..59254dfc7 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -8418,6 +8418,11 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" +validator@^12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-12.0.0.tgz#fb33221f5320abe2422cda2f517dc3838064e813" + integrity sha512-r5zA1cQBEOgYlesRmSEwc9LkbfNLTtji+vWyaHzRZUxCTHdsX3bd+sdHfs5tGZ2W6ILGGsxWxCNwT/h3IY/3ng== + vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"