Merge pull request #1512 from Human-Connection/refactor_email_middleware

Refactor email middleware
This commit is contained in:
Robert Schäfer 2019-09-11 01:58:37 +02:00 committed by GitHub
commit 5586a2b5ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 286 additions and 215 deletions

View File

@ -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,
},
}

View File

@ -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

View File

@ -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}

View File

@ -33,9 +33,7 @@ export default schema => {
user,
includedFields,
orderBy,
email: email({
isEnabled: CONFIG.SMTP_HOST && CONFIG.SMTP_PORT,
}),
email,
}
let order = [

View File

@ -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
},
},
}

View File

@ -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

View File

@ -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)) {

View File

@ -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

View File

@ -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)
})

View File

@ -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 },

View File

@ -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)
})
})

View File

@ -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 })
},
},
}

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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',

View File

@ -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,

View File

@ -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 } })
},
},
}

View File

@ -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 } })
},
},
}