Skip to content

Commit

Permalink
Merge branch 'feature/email_verification_#21'
Browse files Browse the repository at this point in the history
Closes #21
  • Loading branch information
cleverbeagle committed Aug 1, 2017
2 parents 54d7acb + 75b4709 commit 6b9d557
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 10 deletions.
7 changes: 6 additions & 1 deletion imports/api/Users/server/methods.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { Meteor } from 'meteor/meteor';
import { check, Match } from 'meteor/check';
import { check } from 'meteor/check';
import { Accounts } from 'meteor/accounts-base';
import editProfile from './edit-profile';
import rateLimit from '../../../modules/rate-limit';

Meteor.methods({
'users.sendVerificationEmail': function usersResendVerification() {
return Accounts.sendVerificationEmail(this.userId);
},
'users.editProfile': function usersEditProfile(profile) {
check(profile, {
emailAddress: String,
Expand All @@ -25,6 +29,7 @@ Meteor.methods({

rateLimit({
methods: [
'users.resendVerification',
'users.editProfile',
],
limit: 5,
Expand Down
22 changes: 14 additions & 8 deletions imports/startup/server/accounts/email-templates.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,26 @@ const emailTemplates = Accounts.emailTemplates;
emailTemplates.siteName = name;
emailTemplates.from = from;

emailTemplates.verifyEmail = {
subject() {
return `[${name}] Verify Your Email Address`;
},
text(user, url) {
const userEmail = user.emails[0].address;
const urlWithoutHash = url.replace('#/', '');
if (Meteor.isDevelopment) console.info(`Verify Email Link: ${urlWithoutHash}`); // eslint-disable-line
return `Hey, ${user.profile.name.first}! Welcome to ${name}.\n\nTo verify your email address (${userEmail}), click the link below:\n\n${urlWithoutHash}\n\nIf you feel something is wrong, please contact our support team: ${email}.`;
},
};

emailTemplates.resetPassword = {
subject() {
return `[${name}] Reset Your Password`;
},
text(user, url) {
const userEmail = user.emails[0].address;
const urlWithoutHash = url.replace('#/', '');

if (Meteor.isDevelopment) console.info(`Reset Password Link: ${urlWithoutHash}`);

return `A password reset has been requested for the account related to this
address (${userEmail}). To reset the password, visit the following link:
\n\n${urlWithoutHash}\n\n If you did not request this reset, please ignore
this email. If you feel something is wrong, please contact our support team:
${email}.`;
if (Meteor.isDevelopment) console.info(`Reset Password Link: ${urlWithoutHash}`); // eslint-disable-line
return `A password reset has been requested for the account related to this address (${userEmail}).\n\nTo reset the password, visit the following link: \n\n${urlWithoutHash}\n\n If you did not request this reset, please ignore this email. If you feel something is wrong, please contact our support team: ${email}.`;
},
};
29 changes: 28 additions & 1 deletion imports/ui/layouts/App/App.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
/* eslint-disable jsx-a11y/no-href*/

import React from 'react';
import PropTypes from 'prop-types';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import { Grid } from 'react-bootstrap';
import { Grid, Alert, Button } from 'react-bootstrap';
import { Meteor } from 'meteor/meteor';
import { createContainer } from 'meteor/react-meteor-data';
import { Roles } from 'meteor/alanning:roles';
import { Bert } from 'meteor/themeteorchef:bert';
import Navigation from '../../components/Navigation/Navigation';
import Authenticated from '../../components/Authenticated/Authenticated';
import Public from '../../components/Public/Public';
Expand All @@ -16,6 +19,7 @@ import EditDocument from '../../pages/EditDocument/EditDocument';
import Signup from '../../pages/Signup/Signup';
import Login from '../../pages/Login/Login';
import Logout from '../../pages/Logout/Logout';
import VerifyEmail from '../../pages/VerifyEmail/VerifyEmail';
import RecoverPassword from '../../pages/RecoverPassword/RecoverPassword';
import ResetPassword from '../../pages/ResetPassword/ResetPassword';
import Profile from '../../pages/Profile/Profile';
Expand All @@ -27,9 +31,20 @@ import ExamplePage from '../../pages/ExamplePage/ExamplePage';

import './App.scss';

const handleResendVerificationEmail = (emailAddress) => {
Meteor.call('users.sendVerificationEmail', (error) => {
if (error) {
Bert.alert(error.reason, 'danger');
} else {
Bert.alert(`Check ${emailAddress} for a verification link!`, 'success');
}
});
};

const App = props => (
<Router>
{!props.loading ? <div className="App">
{props.userId && !props.emailVerified ? <Alert className="verify-email text-center"><p>Hey friend! Can you <strong>verify your email address</strong> ({props.emailAddress}) for us? <Button bsStyle="link" onClick={() => handleResendVerificationEmail(props.emailAddress)} href="#">Re-send verification email</Button></p></Alert> : ''}
<Navigation {...props} />
<Grid>
<Switch>
Expand All @@ -42,6 +57,7 @@ const App = props => (
<Public path="/signup" component={Signup} {...props} />
<Public path="/login" component={Login} {...props} />
<Public path="/logout" component={Logout} {...props} />
<Route name="verify-email" path="/verify-email/:token" component={VerifyEmail} />
<Route name="recover-password" path="/recover-password" component={RecoverPassword} />
<Route name="reset-password" path="/reset-password/:token" component={ResetPassword} />
<Route name="terms" path="/terms" component={Terms} />
Expand All @@ -55,8 +71,16 @@ const App = props => (
</Router>
);

App.defaultProps = {
userId: '',
emailAddress: '',
};

App.propTypes = {
loading: PropTypes.bool.isRequired,
userId: PropTypes.string,
emailAddress: PropTypes.string,
emailVerified: PropTypes.bool.isRequired,
};

const getUserName = name => ({
Expand All @@ -78,5 +102,8 @@ export default createContainer(() => {
authenticated: !loggingIn && !!userId,
name: name || emailAddress,
roles: !loading && Roles.getRolesForUser(userId),
userId,
emailAddress,
emailVerified: user ? user && user.emails && user.emails[0].verified : true,
};
}, App);
20 changes: 20 additions & 0 deletions imports/ui/layouts/App/App.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
@import '../../stylesheets/colors';

.App > .container {
margin-bottom: 20px;
}

.App .verify-email {
margin-bottom: 0;
padding: 0;
border-top: none;
border-bottom: 1px solid #e7e7e7;
background: #fff;
color: $gray-dark;
border-radius: 0;

p {
padding: 19px;
}

.btn {
padding: 0;
}
}
2 changes: 2 additions & 0 deletions imports/ui/pages/Signup/Signup.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import { Row, Col, FormGroup, ControlLabel, Button } from 'react-bootstrap';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import { Bert } from 'meteor/themeteorchef:bert';
import OAuthLoginButtons from '../../components/OAuthLoginButtons/OAuthLoginButtons';
Expand Down Expand Up @@ -71,6 +72,7 @@ class Signup extends React.Component {
if (error) {
Bert.alert(error.reason, 'danger');
} else {
Meteor.call('users.sendVerificationEmail');
Bert.alert('Welcome!', 'success');
history.push('/documents');
}
Expand Down
42 changes: 42 additions & 0 deletions imports/ui/pages/VerifyEmail/VerifyEmail.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Alert } from 'react-bootstrap';
import { Accounts } from 'meteor/accounts-base';
import { Bert } from 'meteor/themeteorchef:bert';

class VerifyEmail extends React.Component {
constructor(props) {
super(props);
this.state = { error: null };
}

componentDidMount() {
const { match, history } = this.props;
Accounts.verifyEmail(match.params.token, (error) => {
if (error) {
Bert.alert(error.reason, 'danger');
this.setState({ error: `${error.reason}. Please try again.` });
} else {
setTimeout(() => {
Bert.alert('All set, thanks!', 'success');
history.push('/documents');
}, 2000);
}
});
}

render() {
return (<div className="VerifyEmail">
<Alert bsStyle={!this.state.error ? 'info' : 'danger'}>
{!this.state.error ? 'Verifying...' : this.state.error}
</Alert>
</div>);
}
}

VerifyEmail.propTypes = {
match: PropTypes.object.isRequired,
history: PropTypes.object.isRequired,
};

export default VerifyEmail;

0 comments on commit 6b9d557

Please sign in to comment.