mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Refactored email resolvers
* If the email middleware does not send mails, it will give a warning similar to sentry middleware * Eliminated GrapphQLClient in one more test * Rename code => nonce
This commit is contained in:
parent
80d1e03c03
commit
e751571981
@ -3,6 +3,19 @@ import nodemailer from 'nodemailer'
|
|||||||
import { resetPasswordMail, wrongAccountMail } from './templates/passwordReset'
|
import { resetPasswordMail, wrongAccountMail } from './templates/passwordReset'
|
||||||
import { signupTemplate } from './templates/signup'
|
import { signupTemplate } from './templates/signup'
|
||||||
|
|
||||||
|
let sendMail
|
||||||
|
if (CONFIG.SMTP_HOST && CONFIG.SMTP_PORT) {
|
||||||
|
sendMail = async template => {
|
||||||
|
await transporter().sendMail(template)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendMail = () => {}
|
||||||
|
if (process.env.NODE_ENV !== 'test') {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('Warning: Email middleware will not try to send mails.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const transporter = () => {
|
const transporter = () => {
|
||||||
const configs = {
|
const configs = {
|
||||||
host: CONFIG.SMTP_HOST,
|
host: CONFIG.SMTP_HOST,
|
||||||
@ -17,41 +30,24 @@ const transporter = () => {
|
|||||||
return nodemailer.createTransport(configs)
|
return nodemailer.createTransport(configs)
|
||||||
}
|
}
|
||||||
|
|
||||||
const returnResponse = async (resolve, root, args, context, resolveInfo) => {
|
|
||||||
const { response } = await resolve(root, args, context, resolveInfo)
|
|
||||||
delete response.nonce
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
const sendSignupMail = async (resolve, root, args, context, resolveInfo) => {
|
const sendSignupMail = async (resolve, root, args, context, resolveInfo) => {
|
||||||
const { email } = args
|
const response = await resolve(root, args, context, resolveInfo)
|
||||||
const { response, nonce } = await resolve(root, args, context, resolveInfo)
|
const { email, nonce } = response
|
||||||
|
await sendMail(signupTemplate({ email, nonce }))
|
||||||
delete response.nonce
|
delete response.nonce
|
||||||
await transporter().sendMail(signupTemplate({ email, nonce }))
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function({ isEnabled }) {
|
export default {
|
||||||
if (!isEnabled)
|
Mutation: {
|
||||||
return {
|
requestPasswordReset: async (resolve, root, args, context, resolveInfo) => {
|
||||||
Mutation: {
|
const { email } = args
|
||||||
requestPasswordReset: returnResponse,
|
const { email: emailFound, nonce, name } = await resolve(root, args, context, resolveInfo)
|
||||||
Signup: returnResponse,
|
const mailTemplate = emailFound ? resetPasswordMail : wrongAccountMail
|
||||||
SignupByInvitation: returnResponse,
|
await sendMail(mailTemplate({ email, nonce, name }))
|
||||||
},
|
return true
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
Mutation: {
|
|
||||||
requestPasswordReset: async (resolve, root, args, context, resolveInfo) => {
|
|
||||||
const { email } = args
|
|
||||||
const { response, user, code, name } = await resolve(root, args, context, resolveInfo)
|
|
||||||
const mailTemplate = user ? resetPasswordMail : wrongAccountMail
|
|
||||||
await transporter().sendMail(mailTemplate({ email, code, name }))
|
|
||||||
return response
|
|
||||||
},
|
|
||||||
Signup: sendSignupMail,
|
|
||||||
SignupByInvitation: sendSignupMail,
|
|
||||||
},
|
},
|
||||||
}
|
Signup: sendSignupMail,
|
||||||
|
SignupByInvitation: sendSignupMail,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,12 +6,12 @@ export const resetPasswordMail = options => {
|
|||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
email,
|
email,
|
||||||
code,
|
nonce,
|
||||||
subject = 'Use this link to reset your password. The link is only valid for 24 hours.',
|
subject = 'Use this link to reset your password. The link is only valid for 24 hours.',
|
||||||
supportUrl = 'https://human-connection.org/en/contact/',
|
supportUrl = 'https://human-connection.org/en/contact/',
|
||||||
} = options
|
} = options
|
||||||
const actionUrl = new URL('/password-reset/change-password', CONFIG.CLIENT_URI)
|
const actionUrl = new URL('/password-reset/change-password', CONFIG.CLIENT_URI)
|
||||||
actionUrl.searchParams.set('code', code)
|
actionUrl.searchParams.set('nonce', nonce)
|
||||||
actionUrl.searchParams.set('email', email)
|
actionUrl.searchParams.set('email', email)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -37,7 +37,7 @@ The Human Connection Team
|
|||||||
If you're having trouble with the link above, you can manually copy and
|
If you're having trouble with the link above, you can manually copy and
|
||||||
paste the following code into your browser window:
|
paste the following code into your browser window:
|
||||||
|
|
||||||
${code}
|
${nonce}
|
||||||
|
|
||||||
Human Connection gemeinnützige GmbH
|
Human Connection gemeinnützige GmbH
|
||||||
Bahnhofstr. 11
|
Bahnhofstr. 11
|
||||||
|
|||||||
@ -22,7 +22,7 @@ and create a user account:
|
|||||||
|
|
||||||
${actionUrl}
|
${actionUrl}
|
||||||
|
|
||||||
You can also copy+paste this verification code in your browser window:
|
You can also copy+paste this verification nonce in your browser window:
|
||||||
|
|
||||||
${nonce}
|
${nonce}
|
||||||
|
|
||||||
|
|||||||
@ -33,9 +33,7 @@ export default schema => {
|
|||||||
user,
|
user,
|
||||||
includedFields,
|
includedFields,
|
||||||
orderBy,
|
orderBy,
|
||||||
email: email({
|
email,
|
||||||
isEnabled: CONFIG.SMTP_HOST && CONFIG.SMTP_PORT,
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let order = [
|
let order = [
|
||||||
|
|||||||
@ -2,39 +2,47 @@ import uuid from 'uuid/v4'
|
|||||||
import bcrypt from 'bcryptjs'
|
import bcrypt from 'bcryptjs'
|
||||||
|
|
||||||
export async function createPasswordReset(options) {
|
export async function createPasswordReset(options) {
|
||||||
const { driver, code, email, issuedAt = new Date() } = options
|
const { driver, nonce, email, issuedAt = new Date() } = options
|
||||||
const session = driver.session()
|
const session = driver.session()
|
||||||
const cypher = `
|
let response = {}
|
||||||
|
try {
|
||||||
|
const cypher = `
|
||||||
MATCH (u:User)-[:PRIMARY_EMAIL]->(e:EmailAddress {email:$email})
|
MATCH (u:User)-[:PRIMARY_EMAIL]->(e:EmailAddress {email:$email})
|
||||||
CREATE(pr:PasswordReset {code: $code, issuedAt: datetime($issuedAt), usedAt: NULL})
|
CREATE(pr:PasswordReset {nonce: $nonce, issuedAt: datetime($issuedAt), usedAt: NULL})
|
||||||
MERGE (u)-[:REQUESTED]->(pr)
|
MERGE (u)-[:REQUESTED]->(pr)
|
||||||
RETURN u
|
RETURN e, pr, u
|
||||||
`
|
`
|
||||||
const transactionRes = await session.run(cypher, {
|
const transactionRes = await session.run(cypher, {
|
||||||
issuedAt: issuedAt.toISOString(),
|
issuedAt: issuedAt.toISOString(),
|
||||||
code,
|
nonce,
|
||||||
email,
|
email,
|
||||||
})
|
})
|
||||||
const users = transactionRes.records.map(record => record.get('u'))
|
const records = transactionRes.records.map(record => {
|
||||||
session.close()
|
const { email } = record.get('e').properties
|
||||||
return users
|
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 {
|
export default {
|
||||||
Mutation: {
|
Mutation: {
|
||||||
requestPasswordReset: async (_, { email }, { driver }) => {
|
requestPasswordReset: async (_, { email }, { driver }) => {
|
||||||
const code = uuid().substring(0, 6)
|
const nonce = uuid().substring(0, 6)
|
||||||
const [user] = await createPasswordReset({ driver, code, email })
|
return createPasswordReset({ driver, nonce, email })
|
||||||
const name = (user && user.name) || ''
|
|
||||||
return { user, code, name, response: true }
|
|
||||||
},
|
},
|
||||||
resetPassword: async (_, { email, code, newPassword }, { driver }) => {
|
resetPassword: async (_, { email, nonce, newPassword }, { driver }) => {
|
||||||
const session = driver.session()
|
const session = driver.session()
|
||||||
const stillValid = new Date()
|
const stillValid = new Date()
|
||||||
stillValid.setDate(stillValid.getDate() - 1)
|
stillValid.setDate(stillValid.getDate() - 1)
|
||||||
const encryptedNewPassword = await bcrypt.hashSync(newPassword, 10)
|
const encryptedNewPassword = await bcrypt.hashSync(newPassword, 10)
|
||||||
const cypher = `
|
const cypher = `
|
||||||
MATCH (pr:PasswordReset {code: $code})
|
MATCH (pr:PasswordReset {nonce: $nonce})
|
||||||
MATCH (e:EmailAddress {email: $email})<-[:PRIMARY_EMAIL]-(u:User)-[:REQUESTED]->(pr)
|
MATCH (e:EmailAddress {email: $email})<-[:PRIMARY_EMAIL]-(u:User)-[:REQUESTED]->(pr)
|
||||||
WHERE duration.between(pr.issuedAt, datetime()).days <= 0 AND pr.usedAt IS NULL
|
WHERE duration.between(pr.issuedAt, datetime()).days <= 0 AND pr.usedAt IS NULL
|
||||||
SET pr.usedAt = datetime()
|
SET pr.usedAt = datetime()
|
||||||
@ -44,13 +52,13 @@ export default {
|
|||||||
const transactionRes = await session.run(cypher, {
|
const transactionRes = await session.run(cypher, {
|
||||||
stillValid,
|
stillValid,
|
||||||
email,
|
email,
|
||||||
code,
|
nonce,
|
||||||
encryptedNewPassword,
|
encryptedNewPassword,
|
||||||
})
|
})
|
||||||
const [reset] = transactionRes.records.map(record => record.get('pr'))
|
const [reset] = transactionRes.records.map(record => record.get('pr'))
|
||||||
const result = !!(reset && reset.properties.usedAt)
|
const response = !!(reset && reset.properties.usedAt)
|
||||||
session.close()
|
session.close()
|
||||||
return result
|
return response
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,17 @@
|
|||||||
import { GraphQLClient } from 'graphql-request'
|
|
||||||
import Factory from '../../seed/factories'
|
import Factory from '../../seed/factories'
|
||||||
import { host } from '../../jest/helpers'
|
import { gql } from '../../jest/helpers'
|
||||||
import { getDriver } from '../../bootstrap/neo4j'
|
import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||||
import { createPasswordReset } from './passwordReset'
|
import { createPasswordReset } from './passwordReset'
|
||||||
|
import createServer from '../../server'
|
||||||
|
import { createTestClient } from 'apollo-server-testing'
|
||||||
|
|
||||||
const factory = Factory()
|
const neode = getNeode()
|
||||||
let client
|
|
||||||
const driver = getDriver()
|
const driver = getDriver()
|
||||||
|
const factory = Factory()
|
||||||
|
|
||||||
|
let mutate
|
||||||
|
let authenticatedUser
|
||||||
|
let variables
|
||||||
|
|
||||||
const getAllPasswordResets = async () => {
|
const getAllPasswordResets = async () => {
|
||||||
const session = driver.session()
|
const session = driver.session()
|
||||||
@ -16,120 +21,168 @@ const getAllPasswordResets = async () => {
|
|||||||
return resets
|
return resets
|
||||||
}
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
variables = {}
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
const { server } = createServer({
|
||||||
|
context: () => {
|
||||||
|
return {
|
||||||
|
driver,
|
||||||
|
neode,
|
||||||
|
user: authenticatedUser,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
mutate = createTestClient(server).mutate
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await factory.cleanDatabase()
|
||||||
|
})
|
||||||
|
|
||||||
describe('passwordReset', () => {
|
describe('passwordReset', () => {
|
||||||
beforeEach(async () => {
|
describe('given a user', () => {
|
||||||
client = new GraphQLClient(host)
|
beforeEach(async () => {
|
||||||
await factory.create('User', {
|
await factory.create('User', {
|
||||||
email: 'user@example.org',
|
email: 'user@example.org',
|
||||||
role: 'user',
|
})
|
||||||
password: '1234',
|
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(async () => {
|
describe('requestPasswordReset', () => {
|
||||||
await factory.cleanDatabase()
|
const mutation = gql`
|
||||||
})
|
mutation($email: String!) {
|
||||||
|
requestPasswordReset(email: $email)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
describe('requestPasswordReset', () => {
|
describe('with invalid email', () => {
|
||||||
const mutation = `mutation($email: String!) { requestPasswordReset(email: $email) }`
|
beforeEach(() => {
|
||||||
|
variables = { ...variables, email: 'non-existent@example.org' }
|
||||||
|
})
|
||||||
|
|
||||||
describe('with invalid email', () => {
|
it('resolves anyways', async () => {
|
||||||
const variables = { email: 'non-existent@example.org' }
|
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
|
||||||
|
data: { requestPasswordReset: true },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('resolves anyways', async () => {
|
it('creates no node', async () => {
|
||||||
await expect(client.request(mutation, variables)).resolves.toEqual({
|
await mutate({ mutation, variables })
|
||||||
requestPasswordReset: true,
|
const resets = await getAllPasswordResets()
|
||||||
|
expect(resets).toHaveLength(0)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('creates no node', async () => {
|
describe('with a valid email', () => {
|
||||||
await client.request(mutation, variables)
|
beforeEach(() => {
|
||||||
const resets = await getAllPasswordResets()
|
variables = { ...variables, email: 'user@example.org' }
|
||||||
expect(resets).toHaveLength(0)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('with a valid email', () => {
|
|
||||||
const variables = { email: 'user@example.org' }
|
|
||||||
|
|
||||||
it('resolves', async () => {
|
|
||||||
await expect(client.request(mutation, variables)).resolves.toEqual({
|
|
||||||
requestPasswordReset: true,
|
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
it('creates node with label `PasswordReset`', async () => {
|
it('resolves', async () => {
|
||||||
await client.request(mutation, variables)
|
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
|
||||||
const resets = await getAllPasswordResets()
|
data: { requestPasswordReset: true },
|
||||||
expect(resets).toHaveLength(1)
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('creates a reset code', async () => {
|
it('creates node with label `PasswordReset`', async () => {
|
||||||
await client.request(mutation, variables)
|
let resets = await getAllPasswordResets()
|
||||||
const resets = await getAllPasswordResets()
|
expect(resets).toHaveLength(0)
|
||||||
const [reset] = resets
|
await mutate({ mutation, variables })
|
||||||
const { code } = reset.properties
|
resets = await getAllPasswordResets()
|
||||||
expect(code).toHaveLength(6)
|
expect(resets).toHaveLength(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates a reset nonce', async () => {
|
||||||
|
await mutate({ mutation, variables })
|
||||||
|
const resets = await getAllPasswordResets()
|
||||||
|
const [reset] = resets
|
||||||
|
const { nonce } = reset.properties
|
||||||
|
expect(nonce).toHaveLength(6)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('resetPassword', () => {
|
describe('resetPassword', () => {
|
||||||
const setup = async (options = {}) => {
|
const setup = async (options = {}) => {
|
||||||
const { email = 'user@example.org', issuedAt = new Date(), code = 'abcdef' } = options
|
const { email = 'user@example.org', issuedAt = new Date(), nonce = 'abcdef' } = options
|
||||||
|
|
||||||
const session = driver.session()
|
const session = driver.session()
|
||||||
await createPasswordReset({ driver, email, issuedAt, code })
|
await createPasswordReset({ driver, email, issuedAt, nonce })
|
||||||
session.close()
|
session.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
const mutation = gql`
|
||||||
|
mutation($nonce: String!, $email: String!, $newPassword: String!) {
|
||||||
|
resetPassword(nonce: $nonce, email: $email, newPassword: $newPassword)
|
||||||
}
|
}
|
||||||
|
`
|
||||||
|
const nonce = 'abcdef'
|
||||||
|
beforeEach(() => {
|
||||||
|
variables = { ...variables, newPassword: 'supersecret' }
|
||||||
|
})
|
||||||
|
|
||||||
const mutation = `mutation($code: String!, $email: String!, $newPassword: String!) { resetPassword(code: $code, email: $email, newPassword: $newPassword) }`
|
describe('given a user', () => {
|
||||||
const email = 'user@example.org'
|
beforeEach(async () => {
|
||||||
const code = 'abcdef'
|
await factory.create('User', {
|
||||||
const newPassword = 'supersecret'
|
email: 'user@example.org',
|
||||||
let variables
|
role: 'user',
|
||||||
|
password: '1234',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('invalid email', () => {
|
describe('invalid email', () => {
|
||||||
it('resolves to false', async () => {
|
it('resolves to false', async () => {
|
||||||
await setup()
|
await setup()
|
||||||
variables = { newPassword, email: 'non-existent@example.org', code }
|
variables = { ...variables, email: 'non-existent@example.org', nonce }
|
||||||
await expect(client.request(mutation, variables)).resolves.toEqual({ resetPassword: false })
|
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
|
||||||
|
data: { resetPassword: false },
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('valid email', () => {
|
describe('valid email', () => {
|
||||||
describe('but invalid code', () => {
|
beforeEach(() => {
|
||||||
|
variables = { ...variables, email: 'user@example.org' }
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('but invalid nonce', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
variables = { ...variables, nonce: 'slkdjf' }
|
||||||
|
})
|
||||||
|
|
||||||
it('resolves to false', async () => {
|
it('resolves to false', async () => {
|
||||||
await setup()
|
await setup()
|
||||||
variables = { newPassword, email, code: 'slkdjf' }
|
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
|
||||||
await expect(client.request(mutation, variables)).resolves.toEqual({
|
data: { resetPassword: false },
|
||||||
resetPassword: false,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('and valid code', () => {
|
describe('and valid nonce', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
variables = {
|
variables = {
|
||||||
newPassword,
|
...variables,
|
||||||
email: 'user@example.org',
|
nonce: 'abcdef',
|
||||||
code: 'abcdef',
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('and code not expired', () => {
|
describe('and nonce not expired', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await setup()
|
await setup()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('resolves to true', async () => {
|
it('resolves to true', async () => {
|
||||||
await expect(client.request(mutation, variables)).resolves.toEqual({
|
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
|
||||||
resetPassword: true,
|
data: { resetPassword: true },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('updates PasswordReset `usedAt` property', async () => {
|
it('updates PasswordReset `usedAt` property', async () => {
|
||||||
await client.request(mutation, variables)
|
await mutate({ mutation, variables })
|
||||||
const requests = await getAllPasswordResets()
|
const requests = await getAllPasswordResets()
|
||||||
const [request] = requests
|
const [request] = requests
|
||||||
const { usedAt } = request.properties
|
const { usedAt } = request.properties
|
||||||
@ -137,23 +190,20 @@ describe('passwordReset', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('updates password of the user', async () => {
|
it('updates password of the user', async () => {
|
||||||
await client.request(mutation, variables)
|
await mutate({ mutation, variables })
|
||||||
const checkLoginMutation = `
|
const checkLoginMutation = gql`
|
||||||
mutation($email: String!, $password: String!) {
|
mutation($email: String!, $password: String!) {
|
||||||
login(email: $email, password: $password)
|
login(email: $email, password: $password)
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
const expected = expect.objectContaining({ login: expect.any(String) })
|
variables = { ...variables, email: 'user@example.org', password: 'supersecret' }
|
||||||
await expect(
|
await expect(
|
||||||
client.request(checkLoginMutation, {
|
mutate({ mutation: checkLoginMutation, variables }),
|
||||||
email: 'user@example.org',
|
).resolves.toMatchObject({ data: { login: expect.any(String) } })
|
||||||
password: 'supersecret',
|
|
||||||
}),
|
|
||||||
).resolves.toEqual(expected)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('but expired code', () => {
|
describe('but expired nonce', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const issuedAt = new Date()
|
const issuedAt = new Date()
|
||||||
issuedAt.setDate(issuedAt.getDate() - 1)
|
issuedAt.setDate(issuedAt.getDate() - 1)
|
||||||
@ -161,13 +211,13 @@ describe('passwordReset', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('resolves to false', async () => {
|
it('resolves to false', async () => {
|
||||||
await expect(client.request(mutation, variables)).resolves.toEqual({
|
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
|
||||||
resetPassword: false,
|
data: { resetPassword: false },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('does not update PasswordReset `usedAt` property', async () => {
|
it('does not update PasswordReset `usedAt` property', async () => {
|
||||||
await client.request(mutation, variables)
|
await mutate({ mutation, variables })
|
||||||
const requests = await getAllPasswordResets()
|
const requests = await getAllPasswordResets()
|
||||||
const [request] = requests
|
const [request] = requests
|
||||||
const { usedAt } = request.properties
|
const { usedAt } = request.properties
|
||||||
|
|||||||
@ -43,7 +43,7 @@ export default {
|
|||||||
await checkEmailDoesNotExist({ email: args.email })
|
await checkEmailDoesNotExist({ email: args.email })
|
||||||
try {
|
try {
|
||||||
const emailAddress = await instance.create('EmailAddress', args)
|
const emailAddress = await instance.create('EmailAddress', args)
|
||||||
return { response: emailAddress.toJson(), nonce }
|
return emailAddress.toJson()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new UserInputError(e.message)
|
throw new UserInputError(e.message)
|
||||||
}
|
}
|
||||||
@ -71,7 +71,7 @@ export default {
|
|||||||
throw new UserInputError('Invitation code already used or does not exist.')
|
throw new UserInputError('Invitation code already used or does not exist.')
|
||||||
const emailAddress = await instance.create('EmailAddress', args)
|
const emailAddress = await instance.create('EmailAddress', args)
|
||||||
await validInvitationCode.relateTo(emailAddress, 'activated')
|
await validInvitationCode.relateTo(emailAddress, 'activated')
|
||||||
return { response: emailAddress.toJson(), nonce }
|
return emailAddress.toJson()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new UserInputError(e)
|
throw new UserInputError(e)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,7 +23,7 @@ type Mutation {
|
|||||||
login(email: String!, password: String!): String!
|
login(email: String!, password: String!): String!
|
||||||
changePassword(oldPassword: String!, newPassword: String!): String!
|
changePassword(oldPassword: String!, newPassword: String!): String!
|
||||||
requestPasswordReset(email: String!): Boolean!
|
requestPasswordReset(email: String!): Boolean!
|
||||||
resetPassword(email: String!, code: String!, newPassword: String!): Boolean!
|
resetPassword(email: String!, nonce: String!, newPassword: String!): Boolean!
|
||||||
report(id: ID!, description: String): Report
|
report(id: ID!, description: String): Report
|
||||||
disable(id: ID!): ID
|
disable(id: ID!): ID
|
||||||
enable(id: ID!): ID
|
enable(id: ID!): ID
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user