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:
Cassie Tarakajian 2017-01-13 17:17:31 -05:00
parent b018aa645c
commit 65592cbf9e
9 changed files with 130 additions and 38 deletions

View file

@ -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';

View file

@ -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
};
}

View file

@ -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) => {
type: ActionTypes.PROJECT_SAVE_FAIL, if (response.status === 403) {
error: response.data dispatch(showAuthenticationError());
})); } else {
dispatch({
type: ActionTypes.PROJECT_SAVE_FAIL,
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 => {

View 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&nbsp;
<Link to="/login" onClick={props.closeModal}>log in</Link>.
</p>
</div>
</section>
);
}
AuthenticationError.propTypes = {
closeModal: PropTypes.func.isRequired
};
export default AuthenticationError;

View file

@ -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) {

View file

@ -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;
} }

View 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;
}

View file

@ -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';

View file

@ -23,33 +23,38 @@ export function createProject(req, res) {
} }
export function updateProject(req, res) { export function updateProject(req, res) {
Project.findByIdAndUpdate(req.params.project_id, Project.findById(req.params.project_id, (err, project) => {
{ if (!req.user || !project.user.equals(req.user._id)) {
$set: req.body return res.status(403).send({ success: false, message: 'Session does not match owner of project.'});
}) }
.populate('user', 'username') Project.findByIdAndUpdate(req.params.project_id,
.exec((err, updatedProject) => { {
if (err) { $set: req.body
console.log(err); })
return res.json({ success: false }); .populate('user', 'username')
} .exec((err, updatedProject) => {
if (updatedProject.files.length !== req.body.files.length) { if (err) {
const oldFileIds = updatedProject.files.map(file => file.id); console.log(err);
const newFileIds = req.body.files.map(file => file.id); return res.json({ success: false });
const staleIds = oldFileIds.filter(id => newFileIds.indexOf(id) === -1); }
staleIds.forEach(staleId => { if (updatedProject.files.length !== req.body.files.length) {
updatedProject.files.id(staleId).remove(); const oldFileIds = updatedProject.files.map(file => file.id);
}); const newFileIds = req.body.files.map(file => file.id);
updatedProject.save((innerErr) => { const staleIds = oldFileIds.filter(id => newFileIds.indexOf(id) === -1);
if (innerErr) { staleIds.forEach(staleId => {
console.log(innerErr); updatedProject.files.id(staleId).remove();
return res.json({ success: false }); });
} updatedProject.save((innerErr) => {
return res.json(updatedProject); if (innerErr) {
}); console.log(innerErr);
} return res.json({ success: false });
return res.json(updatedProject); }
}); return res.json(updatedProject);
});
}
return res.json(updatedProject);
});
});
} }
export function getProject(req, res) { export function getProject(req, res) {
@ -64,11 +69,16 @@ export function getProject(req, res) {
} }
export function deleteProject(req, res) { export function deleteProject(req, res) {
Project.remove({ _id: req.params.project_id }, (err) => { Project.findById(req.params.project_id, (err, project) => {
if (err) { if (!req.user || !project.user.equals(req.user._id)) {
return res.status(404).send({ message: 'Project with that id does not exist' }); return res.status(403).json({ success: false, message: 'Session does not match owner of project.'});
} }
return res.json({ success: true }); Project.remove({ _id: req.params.project_id }, (err) => {
if (err) {
return res.status(404).send({ message: 'Project with that id does not exist' });
}
return res.json({ success: true });
});
}); });
} }