diff --git a/src/common/api/user.js b/src/common/api/user.js
index 1fecfbf..51da725 100644
--- a/src/common/api/user.js
+++ b/src/common/api/user.js
@@ -4,6 +4,9 @@ export default (apiEngine) => ({
verifyEmail: ({ token }) => apiEngine.post('/api/users/email/verify', {
data: { verifyEmailToken: token },
}),
+ requestVerifyEmail: (form) => (
+ apiEngine.post('/api/users/email/request-verify', { data: form })
+ ),
login: (user) => apiEngine.post('/api/users/login', { data: user }),
requestResetPassword: (form) => (
apiEngine.post('/api/users/password/request-reset', { data: form })
diff --git a/src/common/components/forms/user/VerifyEmailForm.js b/src/common/components/forms/user/VerifyEmailForm.js
new file mode 100644
index 0000000..b2bb8ad
--- /dev/null
+++ b/src/common/components/forms/user/VerifyEmailForm.js
@@ -0,0 +1,154 @@
+import React, { Component, PropTypes } from 'react';
+import { connect } from 'react-redux';
+import { push } from 'react-router-redux';
+import { Field, reduxForm } from 'redux-form';
+import Alert from 'react-bootstrap/lib/Alert';
+import Button from 'react-bootstrap/lib/Button';
+// import validator from 'validator';
+import FormNames from '../../../constants/FormNames';
+import userAPI from '../../../api/user';
+import { validateForm } from '../../../actions/formActions';
+import { pushErrors } from '../../../actions/errorActions';
+import { Form, FormField, FormFooter } from '../../utils/BsForm';
+import configs from '../../../../../configs/project/client';
+
+export let validate = (values) => {
+ let errors = {};
+
+ // if (values.email && !validator.isEmail(values.email)) {
+ // errors.email = 'Not an email';
+ // }
+
+ if (!values.email) {
+ errors.email = 'Required';
+ }
+
+ if (configs.recaptcha && !values.recaptcha) {
+ errors.recaptcha = 'Required';
+ }
+
+ return errors;
+};
+
+let asyncValidate = (values, dispatch) => {
+ return dispatch(validateForm(
+ FormNames.USER_VERIFY_EMAIL,
+ 'email',
+ values.email
+ )).then((json) => {
+ let validationError = {};
+ if (!json.isPassed) {
+ validationError.email = json.message;
+ throw validationError;
+ }
+ });
+};
+
+class VerifyEmailForm extends Component {
+ constructor() {
+ super();
+ this.handleSubmit = this._handleSubmit.bind(this);
+ this.handleCancleClick = this._handleCancleClick.bind(this);
+ }
+
+ componentDidMount() {
+ let { email, initialize } = this.props;
+
+ if (email) {
+ initialize({ email });
+ }
+ }
+
+ _handleSubmit(formData) {
+ let { dispatch, apiEngine, initialize } = this.props;
+
+ return userAPI(apiEngine)
+ .requestVerifyEmail(formData)
+ .catch((err) => {
+ dispatch(pushErrors(err));
+ throw err;
+ })
+ .then((json) => {
+ initialize({
+ email: '',
+ });
+ });
+ }
+
+ _handleCancleClick() {
+ let { onCancel, dispatch } = this.props;
+
+ if (onCancel) {
+ onCancel();
+ } else {
+ dispatch(push('/'));
+ }
+ }
+
+ render() {
+ const {
+ email,
+ handleSubmit,
+ submitSucceeded,
+ submitFailed,
+ error,
+ pristine,
+ submitting,
+ invalid,
+ } = this.props;
+
+ return (
+
+ );
+ }
+};
+
+VerifyEmailForm.propTypes = {
+ email: PropTypes.string,
+ onCancel: PropTypes.func,
+};
+
+export default reduxForm({
+ form: FormNames.USER_VERIFY_EMAIL,
+ validate,
+ asyncValidate,
+ asyncBlurFields: ['email'],
+})(connect(state => ({
+ apiEngine: state.apiEngine,
+}))(VerifyEmailForm));
diff --git a/src/common/components/pages/user/ShowPage.js b/src/common/components/pages/user/ShowPage.js
index 68a5708..5fe05e6 100644
--- a/src/common/components/pages/user/ShowPage.js
+++ b/src/common/components/pages/user/ShowPage.js
@@ -2,6 +2,7 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router';
import PageHeader from 'react-bootstrap/lib/PageHeader';
+import Modal from 'react-bootstrap/lib/Modal';
import Row from 'react-bootstrap/lib/Row';
import Col from 'react-bootstrap/lib/Col';
import Button from 'react-bootstrap/lib/Button';
@@ -11,13 +12,17 @@ import Head from '../../widgets/Head';
import PageLayout from '../../layouts/PageLayout';
import Time from '../../widgets/Time';
import RefreshImage from '../../utils/RefreshImage';
+import VerifyEmailForm from '../../forms/user/VerifyEmailForm';
class ShowPage extends Component {
constructor(props) {
super(props);
this.state = {
user: props.initialUser,
+ isShowVerifyEmailModal: false,
};
+ this.openModal = this._openModal.bind(this);
+ this.closeModal = this._closeModal.bind(this);
}
componentDidMount() {
@@ -36,6 +41,35 @@ class ShowPage extends Component {
});
}
+ _openModal() {
+ this.setState({ isShowVerifyEmailModal: true });
+ }
+
+ _closeModal() {
+ this.setState({ isShowVerifyEmailModal: false });
+ }
+
+ renderModal() {
+ let { isShowVerifyEmailModal, user } = this.state;
+
+ return (
+
+
+ Send Verification Mail
+
+
+
+
+
+ );
+ }
+
render() {
const { user } = this.state;
return (
@@ -45,6 +79,7 @@ class ShowPage extends Component {
'https://www.gstatic.com/firebasejs/live/3.0/firebase.js',
]}
/>
+ {this.renderModal()}
@@ -64,7 +99,14 @@ class ShowPage extends Component {
name
{user.name}
email
- {user.email && user.email.value}
+
+ {user.email && user.email.value}
+ {user.email && !user.email.isVerified && (
+
+ )}
+
updatedAt
diff --git a/src/common/constants/FormNames.js b/src/common/constants/FormNames.js
index ddb2a88..794e49f 100644
--- a/src/common/constants/FormNames.js
+++ b/src/common/constants/FormNames.js
@@ -3,6 +3,7 @@ export default {
USER_LOGIN: 'USER_LOGIN',
USER_EDIT: 'USER_EDIT',
USER_AVATAR: 'USER_AVATAR',
+ USER_VERIFY_EMAIL: 'USER_VERIFY_EMAIL',
USER_CHANGE_PASSWORD: 'USER_CHANGE_PASSWORD',
USER_FORGET_PASSWORD: 'USER_FORGET_PASSWORD',
USER_RESET_PASSWORD: 'USER_RESET_PASSWORD',
diff --git a/src/server/controllers/formValidation.js b/src/server/controllers/formValidation.js
index fc6c354..15ce02d 100644
--- a/src/server/controllers/formValidation.js
+++ b/src/server/controllers/formValidation.js
@@ -22,6 +22,25 @@ export default {
},
},
+ [FormNames.USER_VERIFY_EMAIL]: {
+ email(req, res) {
+ User.findOne({
+ 'email.value': req.body.value,
+ }, handleDbError(res)((user) => {
+ if (!user) {
+ res.json({
+ isPassed: false,
+ message: 'This is an invalid account',
+ });
+ } else {
+ res.json({
+ isPassed: true,
+ });
+ }
+ }));
+ },
+ },
+
[FormNames.USER_FORGET_PASSWORD]: {
email(req, res) {
User.findOne({
diff --git a/src/server/controllers/user.js b/src/server/controllers/user.js
index f1bba60..e702a23 100644
--- a/src/server/controllers/user.js
+++ b/src/server/controllers/user.js
@@ -40,6 +40,9 @@ export default {
value: req.body.email,
},
password: req.body.password,
+ nonce: {
+ verifyEmail: Math.random(),
+ },
});
user.save(handleDbError(res)((user) => {
req.user = user;
@@ -55,15 +58,12 @@ export default {
},
verifyEmail(req, res) {
- User.findById(req.decodedPayload._id, handleDbError(res)((user) => {
- if (user.email.isVerified) {
- return res.errors([Errors.TOKEN_REUSED]);
- }
- user.email.isVerified = true;
- user.email.verifiedAt = new Date();
- user.save(handleDbError(res)(() => {
- res.json({});
- }));
+ let { user } = req;
+
+ user.email.isVerified = true;
+ user.email.verifiedAt = new Date();
+ user.save(handleDbError(res)(() => {
+ res.json({});
}));
},
diff --git a/src/server/models/User.js b/src/server/models/User.js
index e4fe0c2..3154cae 100644
--- a/src/server/models/User.js
+++ b/src/server/models/User.js
@@ -52,7 +52,8 @@ let UserSchema = new mongoose.Schema({
},
},
nonce: {
- password: Number,
+ verifyEmail: Number,
+ resetPassword: Number,
},
lastLoggedInAt: Date,
}, {
@@ -73,6 +74,7 @@ UserSchema.methods.auth = function(password, cb) {
UserSchema.methods.toVerifyEmailToken = function(cb) {
const user = {
_id: this._id,
+ nonce: this.nonce.verifyEmail,
};
const token = jwt.sign(user, configs.jwt.verifyEmail.secret, {
expiresIn: configs.jwt.verifyEmail.expiresIn,
@@ -83,7 +85,7 @@ UserSchema.methods.toVerifyEmailToken = function(cb) {
UserSchema.methods.toResetPasswordToken = function(cb) {
const user = {
_id: this._id,
- nonce: this.nonce.password,
+ nonce: this.nonce.resetPassword,
};
const token = jwt.sign(user, configs.jwt.resetPassword.secret, {
expiresIn: configs.jwt.resetPassword.expiresIn,
diff --git a/src/server/routes/api.js b/src/server/routes/api.js
index 2ff4a45..916cd96 100644
--- a/src/server/routes/api.js
+++ b/src/server/routes/api.js
@@ -31,14 +31,22 @@ export default ({ app }) => {
'verifyEmailToken',
configs.jwt.verifyEmail.secret
),
+ validate.verifyUserNonce('verifyEmail'),
userController.verifyEmail
);
+ app.post('/api/users/email/request-verify',
+ bodyParser.json,
+ validate.form('user/VerifyEmailForm'),
+ validate.recaptcha,
+ userController.setNonce('verifyEmail'),
+ mailController.sendVerification
+ );
app.post('/api/users/login', bodyParser.json, userController.login);
app.post('/api/users/password/request-reset',
bodyParser.json,
validate.form('user/ForgetPasswordForm'),
validate.recaptcha,
- userController.setNonce('password'),
+ userController.setNonce('resetPassword'),
mailController.sendResetPasswordLink
);
app.put('/api/users/password',
@@ -47,7 +55,7 @@ export default ({ app }) => {
'resetPasswordToken',
configs.jwt.resetPassword.secret
),
- validate.verifyUserNonce('password'),
+ validate.verifyUserNonce('resetPassword'),
validate.form('user/ResetPasswordForm'),
userController.resetPassword
);
@@ -89,6 +97,11 @@ export default ({ app }) => {
bodyParser.json,
formValidationController[FormNames.USER_REGISTER].email
);
+ app.post(
+ `/api/forms/${FormNames.USER_VERIFY_EMAIL}/fields/email/validation`,
+ bodyParser.json,
+ formValidationController[FormNames.USER_VERIFY_EMAIL].email
+ );
app.post(
`/api/forms/${FormNames.USER_FORGET_PASSWORD}/fields/email/validation`,
bodyParser.json,