add authentcation error component, return 403 error from server when trying to save a project where the user doesn't match the owner
This commit is contained in:
parent
b018aa645c
commit
65592cbf9e
9 changed files with 130 additions and 38 deletions
|
@ -108,3 +108,5 @@ export const RESET_PROJECT_SAVED_TIME = 'RESET_PROJECT_SAVED_TIME';
|
||||||
export const SET_PREVIOUS_PATH = 'SET_PREVIOUS_PATH';
|
export const SET_PREVIOUS_PATH = 'SET_PREVIOUS_PATH';
|
||||||
export const OPEN_FORCE_AUTHENTICATION = 'OPEN_FORCE_AUTHENTICATION';
|
export const OPEN_FORCE_AUTHENTICATION = 'OPEN_FORCE_AUTHENTICATION';
|
||||||
export const CLOSE_FORCE_AUTHENTICATION = 'CLOSE_FORCE_AUTHENTICATION';
|
export const CLOSE_FORCE_AUTHENTICATION = 'CLOSE_FORCE_AUTHENTICATION';
|
||||||
|
export const SHOW_AUTHENTICATION_ERROR = 'SHOW_AUTHENTICATION_ERROR';
|
||||||
|
export const HIDE_AUTHENTICATION_ERROR = 'HIDE_AUTHENTICATION_ERROR';
|
||||||
|
|
|
@ -232,3 +232,15 @@ export function closeForceAuthentication() {
|
||||||
type: ActionTypes.CLOSE_FORCE_AUTHENTICATION
|
type: ActionTypes.CLOSE_FORCE_AUTHENTICATION
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function showAuthenticationError() {
|
||||||
|
return {
|
||||||
|
type: ActionTypes.SHOW_AUTHENTICATION_ERROR
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hideAuthenticationError() {
|
||||||
|
return {
|
||||||
|
type: ActionTypes.HIDE_AUTHENTICATION_ERROR
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,12 @@ import * as ActionTypes from '../../../constants';
|
||||||
import { browserHistory } from 'react-router';
|
import { browserHistory } from 'react-router';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { showToast, setToastText } from './toast';
|
import { showToast, setToastText } from './toast';
|
||||||
import { setUnsavedChanges, justOpenedProject, resetJustOpenedProject, setProjectSavedTime, resetProjectSavedTime } from './ide';
|
import { setUnsavedChanges,
|
||||||
|
justOpenedProject,
|
||||||
|
resetJustOpenedProject,
|
||||||
|
setProjectSavedTime,
|
||||||
|
resetProjectSavedTime,
|
||||||
|
showAuthenticationError } from './ide';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
const ROOT_URL = location.href.indexOf('localhost') > 0 ? 'http://localhost:8000/api' : '/api';
|
const ROOT_URL = location.href.indexOf('localhost') > 0 ? 'http://localhost:8000/api' : '/api';
|
||||||
|
@ -67,10 +72,16 @@ export function saveProject(autosave = false) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((response) => dispatch({
|
.catch((response) => {
|
||||||
|
if (response.status === 403) {
|
||||||
|
dispatch(showAuthenticationError());
|
||||||
|
} else {
|
||||||
|
dispatch({
|
||||||
type: ActionTypes.PROJECT_SAVE_FAIL,
|
type: ActionTypes.PROJECT_SAVE_FAIL,
|
||||||
error: response.data
|
error: response.data
|
||||||
}));
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
axios.post(`${ROOT_URL}/projects`, formParams, { withCredentials: true })
|
axios.post(`${ROOT_URL}/projects`, formParams, { withCredentials: true })
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
|
24
client/modules/IDE/components/AuthenticationError.jsx
Normal file
24
client/modules/IDE/components/AuthenticationError.jsx
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import React, { PropTypes } from 'react';
|
||||||
|
import { Link } from 'react-router';
|
||||||
|
|
||||||
|
function AuthenticationError(props) {
|
||||||
|
return (
|
||||||
|
<section className="authentication-error" tabIndex="0">
|
||||||
|
<header className="authentication-error__header">
|
||||||
|
<h2 className="authentication-error__title">Error</h2>
|
||||||
|
</header>
|
||||||
|
<div className="authentication-error__copy">
|
||||||
|
<p>
|
||||||
|
It looks like you've been logged out. Please
|
||||||
|
<Link to="/login" onClick={props.closeModal}>log in</Link>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthenticationError.propTypes = {
|
||||||
|
closeModal: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthenticationError;
|
|
@ -10,6 +10,7 @@ import NewFolderModal from '../components/NewFolderModal';
|
||||||
import ShareModal from '../components/ShareModal';
|
import ShareModal from '../components/ShareModal';
|
||||||
import KeyboardShortcutModal from '../components/KeyboardShortcutModal';
|
import KeyboardShortcutModal from '../components/KeyboardShortcutModal';
|
||||||
import ForceAuthentication from '../components/ForceAuthentication';
|
import ForceAuthentication from '../components/ForceAuthentication';
|
||||||
|
import AuthenticationError from '../components/AuthenticationError';
|
||||||
import Nav from '../../../components/Nav';
|
import Nav from '../../../components/Nav';
|
||||||
import Console from '../components/Console';
|
import Console from '../components/Console';
|
||||||
import Toast from '../components/Toast';
|
import Toast from '../components/Toast';
|
||||||
|
@ -446,6 +447,17 @@ class IDEView extends React.Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})()}
|
})()}
|
||||||
|
{(() => { // eslint-disable-line
|
||||||
|
if (this.props.ide.authenticationError) {
|
||||||
|
return (
|
||||||
|
<Overlay>
|
||||||
|
<AuthenticationError
|
||||||
|
closeModal={this.props.hideAuthenticationError}
|
||||||
|
/>
|
||||||
|
</Overlay>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
);
|
);
|
||||||
|
@ -488,7 +500,8 @@ IDEView.propTypes = {
|
||||||
infiniteLoopMessage: PropTypes.string.isRequired,
|
infiniteLoopMessage: PropTypes.string.isRequired,
|
||||||
projectSavedTime: PropTypes.string.isRequired,
|
projectSavedTime: PropTypes.string.isRequired,
|
||||||
previousPath: PropTypes.string.isRequired,
|
previousPath: PropTypes.string.isRequired,
|
||||||
forceAuthenticationVisible: PropTypes.bool.isRequired
|
forceAuthenticationVisible: PropTypes.bool.isRequired,
|
||||||
|
authenticationError: PropTypes.bool.isRequired
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
startSketch: PropTypes.func.isRequired,
|
startSketch: PropTypes.func.isRequired,
|
||||||
stopSketch: PropTypes.func.isRequired,
|
stopSketch: PropTypes.func.isRequired,
|
||||||
|
@ -591,7 +604,8 @@ IDEView.propTypes = {
|
||||||
closeForceAuthentication: PropTypes.func.isRequired,
|
closeForceAuthentication: PropTypes.func.isRequired,
|
||||||
openForceAuthentication: PropTypes.func.isRequired,
|
openForceAuthentication: PropTypes.func.isRequired,
|
||||||
console: PropTypes.array.isRequired,
|
console: PropTypes.array.isRequired,
|
||||||
clearConsole: PropTypes.func.isRequired
|
clearConsole: PropTypes.func.isRequired,
|
||||||
|
hideAuthenticationError: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
|
|
|
@ -20,6 +20,7 @@ const initialState = {
|
||||||
projectSavedTime: '',
|
projectSavedTime: '',
|
||||||
previousPath: '/',
|
previousPath: '/',
|
||||||
forceAuthenticationVisible: false,
|
forceAuthenticationVisible: false,
|
||||||
|
authenticationError: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const ide = (state = initialState, action) => {
|
const ide = (state = initialState, action) => {
|
||||||
|
@ -96,6 +97,10 @@ const ide = (state = initialState, action) => {
|
||||||
return Object.assign({}, state, { forceAuthenticationVisible: true });
|
return Object.assign({}, state, { forceAuthenticationVisible: true });
|
||||||
case ActionTypes.CLOSE_FORCE_AUTHENTICATION:
|
case ActionTypes.CLOSE_FORCE_AUTHENTICATION:
|
||||||
return Object.assign({}, state, { forceAuthenticationVisible: false });
|
return Object.assign({}, state, { forceAuthenticationVisible: false });
|
||||||
|
case ActionTypes.SHOW_AUTHENTICATION_ERROR:
|
||||||
|
return Object.assign({}, state, { authenticationError: true });
|
||||||
|
case ActionTypes.HIDE_AUTHENTICATION_ERROR:
|
||||||
|
return Object.assign({}, state, { authenticationError: false });
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
13
client/styles/components/_authentication-error.scss
Normal file
13
client/styles/components/_authentication-error.scss
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
.authentication-error {
|
||||||
|
@extend %modal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.authentication-error__header {
|
||||||
|
padding: #{20 / $base-font-size}rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.authentication-error__copy {
|
||||||
|
padding: #{20 / $base-font-size}rem;
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: #{60 / $base-font-size}rem;
|
||||||
|
}
|
|
@ -33,6 +33,7 @@
|
||||||
@import 'components/force-authentication';
|
@import 'components/force-authentication';
|
||||||
@import 'components/form-container';
|
@import 'components/form-container';
|
||||||
@import 'components/uploader';
|
@import 'components/uploader';
|
||||||
|
@import 'components/authentication-error';
|
||||||
|
|
||||||
@import 'layout/ide';
|
@import 'layout/ide';
|
||||||
@import 'layout/fullscreen';
|
@import 'layout/fullscreen';
|
||||||
|
|
|
@ -23,6 +23,10 @@ export function createProject(req, res) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateProject(req, res) {
|
export function updateProject(req, res) {
|
||||||
|
Project.findById(req.params.project_id, (err, project) => {
|
||||||
|
if (!req.user || !project.user.equals(req.user._id)) {
|
||||||
|
return res.status(403).send({ success: false, message: 'Session does not match owner of project.'});
|
||||||
|
}
|
||||||
Project.findByIdAndUpdate(req.params.project_id,
|
Project.findByIdAndUpdate(req.params.project_id,
|
||||||
{
|
{
|
||||||
$set: req.body
|
$set: req.body
|
||||||
|
@ -50,6 +54,7 @@ export function updateProject(req, res) {
|
||||||
}
|
}
|
||||||
return res.json(updatedProject);
|
return res.json(updatedProject);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getProject(req, res) {
|
export function getProject(req, res) {
|
||||||
|
@ -64,12 +69,17 @@ export function getProject(req, res) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteProject(req, res) {
|
export function deleteProject(req, res) {
|
||||||
|
Project.findById(req.params.project_id, (err, project) => {
|
||||||
|
if (!req.user || !project.user.equals(req.user._id)) {
|
||||||
|
return res.status(403).json({ success: false, message: 'Session does not match owner of project.'});
|
||||||
|
}
|
||||||
Project.remove({ _id: req.params.project_id }, (err) => {
|
Project.remove({ _id: req.params.project_id }, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return res.status(404).send({ message: 'Project with that id does not exist' });
|
return res.status(404).send({ message: 'Project with that id does not exist' });
|
||||||
}
|
}
|
||||||
return res.json({ success: true });
|
return res.json({ success: true });
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getProjects(req, res) {
|
export function getProjects(req, res) {
|
||||||
|
|
Loading…
Reference in a new issue