Forgot password and reset password
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
Hello {{recipientFullName}}.
|
Hello {{recipientFullName}}.
|
||||||
|
|
||||||
This email is for your records to indicated that your account for the Deighton AR system has been deleted.
|
Your account for the Deighton AR system has been deleted.
|
||||||
|
|
||||||
Please contact {{supportEmail}} if you have any questions.
|
Please contact {{supportEmail}} if you have any questions.
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
Hello {{recipientFullName}},
|
Hello {{recipientFullName}},
|
||||||
|
|
||||||
This message allows you to complete the process of changing your email. If you did not make this request please do not worry. Just ignore this email and your account will remain unchanged.
|
This message allows you to complete the process of changing your email on the Deighton AR system. If you did not make this request please do not worry, but we would request that you change your password immediately just to be safe. If you ignore this email your account will remain unchanged.
|
||||||
|
|
||||||
If you did make this request, please click on the following link to confirm your new email:
|
If you _did_ make this request, please click on the following link to confirm your new email:
|
||||||
|
|
||||||
{{confirmEmailLink}}
|
{{confirmEmailLink}}
|
||||||
|
|
||||||
@@ -10,4 +10,4 @@ If you have any questions, please contact us at {{supportEmail}}.
|
|||||||
|
|
||||||
Regards,
|
Regards,
|
||||||
|
|
||||||
Deighton
|
{{senderFullName}}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
Hello {{recipientFullName}},
|
Hello {{recipientFullName}},
|
||||||
|
|
||||||
This message is to inform you that a request was made to change your email to {{recipientNewEmail}}. If you did not make this request please do not worry. Just ignore this email and your account will remain unchanged.
|
This message is to inform you that a request was made to change your email to {{recipientNewEmail}}. If you did not make this request please do not worry, but we would request that you change your password immediately just to be safe.
|
||||||
|
|
||||||
If you did make this request, please see the message sent to your new email account for further instructions.
|
If you did make this request, please see the message sent to your new email account for further instructions.
|
||||||
|
|
||||||
@@ -8,4 +8,4 @@ If you have any questions, please contact us at {{supportEmail}}.
|
|||||||
|
|
||||||
Regards,
|
Regards,
|
||||||
|
|
||||||
Deighton
|
{{senderFullName}}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
Hello {{recipientFullName}},
|
Hello {{recipientFullName}},
|
||||||
|
|
||||||
The following link will allow you to reset your password. Please paste it into your browser and you will be redirected to the Deighton AR site to set your new password:
|
The following link will allow you to reset your password. Please click on it or paste it into your browser and you will be redirected to the Deighton AR site to set your new password:
|
||||||
|
|
||||||
{{resetPasswordLink}}
|
{{resetPasswordLink}}
|
||||||
|
|
||||||
@@ -8,4 +8,4 @@ Please contact {{supportEmail}} if you have any questions or problems.
|
|||||||
|
|
||||||
Regards,
|
Regards,
|
||||||
|
|
||||||
Deighton
|
{{senderFullName}}
|
||||||
|
|||||||
@@ -317,59 +317,60 @@ export class AuthRoutes {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
changePassword(req, res, next) {
|
async changePassword(req, res, next) {
|
||||||
let User = this.db.User
|
let User = this.db.User
|
||||||
let cr = credential()
|
let cr = credential()
|
||||||
User.findById({ _id: req.user._id }).then((user) => {
|
|
||||||
|
try {
|
||||||
|
const user = await User.findById({ _id: req.user._id })
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return next(createError.NotFound(`User ${req.user._id} not found`))
|
throw createError.NotFound(`User ${req.user._id} not found`)
|
||||||
}
|
}
|
||||||
return Promise.all([
|
|
||||||
Promise.resolve(user),
|
const ok = await cr.verify(JSON.stringify(user.passwordHash), req.body.oldPassword)
|
||||||
cr.verify(JSON.stringify(user.passwordHash), req.body.oldPassword)
|
const obj = await cr.hash(req.body.newPassword)
|
||||||
])
|
|
||||||
}).then((arr) => {
|
|
||||||
const [user, ok] = arr
|
|
||||||
return Promise.all([Promise.resolve(user), cr.hash(req.body.newPassword)])
|
|
||||||
}).then((arr) => {
|
|
||||||
const [user, obj] = arr
|
|
||||||
user.passwordHash = JSON.parse(obj)
|
user.passwordHash = JSON.parse(obj)
|
||||||
return user.save()
|
await user.save()
|
||||||
}).then((savedUser) => {
|
|
||||||
res.json({})
|
res.json({})
|
||||||
}).catch((err) => {
|
} catch(err) {
|
||||||
return next(createError.InternalServerError(err.message))
|
if (err instanceof createError.HttpError) {
|
||||||
})
|
next(err)
|
||||||
|
} else {
|
||||||
|
next(createError.InternalServerError(err.message))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendPasswordResetEmail(req, res, next){
|
async sendPasswordResetEmail(req, res, next){
|
||||||
const email = req.body.email
|
const email = req.body.email
|
||||||
let User = this.db.User
|
let User = this.db.User
|
||||||
|
|
||||||
if (!email) {
|
try {
|
||||||
return next(createError.BadRequest('Invalid request parameters'))
|
if (!email) {
|
||||||
}
|
throw createError.BadRequest('Invalid request parameters')
|
||||||
|
|
||||||
User.findOne({ email }).then((user) => {
|
|
||||||
// User must exist their email must be confirmed
|
|
||||||
if (!user || user.emailToken) {
|
|
||||||
// Don't give away any information about why we rejected the request
|
|
||||||
return Promise.reject(createError.BadRequest('Not a valid request'))
|
|
||||||
} else if (user.passwordToken && (new Date() - user.emailToken.created) < this.sendEmailDelayInSeconds) {
|
|
||||||
return Promise.reject(createError.BadRequest('Cannot request password reset so soon'))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all([Promise.resolve(user), util.promisify(crypto.randomBytes)(32)])
|
const user = await User.findOne({ email })
|
||||||
}).then((arr) => {
|
|
||||||
let [ user, buf ] = arr
|
// User must exist and their email must be confirmed
|
||||||
|
if (!user || user.emailToken) {
|
||||||
|
// Don't give away any information about why we rejected the request
|
||||||
|
throw createError.BadRequest('Not a valid request')
|
||||||
|
} else if (user.passwordToken && user.passwordToken.created &&
|
||||||
|
(new Date() - user.passwordToken.created) < this.sendEmailDelayInSeconds) {
|
||||||
|
throw createError.BadRequest('Cannot request password reset so soon')
|
||||||
|
}
|
||||||
|
|
||||||
|
const buf = await util.promisify(crypto.randomBytes)(32)
|
||||||
|
|
||||||
user.passwordToken = {
|
user.passwordToken = {
|
||||||
value: urlSafeBase64.encode(buf),
|
value: urlSafeBase64.encode(buf),
|
||||||
created: new Date()
|
created: new Date()
|
||||||
}
|
}
|
||||||
|
|
||||||
return user.save()
|
const savedUser = await user.save()
|
||||||
}).then((savedUser) => {
|
|
||||||
const userFullName = `${savedUser.firstName} ${savedUser.lastName}`
|
const userFullName = `${savedUser.firstName} ${savedUser.lastName}`
|
||||||
const siteUrl = url.parse(req.headers.referer)
|
const siteUrl = url.parse(req.headers.referer)
|
||||||
const msg = {
|
const msg = {
|
||||||
@@ -381,15 +382,17 @@ export class AuthRoutes {
|
|||||||
supportEmail: this.supportEmail
|
supportEmail: this.supportEmail
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.sendEmail ? this.mq.request('dar-email', 'sendEmail', msg) : Promise.resolve()
|
if (this.sendEmail) {
|
||||||
}).then(() => {
|
await this.mq.request('dar-email', 'sendEmail', msg)
|
||||||
|
}
|
||||||
|
|
||||||
res.json({})
|
res.json({})
|
||||||
}).catch((err) => {
|
} catch(err) {
|
||||||
if (err instanceof createError.HttpError) {
|
if (err instanceof createError.HttpError) {
|
||||||
next(err)
|
next(err)
|
||||||
} else {
|
} else {
|
||||||
next(createError.InternalServerError(`Unable to send password reset email. ${err.message}`))
|
next(createError.InternalServerError(`Unable to send password reset email. ${err.message}`))
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ module.exports = {
|
|||||||
// It enables caching results in ./node_modules/.cache/babel-loader/
|
// It enables caching results in ./node_modules/.cache/babel-loader/
|
||||||
// directory for faster rebuilds.
|
// directory for faster rebuilds.
|
||||||
cacheDirectory: true,
|
cacheDirectory: true,
|
||||||
|
plugins: [ 'transform-decorators-legacy' ]
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// "postcss" loader applies autoprefixer to our CSS.
|
// "postcss" loader applies autoprefixer to our CSS.
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ module.exports = {
|
|||||||
options: {
|
options: {
|
||||||
compact: true,
|
compact: true,
|
||||||
},
|
},
|
||||||
|
plugins: [ 'transform-decorators-legacy' ]
|
||||||
},
|
},
|
||||||
// The notation here is somewhat confusing.
|
// The notation here is somewhat confusing.
|
||||||
// "postcss" loader applies autoprefixer to our CSS.
|
// "postcss" loader applies autoprefixer to our CSS.
|
||||||
|
|||||||
22
website/package-lock.json
generated
22
website/package-lock.json
generated
@@ -375,6 +375,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/auto-bind2/-/auto-bind2-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/auto-bind2/-/auto-bind2-1.0.3.tgz",
|
||||||
"integrity": "sha512-+br9nya9M8ayHjai7m9rdpRxuEr8xcYRDrIp7HybNe0ixUHbc1kDiWXKMb0ldsfWb9Zi+SqJ9JfjW8nTkYD0QQ=="
|
"integrity": "sha512-+br9nya9M8ayHjai7m9rdpRxuEr8xcYRDrIp7HybNe0ixUHbc1kDiWXKMb0ldsfWb9Zi+SqJ9JfjW8nTkYD0QQ=="
|
||||||
},
|
},
|
||||||
|
"autobind-decorator": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/autobind-decorator/-/autobind-decorator-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-bgyxeRi1R2Q8kWpHsb1c+lXCulbIAHsyZRddaS+agAUX3hFUVZMociwvRgeZi1zWvfqEEjybSv4zxWvFV8ydQQ=="
|
||||||
|
},
|
||||||
"autoprefixer": {
|
"autoprefixer": {
|
||||||
"version": "7.1.0",
|
"version": "7.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-7.1.0.tgz",
|
||||||
@@ -795,6 +800,12 @@
|
|||||||
"integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=",
|
"integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"babel-plugin-syntax-decorators": {
|
||||||
|
"version": "6.13.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz",
|
||||||
|
"integrity": "sha1-MSVjtNvePMgGzuPkFszurd0RrAs=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"babel-plugin-syntax-dynamic-import": {
|
"babel-plugin-syntax-dynamic-import": {
|
||||||
"version": "6.18.0",
|
"version": "6.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz",
|
||||||
@@ -854,6 +865,17 @@
|
|||||||
"babel-template": "6.26.0"
|
"babel-template": "6.26.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"babel-plugin-transform-decorators-legacy": {
|
||||||
|
"version": "1.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/babel-plugin-transform-decorators-legacy/-/babel-plugin-transform-decorators-legacy-1.3.4.tgz",
|
||||||
|
"integrity": "sha1-dBtY9sW86eYCfgiC2cmU8E82aSU=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"babel-plugin-syntax-decorators": "6.13.0",
|
||||||
|
"babel-runtime": "6.23.0",
|
||||||
|
"babel-template": "6.26.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"babel-plugin-transform-es2015-arrow-functions": {
|
"babel-plugin-transform-es2015-arrow-functions": {
|
||||||
"version": "6.22.0",
|
"version": "6.22.0",
|
||||||
"resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"animejs": "^2.2.0",
|
"animejs": "^2.2.0",
|
||||||
"auto-bind2": "^1.0.2",
|
"auto-bind2": "^1.0.2",
|
||||||
|
"autobind-decorator": "^2.1.0",
|
||||||
"eventemitter3": "^2.0.3",
|
"eventemitter3": "^2.0.3",
|
||||||
"npm": "^5.7.1",
|
"npm": "^5.7.1",
|
||||||
"prop-types": "^15.5.10",
|
"prop-types": "^15.5.10",
|
||||||
@@ -21,6 +22,7 @@
|
|||||||
"babel-core": "6.24.1",
|
"babel-core": "6.24.1",
|
||||||
"babel-eslint": "7.2.3",
|
"babel-eslint": "7.2.3",
|
||||||
"babel-loader": "7.0.0",
|
"babel-loader": "7.0.0",
|
||||||
|
"babel-plugin-transform-decorators-legacy": "^1.3.4",
|
||||||
"babel-preset-react-app": "^3.0.1",
|
"babel-preset-react-app": "^3.0.1",
|
||||||
"babel-runtime": "6.23.0",
|
"babel-runtime": "6.23.0",
|
||||||
"case-sensitive-paths-webpack-plugin": "1.1.4",
|
"case-sensitive-paths-webpack-plugin": "1.1.4",
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import React from 'react'
|
import React, { Component, Fragment } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { regExpPattern } from 'regexp-pattern'
|
import { regExpPattern } from 'regexp-pattern'
|
||||||
import { Text, Column, BoundInput, BoundButton } from 'ui'
|
import { Image, Text, Column, Row, BoundInput, BoundButton, Box } from 'ui'
|
||||||
import { MessageModal, WaitModal } from '../Modal'
|
import { MessageModal, WaitModal } from '../Modal'
|
||||||
import { api } from 'src/API'
|
import { api } from 'src/API'
|
||||||
import { FormBinder } from 'react-form-binder'
|
import { FormBinder } from 'react-form-binder'
|
||||||
|
import headerLogo from 'images/deighton.png'
|
||||||
|
import { sizeInfo, colorInfo } from 'ui/style'
|
||||||
|
import autobind from 'autobind-decorator'
|
||||||
|
|
||||||
export class ForgotPassword extends React.Component {
|
export class ForgotPassword extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
history: PropTypes.oneOfType([PropTypes.array, PropTypes.object])
|
history: PropTypes.oneOfType([PropTypes.array, PropTypes.object])
|
||||||
}
|
}
|
||||||
@@ -29,74 +32,109 @@ export class ForgotPassword extends React.Component {
|
|||||||
messageModal: null,
|
messageModal: null,
|
||||||
waitModal: null
|
waitModal: null
|
||||||
}
|
}
|
||||||
this.handleSubmit = this.handleSubmit.bind(this)
|
|
||||||
this.handleMessageModalDismiss = this.handleMessageModalDismiss.bind(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit() {
|
@autobind
|
||||||
const obj = this.state.binder.getValues()
|
handleSubmit(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
const obj = this.state.binder.getModifiedFieldValues()
|
||||||
|
|
||||||
this.setState({ waitModal: { message: 'Requesting Reset Email' } })
|
this.setState({ waitModal: { message: 'Requesting Reset Email' } })
|
||||||
api.sendResetPassword(obj.email).then((res) => {
|
|
||||||
const email = this.state.binder.getField('email').value
|
const cb = (res) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
waitModal: null,
|
waitModal: null,
|
||||||
messageModal: {
|
messageModal: {
|
||||||
error: false,
|
icon: 'thumb',
|
||||||
title: 'Password Reset Requested',
|
title: 'Password Reset Requested',
|
||||||
message: `An email will be sent to '${email}' with a reset link. Please click on it to finish resetting the password.`
|
message: `If everything checks out, an email will be sent to '${obj.email}' with a reset link. Please click on it to finish resetting the password.`
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}).catch((error) => {
|
}
|
||||||
this.setState({
|
|
||||||
binder: new FormBinder({}, ForgotPassword.bindings), // Reset to avoid rapid retries
|
api.sendResetPassword(obj.email).then(cb, cb)
|
||||||
waitModal: null,
|
|
||||||
messageModal: {
|
|
||||||
error: true,
|
|
||||||
title: 'Password Reset Failed',
|
|
||||||
message: `There was a problem requesting the password reset. ${error ? error.message : ''}`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
handleMessageModalDismiss() {
|
handleMessageModalDismiss() {
|
||||||
this.props.history.replace('/')
|
this.props.history.replace('/')
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { binder, waitModal, messageModal } = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Fragment>
|
||||||
<form onSubmit={this.handleSubmit}>
|
<Column.Item grow />
|
||||||
<Column>
|
<Column.Item>
|
||||||
<Column.Item>
|
<Row>
|
||||||
<Text size='large'>Forgotten Password</Text>
|
<Row.Item grow />
|
||||||
</Column.Item>
|
<Row.Item width={sizeInfo.formRowSpacing} />
|
||||||
<Column.Item>
|
<Row.Item width={sizeInfo.modalWidth}>
|
||||||
<BoundInput label='Email' name='email'
|
<form id='forgotPasswordForm' onSubmit={this.handleSubmit}>
|
||||||
placeholder='example@xyz.com' binder={this.state.binder}
|
<Box border={{ width: sizeInfo.headerBorderWidth, color: colorInfo.headerBorder }} radius={sizeInfo.formBoxRadius}>
|
||||||
message='A valid email address' />
|
<Row>
|
||||||
</Column.Item>
|
<Row.Item width={sizeInfo.formRowSpacing} />
|
||||||
<Column.Item>
|
<Row.Item>
|
||||||
<Text>The email address of an existing user to send the password reset link to.</Text>
|
<Column>
|
||||||
</Column.Item>
|
<Column.Item height={sizeInfo.formColumnSpacing} />
|
||||||
<Column.Item>
|
<Column.Item>
|
||||||
<BoundButton name='submit' content='Submit'
|
<Row>
|
||||||
primary submit binder={this.state.binder}>Submit</BoundButton>
|
<Row.Item grow />
|
||||||
</Column.Item>
|
<Row.Item>
|
||||||
</Column>
|
<Image source={headerLogo} width={sizeInfo.loginLogoWidth} />
|
||||||
</form>
|
</Row.Item>
|
||||||
|
<Row.Item grow />
|
||||||
|
</Row>
|
||||||
|
</Column.Item>
|
||||||
|
<Column.Item height={sizeInfo.formColumnSpacing} />
|
||||||
|
<Column.Item>
|
||||||
|
<Text size='large'>Forgotten Password</Text>
|
||||||
|
</Column.Item>
|
||||||
|
<Column.Item height={sizeInfo.formColumnSpacing} />
|
||||||
|
<Column.Item>
|
||||||
|
<BoundInput label='Email' name='email'
|
||||||
|
placeholder='example@xyz.com' binder={this.state.binder}
|
||||||
|
message='A valid email address' />
|
||||||
|
</Column.Item>
|
||||||
|
<Column.Item height={sizeInfo.formColumnSpacing} />
|
||||||
|
<Column.Item>
|
||||||
|
<Text>The email address of an existing user to send the password reset link to.</Text>
|
||||||
|
</Column.Item>
|
||||||
|
<Column.Item height={sizeInfo.formColumnSpacing} />
|
||||||
|
<Column.Item minHeight={sizeInfo.buttonHeight}>
|
||||||
|
<Row>
|
||||||
|
<Row.Item grow />
|
||||||
|
<Row.Item>
|
||||||
|
<BoundButton text='Submit' name='submit' submit='forgotPasswordForm' binder={binder} />
|
||||||
|
</Row.Item>
|
||||||
|
</Row>
|
||||||
|
</Column.Item>
|
||||||
|
<Column.Item height={sizeInfo.formColumnSpacing} />
|
||||||
|
</Column>
|
||||||
|
</Row.Item>
|
||||||
|
<Row.Item width={sizeInfo.formRowSpacing} />
|
||||||
|
</Row>
|
||||||
|
</Box>
|
||||||
|
</form>
|
||||||
|
</Row.Item>
|
||||||
|
<Row.Item grow />
|
||||||
|
</Row>
|
||||||
|
</Column.Item>
|
||||||
|
<Column.Item grow>
|
||||||
|
<WaitModal active={!!waitModal}
|
||||||
|
message={waitModal ? waitModal.message : ''} />
|
||||||
|
|
||||||
<WaitModal active={!!this.state.waitModal}
|
<MessageModal
|
||||||
message={this.state.waitModal ? this.state.waitModal.message : ''} />
|
open={!!messageModal}
|
||||||
|
icon={messageModal ? messageModal.icon : ''}
|
||||||
<MessageModal
|
message={messageModal ? messageModal.message : ''}
|
||||||
open={!!this.state.messageModal}
|
detail={messageModal ? messageModal.detail : ''}
|
||||||
error={this.state.messageModal ? this.state.messageModal.error : true}
|
onDismiss={this.handleMessageModalDismiss} />
|
||||||
title={this.state.messageModal ? this.state.messageModal.title : ''}
|
</Column.Item>
|
||||||
message={this.state.messageModal ? this.state.messageModal.message : ''}
|
</Fragment>
|
||||||
onDismiss={this.handleMessageModalDismiss} />
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ import PropTypes from 'prop-types'
|
|||||||
import { regExpPattern } from 'regexp-pattern'
|
import { regExpPattern } from 'regexp-pattern'
|
||||||
import { api } from 'src/API'
|
import { api } from 'src/API'
|
||||||
import { WaitModal, MessageModal } from '../Modal'
|
import { WaitModal, MessageModal } from '../Modal'
|
||||||
import { Image, Link, Text, Row, Column, BoundInput, BoundCheckbox, BoundButton } from 'ui'
|
import { Box, Image, Link, Text, Row, Column, BoundInput, BoundCheckbox, BoundButton } from 'ui'
|
||||||
import headerLogo from 'images/deighton.png'
|
import headerLogo from 'images/deighton.png'
|
||||||
import { versionInfo } from '../version'
|
import { versionInfo } from '../version'
|
||||||
import { FormBinder } from 'react-form-binder'
|
import { FormBinder } from 'react-form-binder'
|
||||||
import { reactAutoBind } from 'auto-bind2'
|
import { reactAutoBind } from 'auto-bind2'
|
||||||
import { sizeInfo } from 'ui/style'
|
import { sizeInfo, colorInfo } from 'ui/style'
|
||||||
|
|
||||||
export class Login extends Component {
|
export class Login extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@@ -103,52 +103,67 @@ export class Login extends Component {
|
|||||||
<Row.Item grow />
|
<Row.Item grow />
|
||||||
<Row.Item width={sizeInfo.modalWidth}>
|
<Row.Item width={sizeInfo.modalWidth}>
|
||||||
<form onSubmit={this.handleSubmit} id='loginForm'>
|
<form onSubmit={this.handleSubmit} id='loginForm'>
|
||||||
<Column minHeight='100%'>
|
<Box border={{ width: sizeInfo.headerBorderWidth, color: colorInfo.headerBorder }} radius={sizeInfo.formBoxRadius}>
|
||||||
<Column.Item>
|
<Row>
|
||||||
<Row>
|
<Row.Item width={sizeInfo.formRowSpacing} />
|
||||||
<Row.Item grow />
|
<Row.Item>
|
||||||
<Row.Item>
|
<Column>
|
||||||
<Image source={headerLogo} width={sizeInfo.loginLogoWidth} />
|
<Column.Item height={sizeInfo.formColumnSpacing} />
|
||||||
</Row.Item>
|
<Column.Item>
|
||||||
<Row.Item grow />
|
<Row>
|
||||||
</Row>
|
<Row.Item grow />
|
||||||
</Column.Item>
|
<Row.Item>
|
||||||
<Column.Item>
|
<Image source={headerLogo} width={sizeInfo.loginLogoWidth} />
|
||||||
<BoundInput label='Email' name='email'
|
</Row.Item>
|
||||||
placeholder='example@xyz.com' binder={this.state.binder}
|
<Row.Item grow />
|
||||||
message='Enter the email address associated with your account.' />
|
</Row>
|
||||||
</Column.Item>
|
</Column.Item>
|
||||||
<Column.Item>
|
<Column.Item height={sizeInfo.formColumnSpacing} />
|
||||||
<BoundInput password label='Password' name='password'
|
<Column.Item>
|
||||||
binder={this.state.binder} message='Enter your password.' />
|
<Text size='large'>Login</Text>
|
||||||
</Column.Item>
|
</Column.Item>
|
||||||
<Column.Item>
|
<Column.Item height={sizeInfo.formColumnSpacing} />
|
||||||
<Row>
|
<Column.Item>
|
||||||
<Row.Item>
|
<BoundInput label='Email' name='email'
|
||||||
<Link to='/forgot-password'>Forgot your password?</Link>
|
placeholder='example@xyz.com' binder={this.state.binder}
|
||||||
</Row.Item>
|
message='Enter the email address associated with your account.' />
|
||||||
<Row.Item grow />
|
</Column.Item>
|
||||||
<Row.Item>
|
<Column.Item>
|
||||||
<BoundCheckbox label='Remember Me' name='rememberMe' binder={this.state.binder} />
|
<BoundInput password label='Password' name='password'
|
||||||
</Row.Item>
|
binder={this.state.binder} message='Enter your password.' />
|
||||||
</Row>
|
</Column.Item>
|
||||||
</Column.Item>
|
<Column.Item>
|
||||||
<Column.Item height={sizeInfo.formColumnSpacing} />
|
<Row>
|
||||||
<Column.Item height={sizeInfo.buttonHeight}>
|
<Row.Item>
|
||||||
<Row>
|
<Link to='/forgot-password'>Forgot your password?</Link>
|
||||||
<Row.Item grow />
|
</Row.Item>
|
||||||
<Row.Item>
|
<Row.Item grow />
|
||||||
<BoundButton name='submit' text='Login' submit='loginForm' binder={this.state.binder} />
|
<Row.Item>
|
||||||
</Row.Item>
|
<BoundCheckbox label='Remember Me' name='rememberMe' binder={this.state.binder} />
|
||||||
</Row>
|
</Row.Item>
|
||||||
</Column.Item>
|
</Row>
|
||||||
<Column.Item height={sizeInfo.formColumnSpacing} />
|
</Column.Item>
|
||||||
<Column.Item>
|
<Column.Item height={sizeInfo.formColumnSpacing} />
|
||||||
<Text>
|
<Column.Item height={sizeInfo.buttonHeight}>
|
||||||
Please contact <Link to={`mailto:${versionInfo.supportEmail}`}>{versionInfo.supportEmail}</Link> to request login credentials.
|
<Row>
|
||||||
</Text>
|
<Row.Item grow />
|
||||||
</Column.Item>
|
<Row.Item>
|
||||||
</Column>
|
<BoundButton name='submit' text='Login' submit='loginForm' binder={this.state.binder} />
|
||||||
|
</Row.Item>
|
||||||
|
</Row>
|
||||||
|
</Column.Item>
|
||||||
|
<Column.Item height={sizeInfo.formColumnSpacing} />
|
||||||
|
<Column.Item>
|
||||||
|
<Text>
|
||||||
|
Please contact <Link to={`mailto:${versionInfo.supportEmail}`}>{versionInfo.supportEmail}</Link> to request login credentials.
|
||||||
|
</Text>
|
||||||
|
</Column.Item>
|
||||||
|
<Column.Item height={sizeInfo.formColumnSpacing} />
|
||||||
|
</Column>
|
||||||
|
</Row.Item>
|
||||||
|
<Row.Item width={sizeInfo.formRowSpacing} />
|
||||||
|
</Row>
|
||||||
|
</Box>
|
||||||
</form>
|
</form>
|
||||||
</Row.Item>
|
</Row.Item>
|
||||||
<Row.Item grow />
|
<Row.Item grow />
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import React from 'react'
|
import React, { Component, Fragment } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { Text, Column, BoundInput, BoundButton } from 'ui'
|
import { Box, Text, Image, Column, Row, BoundInput, BoundButton } from 'ui'
|
||||||
import { MessageModal, WaitModal } from '../Modal'
|
import { MessageModal, WaitModal } from '../Modal'
|
||||||
import { api } from 'src/API'
|
import { api } from 'src/API'
|
||||||
import { FormBinder } from 'react-form-binder'
|
import { FormBinder } from 'react-form-binder'
|
||||||
|
import { sizeInfo, colorInfo } from 'ui/style'
|
||||||
|
import autobind from 'autobind-decorator'
|
||||||
|
import headerLogo from 'images/deighton.png'
|
||||||
|
|
||||||
export class ResetPassword extends React.Component {
|
export class ResetPassword extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
history: PropTypes.oneOfType([PropTypes.array, PropTypes.object])
|
history: PropTypes.oneOfType([PropTypes.array, PropTypes.object])
|
||||||
}
|
}
|
||||||
@@ -16,11 +19,11 @@ export class ResetPassword extends React.Component {
|
|||||||
isValid: (r, v) => (v.length >= 6)
|
isValid: (r, v) => (v.length >= 6)
|
||||||
},
|
},
|
||||||
reenteredNewPassword: {
|
reenteredNewPassword: {
|
||||||
isValid: (r, v) => (v !== '' && v === r.getField('newPassword').value)
|
isValid: (r, v) => (v !== '' && v === r.getFieldValue('newPassword'))
|
||||||
},
|
},
|
||||||
submit: {
|
submit: {
|
||||||
noValue: true,
|
noValue: true,
|
||||||
isDisabled: (r) => (!r.anyModified && !r.allValid)
|
isDisabled: (r) => (!r.anyModified || !r.allValid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,73 +34,112 @@ export class ResetPassword extends React.Component {
|
|||||||
messageModal: null,
|
messageModal: null,
|
||||||
waitModal: null
|
waitModal: null
|
||||||
}
|
}
|
||||||
this.handleSubmit = this.handleSubmit.bind(this)
|
|
||||||
this.handleMessageModalDismiss = this.handleMessageModalDismiss.bind(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit() {
|
@autobind
|
||||||
const obj = this.state.binder.getValues()
|
handleSubmit(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
const obj = this.state.binder.getModifiedFieldValues()
|
||||||
const passwordToken = new URLSearchParams(decodeURIComponent(window.location.search)).get('password-token')
|
const passwordToken = new URLSearchParams(decodeURIComponent(window.location.search)).get('password-token')
|
||||||
|
|
||||||
this.setState({ waitModal: { message: 'Setting Password...' } })
|
this.setState({ waitModal: { message: 'Setting Password...' } })
|
||||||
api.resetPassword({ newPassword: obj.newPassword, passwordToken }).then(() => {
|
api.resetPassword({ newPassword: obj.newPassword, passwordToken }).then(() => {
|
||||||
this.setState({ waitModal: null })
|
this.setState({ waitModal: null })
|
||||||
this.props.history.replace('/login')
|
this.props.history.replace('/login')
|
||||||
}).catch((error) => {
|
}).catch((err) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
binder: new FormBinder({}, ResetPassword.bindings), // Reset to avoid accidental rapid retries
|
binder: new FormBinder({}, ResetPassword.bindings), // Reset to avoid accidental rapid retries
|
||||||
waitModal: null,
|
waitModal: null,
|
||||||
messageModal: {
|
messageModal: {
|
||||||
title: 'We had a problem changing your password',
|
icon: 'hand',
|
||||||
message: error.message
|
title: 'There was a problem changing your password',
|
||||||
|
detail: err.message,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
handleMessageModalDismiss() {
|
handleMessageModalDismiss() {
|
||||||
this.setState({ messageModal: null })
|
this.setState({ messageModal: null })
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { messageModal, waitModal, binder } = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Fragment>
|
||||||
<form onSubmit={this.handleSubmit} id='resetPassword'>
|
<Column.Item grow />
|
||||||
<Column>
|
<Column.Item>
|
||||||
<Column.Item>
|
<Row>
|
||||||
<Text size='large'>Reset Password</Text>
|
<Row.Item grow />
|
||||||
</Column.Item>
|
<Row.Item width={sizeInfo.formRowSpacing} />
|
||||||
<Column.Item>
|
<Row.Item width={sizeInfo.modalWidth}>
|
||||||
<BoundInput label='New Password' password name='newPassword'
|
<form onSubmit={this.handleSubmit} id='resetPasswordForm'>
|
||||||
message='A new password, cannot be blank or the same as your old password'
|
<Box border={{ width: sizeInfo.headerBorderWidth, color: colorInfo.headerBorder }} radius={sizeInfo.formBoxRadius}>
|
||||||
binder={this.state.binder} />
|
<Row>
|
||||||
</Column.Item>
|
<Row.Item width={sizeInfo.formRowSpacing} />
|
||||||
<Column.Item>
|
<Row.Item>
|
||||||
<BoundInput label='Re-entered New Password' password name='reenteredNewPassword'
|
<Column>
|
||||||
message='The new password again, must match and cannot be blank'
|
<Column.Item height={sizeInfo.formColumnSpacing} />
|
||||||
binder={this.state.binder} />
|
<Column.Item>
|
||||||
</Column.Item>
|
<Row>
|
||||||
<Column.Item>
|
<Row.Item grow />
|
||||||
<Text>
|
<Row.Item>
|
||||||
Passwords can contain special characters and should be unique to this application.
|
<Image source={headerLogo} width={sizeInfo.loginLogoWidth} />
|
||||||
<br /><br />
|
</Row.Item>
|
||||||
Passwords must be at least 6 characters long.
|
<Row.Item grow />
|
||||||
</Text>
|
</Row>
|
||||||
</Column.Item>
|
</Column.Item>
|
||||||
<Column.Item>
|
<Column.Item height={sizeInfo.formColumnSpacing} />
|
||||||
<BoundButton name='submit' text='Submit' submit='resetPassword' binder={this.state.binder} />
|
<Column.Item>
|
||||||
</Column.Item>
|
<Text size='large'>Reset Password</Text>
|
||||||
</Column>
|
</Column.Item>
|
||||||
</form>
|
<Column.Item height={sizeInfo.formColumnSpacing} />
|
||||||
|
<Column.Item>
|
||||||
|
<BoundInput label='New Password' password name='newPassword'
|
||||||
|
message='A new password, cannot be blank or the same as your old password'
|
||||||
|
binder={binder} />
|
||||||
|
</Column.Item>
|
||||||
|
<Column.Item>
|
||||||
|
<BoundInput label='Re-enter New Password' password name='reenteredNewPassword'
|
||||||
|
message='The new password again, must match and cannot be blank'
|
||||||
|
binder={binder} />
|
||||||
|
</Column.Item>
|
||||||
|
<Column.Item height={sizeInfo.formColumnSpacing} />
|
||||||
|
<Column.Item minHeight={sizeInfo.buttonHeight}>
|
||||||
|
<Row>
|
||||||
|
<Row.Item grow />
|
||||||
|
<Row.Item>
|
||||||
|
<BoundButton text='Submit' name='submit' submit='resetPasswordForm' binder={binder} />
|
||||||
|
</Row.Item>
|
||||||
|
</Row>
|
||||||
|
</Column.Item>
|
||||||
|
<Column.Item height={sizeInfo.formColumnSpacing} />
|
||||||
|
</Column>
|
||||||
|
</Row.Item>
|
||||||
|
<Row.Item width={sizeInfo.formRowSpacing} />
|
||||||
|
</Row>
|
||||||
|
</Box>
|
||||||
|
</form>
|
||||||
|
</Row.Item>
|
||||||
|
<Row.Item grow />
|
||||||
|
</Row>
|
||||||
|
</Column.Item>
|
||||||
|
<Column.Item grow>
|
||||||
|
<MessageModal
|
||||||
|
open={!!messageModal}
|
||||||
|
icon={messageModal ? messageModal.icon : ''}
|
||||||
|
title={messageModal ? messageModal.title : ''}
|
||||||
|
message={messageModal ? messageModal.message : ''}
|
||||||
|
onDismiss={this.handleMessageModalDismiss} />
|
||||||
|
|
||||||
<MessageModal error open={!!this.state.messageModal}
|
<WaitModal active={!!waitModal}
|
||||||
title={this.state.messageModal ? this.state.messageModal.title : ''}
|
message={waitModal ? waitModal.message : ''} />
|
||||||
message={this.state.messageModal ? this.state.messageModal.message : ''}
|
</Column.Item>
|
||||||
onDismiss={this.handleMessageModalDismiss} />
|
</Fragment>
|
||||||
|
|
||||||
<WaitModal active={!!this.state.waitModal}
|
|
||||||
message={this.state.waitModal ? this.state.waitModal.message : ''} />
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React from 'react'
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { Modal, Button, Column, Row, Text, Icon } from 'ui'
|
import { Modal, Button, Column, Row, Text, Icon } from 'ui'
|
||||||
import { sizeInfo } from 'ui/style'
|
import { sizeInfo } from 'ui/style'
|
||||||
import { reactAutoBind } from 'auto-bind2'
|
import autobind from 'autobind-decorator'
|
||||||
|
|
||||||
export class MessageModal extends React.Component {
|
export class MessageModal extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@@ -13,11 +13,7 @@ export class MessageModal extends React.Component {
|
|||||||
onDismiss: PropTypes.func
|
onDismiss: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
@autobind
|
||||||
super(props)
|
|
||||||
reactAutoBind(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
onSubmit(e) {
|
onSubmit(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import React, { Component } from 'react'
|
|||||||
import { Icon, Image } from '.'
|
import { Icon, Image } from '.'
|
||||||
import { colorInfo, sizeInfo } from 'ui/style'
|
import { colorInfo, sizeInfo } from 'ui/style'
|
||||||
|
|
||||||
class HeaderButton extends Component {
|
@Radium
|
||||||
|
export class HeaderButton extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
icon: PropTypes.string,
|
icon: PropTypes.string,
|
||||||
@@ -31,7 +32,7 @@ class HeaderButton extends Component {
|
|||||||
let content = null
|
let content = null
|
||||||
|
|
||||||
if (image) {
|
if (image) {
|
||||||
content = (<Image source={image} width={size} height='100%' margin={sizeInfo.headerButtonMargin} />)
|
content = (<Image source={image} width={size} height={size} margin={sizeInfo.headerButtonMargin} />)
|
||||||
} else if (icon) {
|
} else if (icon) {
|
||||||
content = (<Icon name={icon} size={size} margin={sizeInfo.headerButtonMargin} />)
|
content = (<Icon name={icon} size={size} margin={sizeInfo.headerButtonMargin} />)
|
||||||
}
|
}
|
||||||
@@ -43,5 +44,3 @@ class HeaderButton extends Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Radium(HeaderButton)
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export { default as Anime } from './Anime'
|
export { default as Anime } from './Anime'
|
||||||
export { default as Box } from './Box'
|
export { default as Box } from './Box'
|
||||||
export { default as Button } from './Button'
|
export { default as Button } from './Button'
|
||||||
export { default as HeaderButton } from './HeaderButton'
|
export { HeaderButton } from './HeaderButton'
|
||||||
export { default as HeaderText } from './HeaderText'
|
export { default as HeaderText } from './HeaderText'
|
||||||
export { default as PanelButton } from './PanelButton'
|
export { default as PanelButton } from './PanelButton'
|
||||||
export { default as Checkbox } from './Checkbox'
|
export { default as Checkbox } from './Checkbox'
|
||||||
|
|||||||
Reference in New Issue
Block a user