Force HTTPS redirection for log in and sign up (#319)

* Higher-order component to force some routes to HTTPS

* Force all user-management routes to HTTPS

* Redirect to sourceProtocol as route unmounts.

By default, no redirection occurs if sourceProtocol is not explicitly
defined.

* Sets serveSecure flag on new projects and usea after forcing protocol

The flag is set to `false` on all projects and as the UI has no way to
change this, it always redirects to HTTP after a signup/login action.

* Move HoC to be with other top-level components

* Server should respond to account page request

* Serves AccountView over HTTPS

* Turns HTTPS redirection off in development by default

Will log to the browser console any redirection that would
have happened. Added a line in the README about how to
enable this for testing in development.
This commit is contained in:
Andrew Nicolaou 2017-03-30 18:36:26 +02:00 committed by Cassie Tarakajian
parent 608ebbf917
commit dc801ccf7f
6 changed files with 82 additions and 9 deletions

View file

@ -32,6 +32,8 @@ This project is currently in development! It will be announced when there is a (
###Testing SSL on your local machine ###Testing SSL on your local machine
Please refer to [this gist](https://gist.github.com/andrewn/953ffd5cb17ac2634dc969fc7bdaff3f). This allows you to access the editor using both HTTP and HTTPS. Don't worry about this unless you need to make changes or test HTTPS behavior. Please refer to [this gist](https://gist.github.com/andrewn/953ffd5cb17ac2634dc969fc7bdaff3f). This allows you to access the editor using both HTTP and HTTPS. Don't worry about this unless you need to make changes or test HTTPS behavior.
The automatic redirection to HTTPS is turned off by default in development. If you need to test this behavior, put `FORCE_TO_HTTPS=true` in your `.env` file.
##Production Installation ##Production Installation
1. Clone this repostory and `cd` into it 1. Clone this repostory and `cd` into it
2. `$ git submodule init` 2. `$ git submodule init`

View file

@ -0,0 +1,44 @@
import React, { PropTypes } from 'react';
/**
* A Higher Order Component that forces the protocol to change on mount
*
* targetProtocol: the protocol to redirect to on mount
* sourceProtocol: the protocol to redirect back to on unmount
* disable: if true, the redirection will not happen but what should
* have happened will be logged to the console
*/
const forceProtocol = ({ targetProtocol = 'https:', sourceProtocol, disable = false }) => WrappedComponent => (
class ForceProtocol extends React.Component {
static propTypes = {}
componentDidMount() {
this.redirectToProtocol(targetProtocol);
}
componentWillUnmount() {
if (sourceProtocol != null) {
this.redirectToProtocol(sourceProtocol);
}
}
redirectToProtocol(protocol) {
const currentProtocol = window.location.protocol;
if (protocol !== currentProtocol) {
if (disable === true) {
console.info(`forceProtocol: would have redirected from "${currentProtocol}" to "${protocol}"`);
} else {
window.location = window.location.href.replace(currentProtocol, protocol);
}
}
}
render() {
return <WrappedComponent {...this.props} />;
}
}
);
export default forceProtocol;

View file

@ -5,7 +5,8 @@ const initialState = () => {
const generatedString = generate({ words: 2 }).spaced; const generatedString = generate({ words: 2 }).spaced;
const generatedName = generatedString.charAt(0).toUpperCase() + generatedString.slice(1); const generatedName = generatedString.charAt(0).toUpperCase() + generatedString.slice(1);
return { return {
name: generatedName name: generatedName,
serveSecure: false,
}; };
}; };

View file

@ -1,5 +1,6 @@
import { Route, IndexRoute } from 'react-router'; import { Route, IndexRoute } from 'react-router';
import React from 'react'; import React from 'react';
import forceProtocol from './components/forceProtocol';
import App from './modules/App/App'; import App from './modules/App/App';
import IDEView from './modules/IDE/pages/IDEView'; import IDEView from './modules/IDE/pages/IDEView';
import FullView from './modules/IDE/pages/FullView'; import FullView from './modules/IDE/pages/FullView';
@ -15,22 +16,38 @@ const checkAuth = (store) => {
store.dispatch(getUser()); store.dispatch(getUser());
}; };
const routes = store => const routes = (store) => {
( const sourceProtocol = store.getState().project.serveSecure === true ?
'https:' :
'http:';
// If the flag is false, we stay on HTTP
const forceToHttps = forceProtocol({
targetProtocol: 'https:',
sourceProtocol,
// prints debugging but does not reload page
disable: process.env.FORCE_TO_HTTPS === false,
});
return (
<Route path="/" component={App}> <Route path="/" component={App}>
<IndexRoute component={IDEView} onEnter={checkAuth(store)} /> <IndexRoute component={IDEView} onEnter={checkAuth(store)} />
<Route path="/login" component={LoginView} /> <Route path="/login" component={forceToHttps(LoginView)} />
<Route path="/signup" component={SignupView} /> <Route path="/signup" component={forceToHttps(SignupView)} />
<Route path="/reset-password" component={ResetPasswordView} /> <Route path="/reset-password" component={forceToHttps(ResetPasswordView)} />
<Route path="/reset-password/:reset_password_token" component={NewPasswordView} /> <Route
path="/reset-password/:reset_password_token"
component={forceToHttps(NewPasswordView)}
/>
<Route path="/projects/:project_id" component={IDEView} /> <Route path="/projects/:project_id" component={IDEView} />
<Route path="/full/:project_id" component={FullView} /> <Route path="/full/:project_id" component={FullView} />
<Route path="/sketches" component={IDEView} /> <Route path="/sketches" component={IDEView} />
<Route path="/:username/sketches/:project_id" component={IDEView} /> <Route path="/:username/sketches/:project_id" component={IDEView} />
<Route path="/:username/sketches" component={IDEView} /> <Route path="/:username/sketches" component={IDEView} />
<Route path="/:username/account" component={AccountView} /> <Route path="/:username/account" component={forceToHttps(AccountView)} />
<Route path="/about" component={IDEView} /> <Route path="/about" component={IDEView} />
</Route> </Route>
); );
};
export default routes; export default routes;

View file

@ -54,4 +54,10 @@ router.route('/:username/sketches').get((req, res) => {
)); ));
}); });
router.route('/:username/account').get((req, res) => {
userExists(req.params.username, exists => (
exists ? res.send(renderIndex()) : get404Sketch(html => res.send(html))
));
});
export default router; export default router;

View file

@ -36,6 +36,9 @@ module.exports = {
'process.env': { 'process.env': {
API_URL: '"' + process.env.API_URL + '"', API_URL: '"' + process.env.API_URL + '"',
CLIENT: JSON.stringify(true), CLIENT: JSON.stringify(true),
FORCE_TO_HTTPS: process.env.FORCE_TO_HTTPS === 'true' ?
JSON.stringify(true) :
JSON.stringify(false),
'NODE_ENV': JSON.stringify('development'), 'NODE_ENV': JSON.stringify('development'),
'S3_BUCKET': '"' + process.env.S3_BUCKET + '"' 'S3_BUCKET': '"' + process.env.S3_BUCKET + '"'
} }