Skip to content

Commit 5a6ef75

Browse files
committed
Merge branch 'feature/user-verification' into dev
2 parents 616ca6e + 46ce6e4 commit 5a6ef75

File tree

13 files changed

+201
-15
lines changed

13 files changed

+201
-15
lines changed

configs/project/server.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,20 @@ if (process.env.TRAVIS) {
88
module.exports = JSON.parse(process.env.PROJECT_SERVER_CONFIGS);
99
} else {
1010
module.exports = {
11+
host: {
12+
development: 'http://localhost:3000',
13+
test: 'http://localhost:5566',
14+
production: 'https://express-react-hmr-boilerplate.herokuapp.com',
15+
},
1116
jwt: {
12-
secret: '4eO5viHe23',
13-
expiresIn: 60 * 60 * 24 * 3, // in seconds
17+
authentication: {
18+
secret: '4eO5viHe23',
19+
expiresIn: 60 * 60 * 24 * 3, // in seconds
20+
},
21+
verification: {
22+
secret: 'df5s6sdHdjJdRg56',
23+
expiresIn: 60 * 60, // in seconds
24+
},
1425
},
1526
mongo: require('./mongo/credential'),
1627
firebase: require('./firebase/credential.json'),

specs/endToEnd/apis/user.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ describe('#user', () => {
129129
it('should show user', (done) => {
130130
User.findOne({}, (err, user) => {
131131
expect(err).to.equal(null);
132-
let token = user.toJwtToken();
132+
let token = user.toAuthenticationToken();
133133
request
134134
.get(constants.BASE + '/api/users/me')
135135
.set('Cookie', 'token=' + token)

specs/endToEnd/pages.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ describe('#Pages', () => {
2121
if (err) {
2222
return done(err);
2323
}
24-
userTokens[0] = user.toJwtToken();
24+
userTokens[0] = user.toAuthenticationToken();
2525
done();
2626
});
2727
});

src/common/api/user.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
export default (apiEngine) => ({
22
list: ({ page }) => apiEngine.get('/api/users', { params: { page } }),
33
register: (user) => apiEngine.post('/api/users', { data: user }),
4+
verify: ({ token }) => apiEngine.post('/api/users/verification', {
5+
data: { verificationToken: token },
6+
}),
47
login: (user) => apiEngine.post('/api/users/login', { data: user }),
58
logout: () => apiEngine.get('/api/users/logout'),
69
show: () => apiEngine.get('/api/users/me'),
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import React from 'react';
2+
import { connect } from 'react-redux';
3+
import { Link } from 'react-router';
4+
import Alert from 'react-bootstrap/lib/Alert';
5+
import userAPI from '../../../api/user';
6+
import PageLayout from '../../layouts/PageLayout';
7+
8+
class VerificationPage extends React.Component {
9+
constructor() {
10+
super();
11+
this.state = {
12+
isVerifying: true,
13+
isFail: true,
14+
};
15+
}
16+
17+
componentWillMount() {
18+
let { apiEngine, location } = this.props;
19+
20+
userAPI(apiEngine)
21+
.verify({ token: location.query.token })
22+
.catch((err) => {
23+
this.setState({
24+
isVerifying: false,
25+
isFail: true,
26+
});
27+
throw err;
28+
})
29+
.then((json) => {
30+
this.setState({
31+
isVerifying: false,
32+
isFail: false,
33+
});
34+
});
35+
}
36+
37+
render() {
38+
let { isVerifying, isFail } = this.state;
39+
40+
if (isVerifying) {
41+
return (
42+
<p>Please wait for a while...</p>
43+
);
44+
}
45+
46+
if (isFail) {
47+
return (
48+
<PageLayout>
49+
<Alert bsStyle="danger">
50+
<strong>Verification Failed</strong>
51+
</Alert>
52+
</PageLayout>
53+
);
54+
}
55+
56+
return (
57+
<PageLayout>
58+
<Alert bsStyle="success">
59+
<strong>Verification Success</strong>
60+
<p>
61+
Go to <Link to="/user/login">Login Page</Link>
62+
</p>
63+
</Alert>
64+
</PageLayout>
65+
);
66+
}
67+
};
68+
69+
export default connect(state => ({
70+
apiEngine: state.apiEngine,
71+
}))(VerificationPage);

src/common/routes/user/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export default (store) => ({
44
require.ensure([], (require) => {
55
cb(null, [
66
require('./register').default(store),
7+
require('./verification').default(store),
78
require('./login').default(store),
89
require('./logout').default(store),
910
require('./me').default(store),
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export default (store) => ({
2+
path: 'verification',
3+
getComponent(nextState, cb) {
4+
require.ensure([], (require) => {
5+
cb(null, require('../../components/pages/user/VerificationPage').default);
6+
});
7+
},
8+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import React, { PropTypes } from 'react';
2+
import configs from '../../../configs/project/server';
3+
4+
let tokenToURL = (token) => (
5+
`${configs.host[process.env.NODE_ENV]}` +
6+
`/user/verification?token=${token}`
7+
);
8+
9+
let VerfificationMail = ({ token }) => (
10+
<div>
11+
<p>
12+
Please click the following link to verify your account.
13+
</p>
14+
<p>
15+
<a href={tokenToURL(token)}>
16+
{tokenToURL(token)}
17+
</a>
18+
</p>
19+
</div>
20+
);
21+
22+
VerfificationMail.propTypes = {
23+
token: PropTypes.string,
24+
};
25+
26+
export default VerfificationMail;

src/server/controllers/mail.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React from 'react';
2+
import { renderToString } from 'react-dom/server';
3+
import Errors from '../../common/constants/Errors';
4+
import nodemailerAPI from '../api/nodemailer';
5+
import VerfificationMail from '../components/VerificationMail';
6+
7+
export default {
8+
sendVerification(req, res) {
9+
let { user } = req;
10+
let token = user.toVerificationToken();
11+
12+
nodemailerAPI()
13+
.sendMail({
14+
...(
15+
process.env.NODE_ENV === 'production' ?
16+
{ to: user.email.value } :
17+
{}
18+
),
19+
subject: 'Email Verification',
20+
html: renderToString(
21+
<VerfificationMail token={token} />
22+
),
23+
})
24+
.catch((err) => {
25+
res.errors([Errors.SEND_EMAIL_FAIL]);
26+
throw err;
27+
})
28+
.then((info) => {
29+
res.json({
30+
user: user,
31+
email: info.envelope,
32+
});
33+
});
34+
},
35+
};

src/server/controllers/user.js

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import jwt from 'jsonwebtoken';
2+
import configs from '../../../configs/project/server';
13
import Errors from '../../common/constants/Errors';
24
import { handleDbError } from '../decorators/handleError';
35
import User from '../models/User';
@@ -22,7 +24,7 @@ export default {
2224
}));
2325
},
2426

25-
create(req, res) {
27+
create(req, res, next) {
2628
User.findOne({
2729
'email.value': req.body.email,
2830
}, handleDbError(res)((user) => {
@@ -37,14 +39,26 @@ export default {
3739
password: req.body.password,
3840
});
3941
user.save(handleDbError(res)((user) => {
40-
res.json({
41-
user: user,
42-
});
42+
req.user = user;
43+
next();
4344
}));
4445
}
4546
}));
4647
},
4748

49+
verify(req, res) {
50+
let token = req.body.verificationToken;
51+
let { _id } = jwt.verify(token, configs.jwt.verification.secret);
52+
53+
User.findById(_id, handleDbError(res)((user) => {
54+
user.email.isVerified = true;
55+
user.verifiedAt = new Date();
56+
user.save(handleDbError(res)(() => {
57+
res.json({});
58+
}));
59+
}));
60+
},
61+
4862
login(req, res) {
4963
User.findOne({
5064
'email.value': req.body.email,
@@ -56,7 +70,7 @@ export default {
5670
} else {
5771
user.auth(req.body.password, handleDbError(res)((isAuth) => {
5872
if (isAuth) {
59-
const token = user.toJwtToken();
73+
const token = user.toAuthenticationToken();
6074
user.lastLoggedInAt = new Date();
6175
user.save(handleDbError(res)((user) => {
6276
res.json({
@@ -80,7 +94,7 @@ export default {
8094
if (!user) {
8195
return next();
8296
}
83-
let token = user.toJwtToken();
97+
let token = user.toAuthenticationToken();
8498

8599
user.lastLoggedInAt = new Date();
86100
user.save(handleDbError(res)(() => {

0 commit comments

Comments
 (0)