unify navigation for authentication pages, add authorization to front end, fixes #650

This commit is contained in:
Cassie Tarakajian 2019-09-19 13:38:27 -04:00
parent 7f2529a973
commit 5900e62904
17 changed files with 220 additions and 218 deletions

View File

@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import { connect } from 'react-redux';
import { withRouter } from 'react-router';
import { Link } from 'react-router';
import { Link, browserHistory } from 'react-router';
import InlineSVG from 'react-inlinesvg';
import classNames from 'classnames';
import * as IDEActions from '../modules/IDE/actions/ide';
@ -93,11 +93,12 @@ class Nav extends React.PureComponent {
}
handleNew() {
if (!this.props.unsavedChanges) {
const { unsavedChanges, warnIfUnsavedChanges } = this.props;
if (!unsavedChanges) {
this.props.showToast(1500);
this.props.setToastText('Opened new sketch.');
this.props.newProject();
} else if (this.props.warnIfUnsavedChanges()) {
} else if (warnIfUnsavedChanges && warnIfUnsavedChanges()) {
this.props.showToast(1500);
this.props.setToastText('Opened new sketch.');
this.props.newProject();
@ -166,6 +167,8 @@ class Nav extends React.PureComponent {
handleLogout() {
this.props.logoutUser();
// if you're on the settings page, probably.
browserHistory.push('/');
this.setDropdown('none');
}
@ -535,13 +538,13 @@ class Nav extends React.PureComponent {
renderUnauthenticatedUserMenu(navDropdownState) {
return (
<ul className="nav__items-right" title="user-menu">
<li>
<li className="nav__item">
<Link to="/login">
<span className="nav__item-header">Log in</span>
</Link>
</li>
<span className="nav__item-spacer">or</span>
<li>
<li className="nav__item">
<Link to="/signup">
<span className="nav__item-header">Sign up</span>
</Link>
@ -708,7 +711,7 @@ Nav.propTypes = {
showShareModal: PropTypes.func.isRequired,
showErrorModal: PropTypes.func.isRequired,
unsavedChanges: PropTypes.bool.isRequired,
warnIfUnsavedChanges: PropTypes.func.isRequired,
warnIfUnsavedChanges: PropTypes.func,
showKeyboardShortcutModal: PropTypes.func.isRequired,
cmController: PropTypes.shape({
tidyCode: PropTypes.func,
@ -731,7 +734,8 @@ Nav.defaultProps = {
owner: undefined
},
cmController: {},
layout: 'project'
layout: 'project',
warnIfUnsavedChanges: undefined
};
function mapStateToProps(state) {

View File

@ -86,7 +86,8 @@ class APIKeyForm extends React.Component {
disabled={this.state.keyLabel === ''}
type="submit"
>
<InlineSVG src={plusIcon} alt="" /> Create
<InlineSVG src={plusIcon} className="api-key-form__create-icon" />
Create
</button>
</form>

View File

@ -2,7 +2,6 @@ import PropTypes from 'prop-types';
import React from 'react';
import { reduxForm } from 'redux-form';
import { bindActionCreators } from 'redux';
import { browserHistory } from 'react-router';
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
import axios from 'axios';
import { Helmet } from 'react-helmet';
@ -11,48 +10,37 @@ import AccountForm from '../components/AccountForm';
import { validateSettings } from '../../../utils/reduxFormUtils';
import GithubButton from '../components/GithubButton';
import APIKeyForm from '../components/APIKeyForm';
import NavBasic from '../../../components/NavBasic';
import Nav from '../../../components/Nav';
const __process = (typeof global !== 'undefined' ? global : window).process;
const ROOT_URL = __process.env.API_URL;
class AccountView extends React.Component {
constructor(props) {
super(props);
this.closeAccountPage = this.closeAccountPage.bind(this);
this.gotoHomePage = this.gotoHomePage.bind(this);
}
componentDidMount() {
document.body.className = this.props.theme;
}
closeAccountPage() {
browserHistory.push(this.props.previousPath);
}
gotoHomePage() {
browserHistory.push('/');
}
render() {
const accessTokensUIEnabled = window.process.env.UI_ACCESS_TOKEN_ENABLED;
return (
<div className="user">
<div className="account-settings__container">
<Helmet>
<title>p5.js Web Editor | Account</title>
<title>p5.js Web Editor | Account Settings</title>
</Helmet>
<NavBasic onBack={this.closeAccountPage} />
<Nav layout="dashboard" />
<section className="modal">
<div className="modal-content">
<div className="modal__header">
<h2 className="modal__title">My Account</h2>
</div>
<section className="account-settings">
<header className="account-settings__header">
<h1 className="account-settings__title">Account Settings</h1>
</header>
{accessTokensUIEnabled &&
<Tabs className="account__tabs">
<TabList>
<div className="tabs__titles">
<Tab><h4 className="tabs__title">Account</h4></Tab>
{accessTokensUIEnabled && <Tab><h4 className="tabs__title">Access Tokens</h4></Tab>}
<Tab><h4 className="tabs__title">Access Tokens</h4></Tab>
</div>
</TabList>
<TabPanel>
@ -67,7 +55,17 @@ class AccountView extends React.Component {
<APIKeyForm {...this.props} />
</TabPanel>
</Tabs>
</div>
}
{!accessTokensUIEnabled &&
<div>
<AccountForm {...this.props} />
<h2 className="form-container__divider">Social Login</h2>
<p className="account__social-text">
Link this account with your GitHub account to allow login from both.
</p>
<GithubButton buttonText="Login with GitHub" />
</div>
}
</section>
</div>
);
@ -96,7 +94,7 @@ function asyncValidate(formProps, dispatch, props) {
const queryParams = {};
queryParams[fieldToValidate] = formProps[fieldToValidate];
queryParams.check_type = fieldToValidate;
return axios.get('/api/signup/duplicate_check', { params: queryParams })
return axios.get(`${ROOT_URL}/signup/duplicate_check`, { params: queryParams })
.then((response) => {
if (response.data.exists) {
const error = {};

View File

@ -3,13 +3,10 @@ import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { browserHistory } from 'react-router';
import InlineSVG from 'react-inlinesvg';
import get from 'lodash/get';
import { Helmet } from 'react-helmet';
import { verifyEmailConfirmation } from '../actions';
const exitUrl = require('../../../images/exit.svg');
const logoUrl = require('../../../images/p5js-logo.svg');
import Nav from '../../../components/Nav';
class EmailVerificationView extends React.Component {
@ -17,12 +14,6 @@ class EmailVerificationView extends React.Component {
emailVerificationTokenState: null,
}
constructor(props) {
super(props);
this.closeLoginPage = this.closeLoginPage.bind(this);
this.gotoHomePage = this.gotoHomePage.bind(this);
}
componentWillMount() {
const verificationToken = this.verificationToken();
if (verificationToken != null) {
@ -32,14 +23,6 @@ class EmailVerificationView extends React.Component {
verificationToken = () => get(this.props, 'location.query.t', null);
closeLoginPage() {
browserHistory.push(this.props.previousPath);
}
gotoHomePage() {
browserHistory.push('/');
}
render() {
let status = null;
const {
@ -48,7 +31,7 @@ class EmailVerificationView extends React.Component {
if (this.verificationToken() == null) {
status = (
<p>That link is invalid</p>
<p>That link is invalid.</p>
);
} else if (emailVerificationTokenState === 'checking') {
status = (
@ -58,6 +41,7 @@ class EmailVerificationView extends React.Component {
status = (
<p>All done, your email address has been verified.</p>
);
setTimeout(() => browserHistory.push('/'), 1000);
} else if (emailVerificationTokenState === 'invalid') {
status = (
<p>Something went wrong.</p>
@ -65,19 +49,12 @@ class EmailVerificationView extends React.Component {
}
return (
<div className="user">
<div className="email-verification">
<Nav layout="dashboard" />
<div className="form-container">
<Helmet>
<title>p5.js Web Editor | Email Verification</title>
</Helmet>
<div className="form-container__header">
<button className="form-container__logo-button" onClick={this.gotoHomePage}>
<InlineSVG src={logoUrl} alt="p5js Logo" />
</button>
<button className="form-container__exit-button" onClick={this.closeLoginPage}>
<InlineSVG src={exitUrl} alt="Close Login Page" />
</button>
</div>
<div className="form-container__content">
<h2 className="form-container__title">Verify your email</h2>
{status}
@ -91,7 +68,6 @@ class EmailVerificationView extends React.Component {
function mapStateToProps(state) {
return {
emailVerificationTokenState: state.user.emailVerificationTokenState,
previousPath: state.ide.previousPath
};
}
@ -103,7 +79,6 @@ function mapDispatchToProps(dispatch) {
EmailVerificationView.propTypes = {
previousPath: PropTypes.string.isRequired,
emailVerificationTokenState: PropTypes.oneOf([
'checking', 'verified', 'invalid'
]),

View File

@ -2,16 +2,13 @@ import PropTypes from 'prop-types';
import React from 'react';
import { reduxForm } from 'redux-form';
import { Link, browserHistory } from 'react-router';
import InlineSVG from 'react-inlinesvg';
import { Helmet } from 'react-helmet';
import { validateAndLoginUser } from '../actions';
import LoginForm from '../components/LoginForm';
import { validateLogin } from '../../../utils/reduxFormUtils';
import GithubButton from '../components/GithubButton';
import GoogleButton from '../components/GoogleButton';
const exitUrl = require('../../../images/exit.svg');
const logoUrl = require('../../../images/p5js-logo.svg');
import Nav from '../../../components/Nav';
class LoginView extends React.Component {
constructor(props) {
@ -34,19 +31,12 @@ class LoginView extends React.Component {
return null;
}
return (
<div className="user">
<div className="login">
<Nav layout="dashboard" />
<div className="form-container">
<Helmet>
<title>p5.js Web Editor | Login</title>
</Helmet>
<div className="form-container__header">
<button className="form-container__logo-button" onClick={this.gotoHomePage}>
<InlineSVG src={logoUrl} alt="p5js Logo" />
</button>
<button className="form-container__exit-button" onClick={this.closeLoginPage}>
<InlineSVG src={exitUrl} alt="Close Login Page" />
</button>
</div>
<div className="form-container__content">
<h2 className="form-container__title">Log In</h2>
<LoginForm {...this.props} />

View File

@ -2,63 +2,36 @@ import PropTypes from 'prop-types';
import React from 'react';
import { reduxForm } from 'redux-form';
import classNames from 'classnames';
import { browserHistory } from 'react-router';
import InlineSVG from 'react-inlinesvg';
import { bindActionCreators } from 'redux';
import { Helmet } from 'react-helmet';
import NewPasswordForm from '../components/NewPasswordForm';
import * as UserActions from '../actions';
import Nav from '../../../components/Nav';
const exitUrl = require('../../../images/exit.svg');
const logoUrl = require('../../../images/p5js-logo.svg');
class NewPasswordView extends React.Component {
constructor(props) {
super(props);
this.gotoHomePage = this.gotoHomePage.bind(this);
}
componentDidMount() {
// need to check if this is a valid token
this.props.validateResetPasswordToken(this.props.params.reset_password_token);
}
gotoHomePage() {
browserHistory.push('/');
}
render() {
const newPasswordClass = classNames({
'new-password': true,
'new-password--invalid': this.props.user.resetPasswordInvalid,
'form-container': true,
'user': true
});
return (
<div className="user">
<div className={newPasswordClass}>
<Helmet>
<title>p5.js Web Editor | New Password</title>
</Helmet>
<div className="form-container__header">
<button className="form-container__logo-button" onClick={this.gotoHomePage}>
<InlineSVG src={logoUrl} alt="p5js Logo" />
</button>
<button className="form-container__exit-button" onClick={this.gotoHomePage}>
<InlineSVG src={exitUrl} alt="Close NewPassword Page" />
</button>
</div>
<div className="form-container__content">
<h2 className="form-container__title">Set a New Password</h2>
<NewPasswordForm {...this.props} />
<p className="new-password__invalid">
The password reset token is invalid or has expired.
</p>
</div>
function NewPasswordView(props) {
const newPasswordClass = classNames({
'new-password': true,
'new-password--invalid': props.user.resetPasswordInvalid,
'form-container': true,
'user': true
});
return (
<div className="new-password-container">
<Nav layout="dashboard" />
<div className={newPasswordClass}>
<Helmet>
<title>p5.js Web Editor | New Password</title>
</Helmet>
<div className="form-container__content">
<h2 className="form-container__title">Set a New Password</h2>
<NewPasswordForm {...props} />
<p className="new-password__invalid">
The password reset token is invalid or has expired.
</p>
</div>
</div>
);
}
</div>
);
}
NewPasswordView.propTypes = {

View File

@ -1,71 +1,46 @@
import PropTypes from 'prop-types';
import React from 'react';
import { Link, browserHistory } from 'react-router';
import { Link } from 'react-router';
import classNames from 'classnames';
import InlineSVG from 'react-inlinesvg';
import { bindActionCreators } from 'redux';
import { reduxForm } from 'redux-form';
import { Helmet } from 'react-helmet';
import * as UserActions from '../actions';
import ResetPasswordForm from '../components/ResetPasswordForm';
import { validateResetPassword } from '../../../utils/reduxFormUtils';
import Nav from '../../../components/Nav';
const exitUrl = require('../../../images/exit.svg');
const logoUrl = require('../../../images/p5js-logo.svg');
class ResetPasswordView extends React.Component {
constructor(props) {
super(props);
this.gotoHomePage = this.gotoHomePage.bind(this);
}
componentWillMount() {
this.props.resetPasswordReset();
}
gotoHomePage() {
browserHistory.push('/');
}
render() {
const resetPasswordClass = classNames({
'reset-password': true,
'reset-password--submitted': this.props.user.resetPasswordInitiate,
'form-container': true,
'user': true
});
return (
<div className="user">
<div className={resetPasswordClass}>
<Helmet>
<title>p5.js Web Editor | Reset Password</title>
</Helmet>
<div className="form-container__header">
<button className="form-container__logo-button" onClick={this.gotoHomePage}>
<InlineSVG src={logoUrl} alt="p5js Logo" />
</button>
<button className="form-container__exit-button" onClick={this.gotoHomePage}>
<InlineSVG src={exitUrl} alt="Close ResetPassword Page" />
</button>
</div>
<div className="form-container__content">
<h2 className="form-container__title">Reset Your Password</h2>
<ResetPasswordForm {...this.props} />
<p className="reset-password__submitted">
Your password reset email should arrive shortly. If you don&apos;t see it, check
in your spam folder as sometimes it can end up there.
</p>
<p className="form__navigation-options">
<Link className="form__login-button" to="/login">Log In</Link>
&nbsp;or&nbsp;
<Link className="form__signup-button" to="/signup">Sign Up</Link>
</p>
</div>
function ResetPasswordView(props) {
const resetPasswordClass = classNames({
'reset-password': true,
'reset-password--submitted': props.user.resetPasswordInitiate,
'form-container': true,
'user': true
});
return (
<div className="reset-password-container">
<Nav layout="dashboard" />
<div className={resetPasswordClass}>
<Helmet>
<title>p5.js Web Editor | Reset Password</title>
</Helmet>
<div className="form-container__content">
<h2 className="form-container__title">Reset Your Password</h2>
<ResetPasswordForm {...props} />
<p className="reset-password__submitted">
Your password reset email should arrive shortly. If you don&apos;t see it, check
in your spam folder as sometimes it can end up there.
</p>
<p className="form__navigation-options">
<Link className="form__login-button" to="/login">Log In</Link>
&nbsp;or&nbsp;
<Link className="form__signup-button" to="/signup">Sign Up</Link>
</p>
</div>
</div>
);
}
</div>
);
}
ResetPasswordView.propTypes = {

View File

@ -4,27 +4,17 @@ import { bindActionCreators } from 'redux';
import axios from 'axios';
import { Link, browserHistory } from 'react-router';
import { Helmet } from 'react-helmet';
import InlineSVG from 'react-inlinesvg';
import { reduxForm } from 'redux-form';
import * as UserActions from '../actions';
import SignupForm from '../components/SignupForm';
import { validateSignup } from '../../../utils/reduxFormUtils';
import Nav from '../../../components/Nav';
const exitUrl = require('../../../images/exit.svg');
const logoUrl = require('../../../images/p5js-logo.svg');
const __process = (typeof global !== 'undefined' ? global : window).process;
const ROOT_URL = __process.env.API_URL;
class SignupView extends React.Component {
constructor(props) {
super(props);
this.closeSignupPage = this.closeSignupPage.bind(this);
this.gotoHomePage = this.gotoHomePage.bind(this);
}
closeSignupPage() {
browserHistory.push(this.props.previousPath);
}
gotoHomePage() {
gotoHomePage = () => {
browserHistory.push('/');
}
@ -34,19 +24,12 @@ class SignupView extends React.Component {
return null;
}
return (
<div className="user">
<div className="signup">
<Nav layout="dashboard" />
<div className="form-container">
<Helmet>
<title>p5.js Web Editor | Signup</title>
</Helmet>
<div className="form-container__header">
<button className="form-container__logo-button" onClick={this.gotoHomePage}>
<InlineSVG src={logoUrl} alt="p5js Logo" />
</button>
<button className="form-container__exit-button" onClick={this.closeSignupPage}>
<InlineSVG src={exitUrl} alt="Close Signup Page" />
</button>
</div>
<div className="form-container__content">
<h2 className="form-container__title">Sign Up</h2>
<SignupForm {...this.props} />
@ -97,7 +80,7 @@ function asyncValidate(formProps, dispatch, props) {
const queryParams = {};
queryParams[fieldToValidate] = formProps[fieldToValidate];
queryParams.check_type = fieldToValidate;
return axios.get('/api/signup/duplicate_check', { params: queryParams })
return axios.get(`${ROOT_URL}/signup/duplicate_check`, { params: queryParams })
.then((response) => {
if (response.data.exists) {
errors[fieldToValidate] = response.data.message;
@ -120,9 +103,9 @@ function onSubmitFail(errors) {
SignupView.propTypes = {
previousPath: PropTypes.string.isRequired,
user: {
user: PropTypes.shape({
authenticated: PropTypes.bool
}
})
};
SignupView.defaultProps = {

View File

@ -13,6 +13,7 @@ import DashboardView from './modules/User/pages/DashboardView';
import createRedirectWithUsername from './components/createRedirectWithUsername';
import { getUser } from './modules/User/actions';
import { stopSketch } from './modules/IDE/actions/ide';
import { userIsAuthenticated, userIsNotAuthenticated, userIsAuthorized } from './utils/auth';
const checkAuth = (store) => {
store.dispatch(getUser());
@ -25,9 +26,9 @@ const onRouteChange = (store) => {
const routes = store => (
<Route path="/" component={App} onChange={() => { onRouteChange(store); }}>
<IndexRoute component={IDEView} onEnter={checkAuth(store)} />
<Route path="/login" component={LoginView} />
<Route path="/signup" component={SignupView} />
<Route path="/reset-password" component={ResetPasswordView} />
<Route path="/login" component={userIsNotAuthenticated(LoginView)} />
<Route path="/signup" component={userIsNotAuthenticated(SignupView)} />
<Route path="/reset-password" component={userIsNotAuthenticated(ResetPasswordView)} />
<Route path="/verify" component={EmailVerificationView} />
<Route
path="/reset-password/:reset_password_token"
@ -37,12 +38,11 @@ const routes = store => (
<Route path="/:username/full/:project_id" component={FullView} />
<Route path="/full/:project_id" component={FullView} />
<Route path="/sketches" component={createRedirectWithUsername('/:username/sketches')} />
<Route path="/:username/assets" component={DashboardView} />
<Route path="/:username/assets" component={userIsAuthenticated(userIsAuthorized(DashboardView))} />
<Route path="/assets" component={createRedirectWithUsername('/:username/assets')} />
<Route path="/account" component={AccountView} />
<Route path="/account" component={userIsAuthenticated(AccountView)} />
<Route path="/:username/sketches/:project_id" component={IDEView} />
<Route path="/:username/sketches" component={DashboardView} />
<Route path="/:username/assets" component={DashboardView} />
<Route path="/about" component={IDEView} />
<Route path="/feedback" component={IDEView} />
</Route>

View File

@ -62,6 +62,10 @@ button {
border: none;
}
h1 {
font-size: #{21 / $base-font-size}em;
}
h2 {
font-size: #{21 / $base-font-size}em;
}

View File

@ -1,3 +1,17 @@
.account-settings__container {
@include themify() {
color: getThemifyVariable('primary-text-color');
background-color: getThemifyVariable('background-color');
}
}
.account-settings {
max-width: #{700 / $base-font-size}rem;
align-self: center;
padding: 0 #{10 / $base-font-size}rem;
margin: 0 auto;
}
.account__tabs {
padding-top: #{20 / $base-font-size}rem;
}

View File

@ -21,6 +21,11 @@
.api-key-form__create-button {
display: flex;
justify-content: center;
align-items: center;
}
.api-key-form__create-icon {
display: flex;
}
.api-key-form__create-button .isvg {

View File

@ -8,3 +8,15 @@
background-color: getThemifyVariable('background-color');
}
}
.login,
.signup,
.reset-password-container,
.new-password-container,
.email-verification {
height: 100%;
@include themify() {
color: getThemifyVariable('primary-text-color');
background-color: getThemifyVariable('background-color');
}
}

29
client/utils/auth.js Normal file
View File

@ -0,0 +1,29 @@
import { connectedRouterRedirect } from 'redux-auth-wrapper/history3/redirect';
import locationHelperBuilder from 'redux-auth-wrapper/history3/locationHelper';
const locationHelper = locationHelperBuilder({});
export const userIsAuthenticated = connectedRouterRedirect({
// The url to redirect user to if they fail
redirectPath: '/login',
// Determine if the user is authenticated or not
authenticatedSelector: state => state.user.authenticated === true,
// A nice display name for this check
wrapperDisplayName: 'UserIsAuthenticated'
});
export const userIsNotAuthenticated = connectedRouterRedirect({
redirectPath: (state, ownProps) => locationHelper.getRedirectQueryParam(ownProps) || '/',
allowRedirectBack: false,
authenticatedSelector: state => state.user.authenticated === false,
wrapperDisplayName: 'UserIsNotAuthenticated'
});
export const userIsAuthorized = connectedRouterRedirect({
redirectPath: '/',
allowRedirectBack: false,
authenticatedSelector: (state, ownProps) => {
const { username } = ownProps.params;
return state.user.username === username;
},
});

37
package-lock.json generated
View File

@ -11348,6 +11348,11 @@
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk="
},
"lodash.isempty": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz",
"integrity": "sha1-b4bL7di+TsmHvpqvM8loTbGzHn4="
},
"lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
@ -17239,6 +17244,38 @@
"symbol-observable": "^1.0.3"
}
},
"redux-auth-wrapper": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/redux-auth-wrapper/-/redux-auth-wrapper-2.1.0.tgz",
"integrity": "sha512-UtU64cJk2pWcMMfgWIVoyBVG0p8ZiGJ++vqrvQ5r5ghZZOLRq+M5aTS0RRNneiB+aCCZBzI+txFSaKYVRrv8qQ==",
"requires": {
"hoist-non-react-statics": "^3.3.0",
"invariant": "^2.2.4",
"lodash.isempty": "^4.4.0",
"prop-types": "^15.5.0",
"query-string": "^5.1.1"
},
"dependencies": {
"hoist-non-react-statics": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz",
"integrity": "sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==",
"requires": {
"react-is": "^16.7.0"
}
},
"query-string": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz",
"integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==",
"requires": {
"decode-uri-component": "^0.2.0",
"object-assign": "^4.1.0",
"strict-uri-encode": "^1.0.0"
}
}
}
},
"redux-devtools": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/redux-devtools/-/redux-devtools-3.5.0.tgz",

View File

@ -158,6 +158,7 @@
"react-split-pane": "^0.1.85",
"react-tabs": "^2.3.0",
"redux": "^3.7.2",
"redux-auth-wrapper": "^2.1.0",
"redux-devtools": "^3.4.2",
"redux-devtools-dock-monitor": "^1.1.3",
"redux-devtools-log-monitor": "^1.4.0",

View File

@ -37,9 +37,8 @@ export function findUserByUsername(username, cb) {
);
}
const EMAIL_VERIFY_TOKEN_EXPIRY_TIME = Date.now() + (3600000 * 24); // 24 hours
export function createUser(req, res, next) {
const EMAIL_VERIFY_TOKEN_EXPIRY_TIME = Date.now() + (3600000 * 24); // 24 hours
random((tokenError, token) => {
const user = new User({
username: req.body.username,
@ -224,6 +223,7 @@ export function emailVerificationInitiate(req, res) {
if (mailErr != null) {
res.status(500).send({ error: 'Error sending mail' });
} else {
const EMAIL_VERIFY_TOKEN_EXPIRY_TIME = Date.now() + (3600000 * 24); // 24 hours
user.verified = User.EmailConfirmation.Resent;
user.verifiedToken = token;
user.verifiedTokenExpires = EMAIL_VERIFY_TOKEN_EXPIRY_TIME; // 24 hours
@ -240,7 +240,7 @@ export function emailVerificationInitiate(req, res) {
export function verifyEmail(req, res) {
const token = req.query.t;
User.findOne({ verifiedToken: token, verifiedTokenExpires: { $gt: Date.now() } }, (err, user) => {
User.findOne({ verifiedToken: token, verifiedTokenExpires: { $gt: new Date() } }, (err, user) => {
if (!user) {
res.status(401).json({ success: false, message: 'Token is invalid or has expired.' });
return;
@ -316,6 +316,7 @@ export function updateSettings(req, res) {
saveUser(res, user);
});
} else if (user.email !== req.body.email) {
const EMAIL_VERIFY_TOKEN_EXPIRY_TIME = Date.now() + (3600000 * 24); // 24 hours
user.verified = User.EmailConfirmation.Sent;
user.email = req.body.email;