mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2025-12-13 07:45:56 +00:00
Merge pull request #1512 from Human-Connection/refactor_email_middleware
Refactor email middleware
This commit is contained in:
commit
5586a2b5ea
@ -3,6 +3,22 @@ import nodemailer from 'nodemailer'
|
||||
import { resetPasswordMail, wrongAccountMail } from './templates/passwordReset'
|
||||
import { signupTemplate } from './templates/signup'
|
||||
|
||||
let sendMail
|
||||
if (CONFIG.SMTP_HOST && CONFIG.SMTP_PORT) {
|
||||
sendMail = async templateArgs => {
|
||||
await transporter().sendMail({
|
||||
from: '"Human Connection" <info@human-connection.org>',
|
||||
...templateArgs,
|
||||
})
|
||||
}
|
||||
} 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 configs = {
|
||||
host: CONFIG.SMTP_HOST,
|
||||
@ -17,41 +33,24 @@ const transporter = () => {
|
||||
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 { email } = args
|
||||
const { response, nonce } = await resolve(root, args, context, resolveInfo)
|
||||
const response = await resolve(root, args, context, resolveInfo)
|
||||
const { email, nonce } = response
|
||||
await sendMail(signupTemplate({ email, nonce }))
|
||||
delete response.nonce
|
||||
await transporter().sendMail(signupTemplate({ email, nonce }))
|
||||
return response
|
||||
}
|
||||
|
||||
export default function({ isEnabled }) {
|
||||
if (!isEnabled)
|
||||
return {
|
||||
Mutation: {
|
||||
requestPasswordReset: returnResponse,
|
||||
Signup: returnResponse,
|
||||
SignupByInvitation: returnResponse,
|
||||
},
|
||||
}
|
||||
|
||||
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,
|
||||
export default {
|
||||
Mutation: {
|
||||
requestPasswordReset: async (resolve, root, args, context, resolveInfo) => {
|
||||
const { email } = args
|
||||
const { email: emailFound, nonce, name } = await resolve(root, args, context, resolveInfo)
|
||||
const mailTemplate = emailFound ? resetPasswordMail : wrongAccountMail
|
||||
await sendMail(mailTemplate({ email, nonce, name }))
|
||||
return true
|
||||
},
|
||||
}
|
||||
Signup: sendSignupMail,
|
||||
SignupByInvitation: sendSignupMail,
|
||||
},
|
||||
}
|
||||
|
||||
@ -1,17 +1,15 @@
|
||||
import CONFIG from '../../../config'
|
||||
|
||||
export const from = '"Human Connection" <info@human-connection.org>'
|
||||
|
||||
export const resetPasswordMail = options => {
|
||||
const {
|
||||
name,
|
||||
email,
|
||||
code,
|
||||
nonce,
|
||||
subject = 'Use this link to reset your password. The link is only valid for 24 hours.',
|
||||
supportUrl = 'https://human-connection.org/en/contact/',
|
||||
} = options
|
||||
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)
|
||||
|
||||
return {
|
||||
@ -37,7 +35,7 @@ The Human Connection Team
|
||||
If you're having trouble with the link above, you can manually copy and
|
||||
paste the following code into your browser window:
|
||||
|
||||
${code}
|
||||
${nonce}
|
||||
|
||||
Human Connection gemeinnützige GmbH
|
||||
Bahnhofstr. 11
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
import CONFIG from '../../../config'
|
||||
|
||||
export const from = '"Human Connection" <info@human-connection.org>'
|
||||
|
||||
export const signupTemplate = options => {
|
||||
const {
|
||||
email,
|
||||
nonce,
|
||||
subject = 'Signup link',
|
||||
subject = 'Welcome to Human Connection! Here is your signup link.',
|
||||
supportUrl = 'https://human-connection.org/en/contact/',
|
||||
} = options
|
||||
const actionUrl = new URL('/registration/create-user-account', CONFIG.CLIENT_URI)
|
||||
@ -17,12 +15,33 @@ export const signupTemplate = options => {
|
||||
to: email,
|
||||
subject,
|
||||
text: `
|
||||
Willkommen bei Human Connection! Klick auf diesen Link, um den
|
||||
Registrierungsprozess abzuschließen und um ein Benutzerkonto zu erstellen!
|
||||
|
||||
${actionUrl}
|
||||
|
||||
Alternativ kannst du diesen Code auch kopieren und im Browserfenster einfügen:
|
||||
|
||||
${nonce}
|
||||
|
||||
Bitte ignoriere diese Mail, falls du dich nicht bei Human Connection angemeldet
|
||||
hast. Bei Fragen kontaktiere gerne unseren Support:
|
||||
|
||||
${supportUrl}
|
||||
|
||||
Danke,
|
||||
Das Human Connection Team
|
||||
|
||||
|
||||
English Version
|
||||
===============
|
||||
|
||||
Welcome to Human Connection! Use this link to complete the registration process
|
||||
and create a user account:
|
||||
|
||||
${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}
|
||||
|
||||
|
||||
@ -33,9 +33,7 @@ export default schema => {
|
||||
user,
|
||||
includedFields,
|
||||
orderBy,
|
||||
email: email({
|
||||
isEnabled: CONFIG.SMTP_HOST && CONFIG.SMTP_PORT,
|
||||
}),
|
||||
email,
|
||||
}
|
||||
|
||||
let order = [
|
||||
|
||||
@ -2,39 +2,47 @@ import uuid from 'uuid/v4'
|
||||
import bcrypt from 'bcryptjs'
|
||||
|
||||
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 cypher = `
|
||||
let response = {}
|
||||
try {
|
||||
const cypher = `
|
||||
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)
|
||||
RETURN u
|
||||
RETURN e, pr, u
|
||||
`
|
||||
const transactionRes = await session.run(cypher, {
|
||||
issuedAt: issuedAt.toISOString(),
|
||||
code,
|
||||
email,
|
||||
})
|
||||
const users = transactionRes.records.map(record => record.get('u'))
|
||||
session.close()
|
||||
return users
|
||||
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: {
|
||||
requestPasswordReset: async (_, { email }, { driver }) => {
|
||||
const code = uuid().substring(0, 6)
|
||||
const [user] = await createPasswordReset({ driver, code, email })
|
||||
const name = (user && user.name) || ''
|
||||
return { user, code, name, response: true }
|
||||
requestPasswordReset: async (_parent, { email }, { driver }) => {
|
||||
const nonce = uuid().substring(0, 6)
|
||||
return createPasswordReset({ driver, nonce, email })
|
||||
},
|
||||
resetPassword: async (_, { email, code, newPassword }, { driver }) => {
|
||||
resetPassword: async (_parent, { email, nonce, newPassword }, { driver }) => {
|
||||
const session = driver.session()
|
||||
const stillValid = new Date()
|
||||
stillValid.setDate(stillValid.getDate() - 1)
|
||||
const encryptedNewPassword = await bcrypt.hashSync(newPassword, 10)
|
||||
const cypher = `
|
||||
MATCH (pr:PasswordReset {code: $code})
|
||||
MATCH (pr:PasswordReset {nonce: $nonce})
|
||||
MATCH (e:EmailAddress {email: $email})<-[:PRIMARY_EMAIL]-(u:User)-[:REQUESTED]->(pr)
|
||||
WHERE duration.between(pr.issuedAt, datetime()).days <= 0 AND pr.usedAt IS NULL
|
||||
SET pr.usedAt = datetime()
|
||||
@ -44,13 +52,13 @@ export default {
|
||||
const transactionRes = await session.run(cypher, {
|
||||
stillValid,
|
||||
email,
|
||||
code,
|
||||
nonce,
|
||||
encryptedNewPassword,
|
||||
})
|
||||
const [reset] = transactionRes.records.map(record => record.get('pr'))
|
||||
const result = !!(reset && reset.properties.usedAt)
|
||||
const response = !!(reset && reset.properties.usedAt)
|
||||
session.close()
|
||||
return result
|
||||
return response
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -1,12 +1,17 @@
|
||||
import { GraphQLClient } from 'graphql-request'
|
||||
import Factory from '../../seed/factories'
|
||||
import { host } from '../../jest/helpers'
|
||||
import { getDriver } from '../../bootstrap/neo4j'
|
||||
import { gql } from '../../jest/helpers'
|
||||
import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
|
||||
import { createPasswordReset } from './passwordReset'
|
||||
import createServer from '../../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
const factory = Factory()
|
||||
let client
|
||||
const neode = getNeode()
|
||||
const driver = getDriver()
|
||||
const factory = Factory()
|
||||
|
||||
let mutate
|
||||
let authenticatedUser
|
||||
let variables
|
||||
|
||||
const getAllPasswordResets = async () => {
|
||||
const session = driver.session()
|
||||
@ -16,120 +21,167 @@ const getAllPasswordResets = async () => {
|
||||
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', () => {
|
||||
beforeEach(async () => {
|
||||
client = new GraphQLClient(host)
|
||||
await factory.create('User', {
|
||||
email: 'user@example.org',
|
||||
role: 'user',
|
||||
password: '1234',
|
||||
describe('given a user', () => {
|
||||
beforeEach(async () => {
|
||||
await factory.create('User', {
|
||||
email: 'user@example.org',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await factory.cleanDatabase()
|
||||
})
|
||||
describe('requestPasswordReset', () => {
|
||||
const mutation = gql`
|
||||
mutation($email: String!) {
|
||||
requestPasswordReset(email: $email)
|
||||
}
|
||||
`
|
||||
|
||||
describe('requestPasswordReset', () => {
|
||||
const mutation = `mutation($email: String!) { requestPasswordReset(email: $email) }`
|
||||
describe('with invalid email', () => {
|
||||
beforeEach(() => {
|
||||
variables = { ...variables, email: 'non-existent@example.org' }
|
||||
})
|
||||
|
||||
describe('with invalid email', () => {
|
||||
const variables = { email: 'non-existent@example.org' }
|
||||
it('resolves anyways', async () => {
|
||||
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
|
||||
data: { requestPasswordReset: true },
|
||||
})
|
||||
})
|
||||
|
||||
it('resolves anyways', async () => {
|
||||
await expect(client.request(mutation, variables)).resolves.toEqual({
|
||||
requestPasswordReset: true,
|
||||
it('creates no node', async () => {
|
||||
await mutate({ mutation, variables })
|
||||
const resets = await getAllPasswordResets()
|
||||
expect(resets).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
it('creates no node', async () => {
|
||||
await client.request(mutation, variables)
|
||||
const resets = await getAllPasswordResets()
|
||||
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,
|
||||
describe('with a valid email', () => {
|
||||
beforeEach(() => {
|
||||
variables = { ...variables, email: 'user@example.org' }
|
||||
})
|
||||
})
|
||||
|
||||
it('creates node with label `PasswordReset`', async () => {
|
||||
await client.request(mutation, variables)
|
||||
const resets = await getAllPasswordResets()
|
||||
expect(resets).toHaveLength(1)
|
||||
})
|
||||
it('resolves', async () => {
|
||||
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
|
||||
data: { requestPasswordReset: true },
|
||||
})
|
||||
})
|
||||
|
||||
it('creates a reset code', async () => {
|
||||
await client.request(mutation, variables)
|
||||
const resets = await getAllPasswordResets()
|
||||
const [reset] = resets
|
||||
const { code } = reset.properties
|
||||
expect(code).toHaveLength(6)
|
||||
it('creates node with label `PasswordReset`', async () => {
|
||||
let resets = await getAllPasswordResets()
|
||||
expect(resets).toHaveLength(0)
|
||||
await mutate({ mutation, variables })
|
||||
resets = await getAllPasswordResets()
|
||||
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', () => {
|
||||
const setup = async (options = {}) => {
|
||||
const { email = 'user@example.org', issuedAt = new Date(), code = 'abcdef' } = options
|
||||
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, code })
|
||||
session.close()
|
||||
const session = driver.session()
|
||||
await createPasswordReset({ driver, email, issuedAt, nonce })
|
||||
session.close()
|
||||
}
|
||||
|
||||
const mutation = gql`
|
||||
mutation($nonce: String!, $email: String!, $newPassword: String!) {
|
||||
resetPassword(nonce: $nonce, email: $email, newPassword: $newPassword)
|
||||
}
|
||||
`
|
||||
beforeEach(() => {
|
||||
variables = { ...variables, newPassword: 'supersecret' }
|
||||
})
|
||||
|
||||
const mutation = `mutation($code: String!, $email: String!, $newPassword: String!) { resetPassword(code: $code, email: $email, newPassword: $newPassword) }`
|
||||
const email = 'user@example.org'
|
||||
const code = 'abcdef'
|
||||
const newPassword = 'supersecret'
|
||||
let variables
|
||||
describe('given a user', () => {
|
||||
beforeEach(async () => {
|
||||
await factory.create('User', {
|
||||
email: 'user@example.org',
|
||||
role: 'user',
|
||||
password: '1234',
|
||||
})
|
||||
})
|
||||
|
||||
describe('invalid email', () => {
|
||||
it('resolves to false', async () => {
|
||||
await setup()
|
||||
variables = { newPassword, email: 'non-existent@example.org', code }
|
||||
await expect(client.request(mutation, variables)).resolves.toEqual({ resetPassword: false })
|
||||
variables = { ...variables, email: 'non-existent@example.org', nonce: 'abcdef' }
|
||||
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
|
||||
data: { resetPassword: false },
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
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 () => {
|
||||
await setup()
|
||||
variables = { newPassword, email, code: 'slkdjf' }
|
||||
await expect(client.request(mutation, variables)).resolves.toEqual({
|
||||
resetPassword: false,
|
||||
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
|
||||
data: { resetPassword: false },
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('and valid code', () => {
|
||||
describe('and valid nonce', () => {
|
||||
beforeEach(() => {
|
||||
variables = {
|
||||
newPassword,
|
||||
email: 'user@example.org',
|
||||
code: 'abcdef',
|
||||
...variables,
|
||||
nonce: 'abcdef',
|
||||
}
|
||||
})
|
||||
|
||||
describe('and code not expired', () => {
|
||||
describe('and nonce not expired', () => {
|
||||
beforeEach(async () => {
|
||||
await setup()
|
||||
})
|
||||
|
||||
it('resolves to true', async () => {
|
||||
await expect(client.request(mutation, variables)).resolves.toEqual({
|
||||
resetPassword: true,
|
||||
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
|
||||
data: { resetPassword: true },
|
||||
})
|
||||
})
|
||||
|
||||
it('updates PasswordReset `usedAt` property', async () => {
|
||||
await client.request(mutation, variables)
|
||||
await mutate({ mutation, variables })
|
||||
const requests = await getAllPasswordResets()
|
||||
const [request] = requests
|
||||
const { usedAt } = request.properties
|
||||
@ -137,23 +189,20 @@ describe('passwordReset', () => {
|
||||
})
|
||||
|
||||
it('updates password of the user', async () => {
|
||||
await client.request(mutation, variables)
|
||||
const checkLoginMutation = `
|
||||
mutation($email: String!, $password: String!) {
|
||||
login(email: $email, password: $password)
|
||||
}
|
||||
await mutate({ mutation, variables })
|
||||
const checkLoginMutation = gql`
|
||||
mutation($email: String!, $password: String!) {
|
||||
login(email: $email, password: $password)
|
||||
}
|
||||
`
|
||||
const expected = expect.objectContaining({ login: expect.any(String) })
|
||||
variables = { ...variables, email: 'user@example.org', password: 'supersecret' }
|
||||
await expect(
|
||||
client.request(checkLoginMutation, {
|
||||
email: 'user@example.org',
|
||||
password: 'supersecret',
|
||||
}),
|
||||
).resolves.toEqual(expected)
|
||||
mutate({ mutation: checkLoginMutation, variables }),
|
||||
).resolves.toMatchObject({ data: { login: expect.any(String) } })
|
||||
})
|
||||
})
|
||||
|
||||
describe('but expired code', () => {
|
||||
describe('but expired nonce', () => {
|
||||
beforeEach(async () => {
|
||||
const issuedAt = new Date()
|
||||
issuedAt.setDate(issuedAt.getDate() - 1)
|
||||
@ -161,13 +210,13 @@ describe('passwordReset', () => {
|
||||
})
|
||||
|
||||
it('resolves to false', async () => {
|
||||
await expect(client.request(mutation, variables)).resolves.toEqual({
|
||||
resetPassword: false,
|
||||
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
|
||||
data: { resetPassword: false },
|
||||
})
|
||||
})
|
||||
|
||||
it('does not update PasswordReset `usedAt` property', async () => {
|
||||
await client.request(mutation, variables)
|
||||
await mutate({ mutation, variables })
|
||||
const requests = await getAllPasswordResets()
|
||||
const [request] = requests
|
||||
const { usedAt } = request.properties
|
||||
|
||||
@ -18,7 +18,7 @@ const checkEmailDoesNotExist = async ({ email }) => {
|
||||
|
||||
export default {
|
||||
Mutation: {
|
||||
CreateInvitationCode: async (parent, args, context, resolveInfo) => {
|
||||
CreateInvitationCode: async (_parent, args, context, _resolveInfo) => {
|
||||
args.token = uuid().substring(0, 6)
|
||||
const {
|
||||
user: { id: userId },
|
||||
@ -37,18 +37,18 @@ export default {
|
||||
}
|
||||
return response
|
||||
},
|
||||
Signup: async (parent, args, context, resolveInfo) => {
|
||||
Signup: async (_parent, args, _context, _resolveInfo) => {
|
||||
const nonce = uuid().substring(0, 6)
|
||||
args.nonce = nonce
|
||||
await checkEmailDoesNotExist({ email: args.email })
|
||||
try {
|
||||
const emailAddress = await instance.create('EmailAddress', args)
|
||||
return { response: emailAddress.toJson(), nonce }
|
||||
return emailAddress.toJson()
|
||||
} catch (e) {
|
||||
throw new UserInputError(e.message)
|
||||
}
|
||||
},
|
||||
SignupByInvitation: async (parent, args, context, resolveInfo) => {
|
||||
SignupByInvitation: async (_parent, args, _context, _resolveInfo) => {
|
||||
const { token } = args
|
||||
const nonce = uuid().substring(0, 6)
|
||||
args.nonce = nonce
|
||||
@ -71,12 +71,12 @@ export default {
|
||||
throw new UserInputError('Invitation code already used or does not exist.')
|
||||
const emailAddress = await instance.create('EmailAddress', args)
|
||||
await validInvitationCode.relateTo(emailAddress, 'activated')
|
||||
return { response: emailAddress.toJson(), nonce }
|
||||
return emailAddress.toJson()
|
||||
} catch (e) {
|
||||
throw new UserInputError(e)
|
||||
}
|
||||
},
|
||||
SignupVerification: async (object, args, context, resolveInfo) => {
|
||||
SignupVerification: async (_parent, args, _context, _resolveInfo) => {
|
||||
const { termsAndConditionsAgreedVersion } = args
|
||||
const regEx = new RegExp(/^[0-9]+\.[0-9]+\.[0-9]+$/g)
|
||||
if (!regEx.test(termsAndConditionsAgreedVersion)) {
|
||||
|
||||
@ -23,7 +23,7 @@ type Mutation {
|
||||
login(email: String!, password: String!): String!
|
||||
changePassword(oldPassword: String!, newPassword: String!): String!
|
||||
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
|
||||
disable(id: ID!): ID
|
||||
enable(id: ID!): ID
|
||||
|
||||
@ -39,10 +39,10 @@ describe('ChangePassword ', () => {
|
||||
})
|
||||
}
|
||||
|
||||
describe('given email and verification code', () => {
|
||||
describe('given email and verification nonce', () => {
|
||||
beforeEach(() => {
|
||||
propsData.email = 'mail@example.org'
|
||||
propsData.code = '123456'
|
||||
propsData.nonce = '123456'
|
||||
})
|
||||
|
||||
describe('submitting new password', () => {
|
||||
@ -59,14 +59,14 @@ describe('ChangePassword ', () => {
|
||||
|
||||
it('delivers new password to backend', () => {
|
||||
const expected = expect.objectContaining({
|
||||
variables: { code: '123456', email: 'mail@example.org', password: 'supersecret' },
|
||||
variables: { nonce: '123456', email: 'mail@example.org', password: 'supersecret' },
|
||||
})
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected)
|
||||
})
|
||||
|
||||
describe('password reset successful', () => {
|
||||
it('displays success message', () => {
|
||||
const expected = 'verify-code.form.change-password.success'
|
||||
const expected = 'verify-nonce.form.change-password.success'
|
||||
expect(mocks.$t).toHaveBeenCalledWith(expected)
|
||||
})
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<ds-card class="verify-code">
|
||||
<ds-card class="verify-nonce">
|
||||
<ds-space margin="large">
|
||||
<ds-form
|
||||
v-if="!changePasswordResult"
|
||||
@ -35,14 +35,14 @@
|
||||
<template v-if="changePasswordResult === 'success'">
|
||||
<sweetalert-icon icon="success" />
|
||||
<ds-text>
|
||||
{{ $t(`verify-code.form.change-password.success`) }}
|
||||
{{ $t(`verify-nonce.form.change-password.success`) }}
|
||||
</ds-text>
|
||||
</template>
|
||||
<template v-else>
|
||||
<sweetalert-icon icon="error" />
|
||||
<ds-text align="left">
|
||||
{{ $t(`verify-code.form.change-password.error`) }}
|
||||
{{ $t('verify-code.form.change-password.help') }}
|
||||
{{ $t(`verify-nonce.form.change-password.error`) }}
|
||||
{{ $t('verify-nonce.form.change-password.help') }}
|
||||
</ds-text>
|
||||
<a href="mailto:support@human-connection.org">support@human-connection.org</a>
|
||||
</template>
|
||||
@ -64,7 +64,7 @@ export default {
|
||||
},
|
||||
props: {
|
||||
email: { type: String, required: true },
|
||||
code: { type: String, required: true },
|
||||
nonce: { type: String, required: true },
|
||||
},
|
||||
data() {
|
||||
const passwordForm = PasswordForm({ translate: this.$t })
|
||||
@ -82,13 +82,13 @@ export default {
|
||||
methods: {
|
||||
async handleSubmitPassword() {
|
||||
const mutation = gql`
|
||||
mutation($code: String!, $email: String!, $password: String!) {
|
||||
resetPassword(code: $code, email: $email, newPassword: $password)
|
||||
mutation($nonce: String!, $email: String!, $password: String!) {
|
||||
resetPassword(nonce: $nonce, email: $email, newPassword: $password)
|
||||
}
|
||||
`
|
||||
const { password } = this.formData
|
||||
const { email, code } = this
|
||||
const variables = { password, email, code }
|
||||
const { email, nonce } = this
|
||||
const variables = { password, email, nonce }
|
||||
try {
|
||||
const {
|
||||
data: { resetPassword },
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils'
|
||||
import VerifyCode from './VerifyCode'
|
||||
import VerifyNonce from './VerifyNonce.vue'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
|
||||
localVue.use(Styleguide)
|
||||
|
||||
describe('VerifyCode ', () => {
|
||||
describe('VerifyNonce ', () => {
|
||||
let wrapper
|
||||
let Wrapper
|
||||
let mocks
|
||||
@ -25,27 +25,27 @@ describe('VerifyCode ', () => {
|
||||
beforeEach(jest.useFakeTimers)
|
||||
|
||||
Wrapper = () => {
|
||||
return mount(VerifyCode, {
|
||||
return mount(VerifyNonce, {
|
||||
mocks,
|
||||
localVue,
|
||||
propsData,
|
||||
})
|
||||
}
|
||||
|
||||
it('renders a verify code form', () => {
|
||||
it('renders a verify nonce form', () => {
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('.verify-code').exists()).toBe(true)
|
||||
expect(wrapper.find('.verify-nonce').exists()).toBe(true)
|
||||
})
|
||||
|
||||
describe('after verification code given', () => {
|
||||
describe('after verification nonce given', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
wrapper.find('input#code').setValue('123456')
|
||||
wrapper.find('input#nonce').setValue('123456')
|
||||
wrapper.find('form').trigger('submit')
|
||||
})
|
||||
|
||||
it('emits `verifyCode`', () => {
|
||||
const expected = [[{ code: '123456', email: 'mail@example.org' }]]
|
||||
it('emits `verification`', () => {
|
||||
const expected = [[{ nonce: '123456', email: 'mail@example.org' }]]
|
||||
expect(wrapper.emitted('verification')).toEqual(expected)
|
||||
})
|
||||
})
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<ds-card class="verify-code">
|
||||
<ds-card class="verify-nonce">
|
||||
<ds-space margin="large">
|
||||
<ds-form
|
||||
v-model="formData"
|
||||
@ -9,19 +9,19 @@
|
||||
@input-valid="handleInputValid"
|
||||
>
|
||||
<ds-input
|
||||
:placeholder="$t('verify-code.form.code')"
|
||||
model="code"
|
||||
name="code"
|
||||
id="code"
|
||||
:placeholder="$t('verify-nonce.form.nonce')"
|
||||
model="nonce"
|
||||
name="nonce"
|
||||
id="nonce"
|
||||
icon="question-circle"
|
||||
/>
|
||||
<ds-space margin-botton="large">
|
||||
<ds-text>
|
||||
{{ $t('verify-code.form.description') }}
|
||||
{{ $t('verify-nonce.form.description') }}
|
||||
</ds-text>
|
||||
</ds-space>
|
||||
<ds-button :disabled="disabled" primary fullwidth name="submit" type="submit">
|
||||
{{ $t('verify-code.form.next') }}
|
||||
{{ $t('verify-nonce.form.next') }}
|
||||
</ds-button>
|
||||
</ds-form>
|
||||
</ds-space>
|
||||
@ -36,15 +36,15 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
formData: {
|
||||
code: '',
|
||||
nonce: '',
|
||||
},
|
||||
formSchema: {
|
||||
code: {
|
||||
nonce: {
|
||||
type: 'string',
|
||||
min: 6,
|
||||
max: 6,
|
||||
required: true,
|
||||
message: this.$t('common.validations.verification-code'),
|
||||
message: this.$t('common.validations.verification-nonce'),
|
||||
},
|
||||
},
|
||||
disabled: true,
|
||||
@ -58,9 +58,9 @@ export default {
|
||||
this.disabled = false
|
||||
},
|
||||
handleSubmitVerify() {
|
||||
const { code } = this.formData
|
||||
const { nonce } = this.formData
|
||||
const email = this.email
|
||||
this.$emit('verification', { email, code })
|
||||
this.$emit('verification', { email, nonce })
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -82,9 +82,9 @@
|
||||
"success": "Dein Benutzerkonto wurde erstellt!"
|
||||
}
|
||||
},
|
||||
"verify-code": {
|
||||
"verify-nonce": {
|
||||
"form": {
|
||||
"code": "Code eingeben",
|
||||
"nonce": "Code eingeben",
|
||||
"description": "Öffne dein E-Mail Postfach und gib den Code ein, den wir geschickt haben.",
|
||||
"next": "Weiter",
|
||||
"change-password": {
|
||||
@ -342,7 +342,7 @@
|
||||
"validations": {
|
||||
"email": "muss eine gültige E-Mail Adresse sein",
|
||||
"url": "muss eine gültige URL sein",
|
||||
"verification-code": "muss genau 6 Buchstaben lang sein"
|
||||
"verification-nonce": "muss genau 6 Buchstaben lang sein"
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
|
||||
@ -82,9 +82,9 @@
|
||||
"success": "Your account has been created!"
|
||||
}
|
||||
},
|
||||
"verify-code": {
|
||||
"verify-nonce": {
|
||||
"form": {
|
||||
"code": "Enter your code",
|
||||
"nonce": "Enter your code",
|
||||
"description": "Open your inbox and enter the code that we've sent to you.",
|
||||
"next": "Continue",
|
||||
"change-password": {
|
||||
@ -342,7 +342,7 @@
|
||||
"validations": {
|
||||
"email": "must be a valid email address",
|
||||
"url": "must be a valid URL",
|
||||
"verification-code": "must be 6 characters long"
|
||||
"verification-nonce": "must be 6 characters long"
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
|
||||
@ -37,9 +37,9 @@
|
||||
"submitted": "Na adres <b>{email}</b> została wysłana wiadomość z dalszymi instrukcjami."
|
||||
}
|
||||
},
|
||||
"verify-code": {
|
||||
"verify-nonce": {
|
||||
"form": {
|
||||
"code": "Wprowadź swój kod",
|
||||
"nonce": "Wprowadź swój kod",
|
||||
"description": "Otwórz swoją skrzynkę odbiorczą i wpisz kod, który do Ciebie wysłaliśmy.",
|
||||
"next": "Kontynuuj",
|
||||
"change-password": {
|
||||
@ -237,7 +237,7 @@
|
||||
"reportContent": "Sprawozdanie",
|
||||
"validations": {
|
||||
"email": "musi być ważny adres e-mail.",
|
||||
"verification-code": "musi mieć długość 6 znaków."
|
||||
"verification-nonce": "musi mieć długość 6 znaków."
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
|
||||
@ -35,11 +35,11 @@ module.exports = {
|
||||
'login',
|
||||
'logout',
|
||||
'password-reset-request',
|
||||
'password-reset-verify-code',
|
||||
'password-reset-verify-nonce',
|
||||
'password-reset-change-password',
|
||||
// 'registration-signup', TODO: uncomment to open public registration
|
||||
'registration-signup-by-invitation-code',
|
||||
'registration-verify-code',
|
||||
// 'registration-signup', TODO: implement to open public registration
|
||||
// 'registration-signup-by-invitation-code',
|
||||
// 'registration-verify-nonce',
|
||||
'registration-create-user-account',
|
||||
'pages-slug',
|
||||
'imprint',
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<change-password
|
||||
:email="email"
|
||||
:code="code"
|
||||
:nonce="nonce"
|
||||
@passwordResetResponse="handlePasswordResetResponse"
|
||||
/>
|
||||
</template>
|
||||
@ -11,8 +11,8 @@ import ChangePassword from '~/components/PasswordReset/ChangePassword'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
const { email = '', code = '' } = this.$route.query
|
||||
return { email, code }
|
||||
const { email = '', nonce = '' } = this.$route.query
|
||||
return { email, nonce }
|
||||
},
|
||||
components: {
|
||||
ChangePassword,
|
||||
|
||||
@ -11,7 +11,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
handlePasswordResetRequested({ email }) {
|
||||
this.$router.push({ path: 'verify-code', query: { email } })
|
||||
this.$router.push({ path: 'verify-nonce', query: { email } })
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -1,21 +1,21 @@
|
||||
<template>
|
||||
<verify-code :email="email" @verification="handleVerification" />
|
||||
<verify-nonce :email="email" @verification="handleVerification" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import VerifyCode from '~/components/PasswordReset/VerifyCode'
|
||||
import VerifyNonce from '~/components/PasswordReset/VerifyNonce'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
VerifyCode,
|
||||
VerifyNonce,
|
||||
},
|
||||
data() {
|
||||
const { email = '' } = this.$route.query
|
||||
return { email }
|
||||
},
|
||||
methods: {
|
||||
handleVerification({ email, code }) {
|
||||
this.$router.push({ path: 'change-password', query: { email, code } })
|
||||
handleVerification({ email, nonce }) {
|
||||
this.$router.push({ path: 'change-password', query: { email, nonce } })
|
||||
},
|
||||
},
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user