unify navigation for authentication pages, add authorization to front end, fixes #650
This commit is contained in:
parent
7f2529a973
commit
5900e62904
17 changed files with 220 additions and 218 deletions
|
@ -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) {
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
{!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 = {};
|
||||
|
|
|
@ -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'
|
||||
]),
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -2,55 +2,29 @@ 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() {
|
||||
function NewPasswordView(props) {
|
||||
const newPasswordClass = classNames({
|
||||
'new-password': true,
|
||||
'new-password--invalid': this.props.user.resetPasswordInvalid,
|
||||
'new-password--invalid': props.user.resetPasswordInvalid,
|
||||
'form-container': true,
|
||||
'user': true
|
||||
});
|
||||
return (
|
||||
<div className="user">
|
||||
<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__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} />
|
||||
<NewPasswordForm {...props} />
|
||||
<p className="new-password__invalid">
|
||||
The password reset token is invalid or has expired.
|
||||
</p>
|
||||
|
@ -58,7 +32,6 @@ class NewPasswordView extends React.Component {
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
NewPasswordView.propTypes = {
|
||||
|
|
|
@ -1,57 +1,33 @@
|
|||
|
||||
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() {
|
||||
function ResetPasswordView(props) {
|
||||
const resetPasswordClass = classNames({
|
||||
'reset-password': true,
|
||||
'reset-password--submitted': this.props.user.resetPasswordInitiate,
|
||||
'reset-password--submitted': props.user.resetPasswordInitiate,
|
||||
'form-container': true,
|
||||
'user': true
|
||||
});
|
||||
return (
|
||||
<div className="user">
|
||||
<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__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} />
|
||||
<ResetPasswordForm {...props} />
|
||||
<p className="reset-password__submitted">
|
||||
Your password reset email should arrive shortly. If you don't see it, check
|
||||
in your spam folder as sometimes it can end up there.
|
||||
|
@ -65,7 +41,6 @@ class ResetPasswordView extends React.Component {
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ResetPasswordView.propTypes = {
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -62,6 +62,10 @@ button {
|
|||
border: none;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: #{21 / $base-font-size}em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: #{21 / $base-font-size}em;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
29
client/utils/auth.js
Normal 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
37
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue