Use HOCs for access control

This commit is contained in:
2018-10-08 22:30:11 +03:00
parent 7fe6e954d6
commit b60b66644d
10 changed files with 171 additions and 152 deletions

View File

@@ -4393,7 +4393,8 @@
},
"ansi-regex": {
"version": "2.1.1",
"bundled": true
"bundled": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
@@ -4758,7 +4759,8 @@
},
"safe-buffer": {
"version": "5.1.1",
"bundled": true
"bundled": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -4806,6 +4808,7 @@
"strip-ansi": {
"version": "3.0.1",
"bundled": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -4844,11 +4847,13 @@
},
"wrappy": {
"version": "1.0.2",
"bundled": true
"bundled": true,
"optional": true
},
"yallist": {
"version": "3.0.2",
"bundled": true
"bundled": true,
"optional": true
}
}
},

View File

@@ -4,12 +4,18 @@ import { BrowserRouter as Router, Route } from 'react-router-dom';
import CssBaseline from '@material-ui/core/CssBaseline';
import Loadable from 'react-loadable';
import Protected from './Protected';
import OnlyUnauth from './OnlyUnauth';
import './Container.css';
import './App.css';
function Loading(props) {
if (props.error) {
return <div>Error! <button onClick={ props.retry }>Retry</button></div>;
return (
<div>
Error! <button onClick={props.retry}>Retry</button>
</div>
);
} else if (props.pastDelay) {
return <div>Loading...</div>;
} else {
@@ -17,29 +23,37 @@ function Loading(props) {
}
}
const LoadableTodosView = Loadable({
loader: () => import('./todolist/TodosView'),
loading: () => Loading,
delay: 1000,
});
const LoadableTodosView = Protected(
Loadable({
loader: () => import('./todolist/TodosView'),
loading: () => Loading,
delay: 1000,
}),
);
const LoadableLoginForm = Loadable({
loader: () => import('./user/LoginForm'),
loading: () => Loading,
delay: 1000,
});
const LoadableLoginForm = OnlyUnauth(
Loadable({
loader: () => import('./user/LoginForm'),
loading: () => Loading,
delay: 1000,
}),
);
const LoadableSignupForm = Loadable({
loader: () => import('./user/SignupForm'),
loading: () => Loading,
delay: 1000,
});
const LoadableSignupForm = OnlyUnauth(
Loadable({
loader: () => import('./user/SignupForm'),
loading: () => Loading,
delay: 1000,
}),
);
const LoadableEditView = Loadable({
loader: () => import('./user/EditForm'),
loading: () => Loading,
delay: 1000,
});
const LoadableEditView = Protected(
Loadable({
loader: () => import('./user/EditForm'),
loading: () => Loading,
delay: 1000,
}),
);
export default class App extends React.PureComponent {
componentDidMount() {

View File

@@ -0,0 +1,25 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import { Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
export default function OnlyUnauth(WrappedComponent) {
function Component({ loggedIn }) {
return loggedIn ? <Redirect to="/" /> : <WrappedComponent />;
}
Component.propTypes = {
loggedIn: PropTypes.bool.isRequired,
};
function mapStateToProps(state) {
return {
loggedIn: state.user.user !== undefined && state.user.user !== null,
};
}
return connect(
mapStateToProps,
null,
)(Component);
}

View File

@@ -0,0 +1,25 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import { Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
export default function Protected(WrappedComponent) {
function Component({ loggedIn }) {
return loggedIn ? <WrappedComponent /> : <Redirect to="/login" />;
}
Component.propTypes = {
loggedIn: PropTypes.bool.isRequired,
};
function mapStateToProps(state) {
return {
loggedIn: state.user.user !== undefined && state.user.user !== null,
};
}
return connect(
mapStateToProps,
null,
)(Component);
}

View File

@@ -11,23 +11,6 @@ import Header from '../Header';
import Filters from '../filters/Filters';
class Todos extends React.PureComponent {
componentDidMount() {
this.checkLogin();
}
componentDidUpdate() {
this.checkLogin();
}
checkLogin() {
const { user, history } = this.props;
//If user isn't logged in
//and isn't in progress of logging in
//show login page
if (!user.user && !user.dirty) {
history.replace('/login');
}
}
render() {
const { list } = this.props;
return (

View File

@@ -28,9 +28,6 @@ function EditForm({
history,
reset,
}) {
if (!user.user) {
history.push('/');
}
if (user.user && user.editSuccess) {
reset();
history.push('/');

View File

@@ -1,7 +1,6 @@
import React from 'react';
import { Field, reduxForm } from 'redux-form';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import PropTypes from 'prop-types';
import { ButtonBase, Button } from '@material-ui/core';
@@ -22,13 +21,6 @@ class LoginForm extends React.PureComponent {
}
}
componentDidUpdate() {
const { user, history } = this.props;
if (user.user) {
history.push('/');
}
}
render() {
const { resetUser, history, handleSubmit, user, onLogin } = this.props;
return (
@@ -123,10 +115,8 @@ export default reduxForm({
password: '',
},
})(
withRouter(
connect(
mapStateToProps,
mapDispatchToProps,
)(LoginForm),
),
connect(
mapStateToProps,
mapDispatchToProps,
)(LoginForm),
);

View File

@@ -1,7 +1,6 @@
import React from 'react';
import { Field, reduxForm } from 'redux-form';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import PropTypes from 'prop-types';
import { ButtonBase, Button } from '@material-ui/core';
@@ -21,9 +20,6 @@ function validate(values) {
}
function SignupForm({ handleSubmit, onSignup, user, history, resetUser }) {
if (user.user) {
history.push('/');
}
return (
<React.Fragment>
<div id="user-header">
@@ -112,10 +108,8 @@ export default reduxForm({
},
validate,
})(
withRouter(
connect(
mapStateToProps,
mapDispatchToProps,
)(SignupForm),
),
connect(
mapStateToProps,
mapDispatchToProps,
)(SignupForm),
);

140
package-lock.json generated
View File

@@ -73,9 +73,9 @@
"dev": true
},
"@babel/runtime": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz",
"integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==",
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.1.2.tgz",
"integrity": "sha512-Y3SCjmhSupzFB6wcv1KmmFucH6gDVnI30WjOcicV10ju0cZjak3Jcs67YLIXBrmZYw1xCrVeJPbycFwrqNyxpg==",
"dev": true,
"requires": {
"regenerator-runtime": "^0.12.0"
@@ -262,9 +262,9 @@
}
},
"ajv": {
"version": "6.5.3",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.3.tgz",
"integrity": "sha512-LqZ9wY+fx3UMiiPd741yB2pj3hhil+hQc8taf4o2QGRFpWgZ2V5C8HA165DY9sS3fJwsk7uT7ZlFEyC3Ig3lLg==",
"version": "6.5.4",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.4.tgz",
"integrity": "sha512-4Wyjt8+t6YszqaXnLDfMmG/8AlO5Zbcsy3ATHncCzjW/NoPzAId8AK6749Ybjmdt+kUY1gP60fCu46oDxPv/mg==",
"dev": true,
"requires": {
"fast-deep-equal": "^2.0.1",
@@ -1169,7 +1169,7 @@
},
"bl": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
"resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
"integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==",
"dev": true,
"requires": {
@@ -2065,13 +2065,13 @@
"dependencies": {
"file-type": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
"resolved": "http://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
"integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=",
"dev": true
},
"get-stream": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz",
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz",
"integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=",
"dev": true,
"requires": {
@@ -2403,16 +2403,16 @@
}
},
"eslint": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-5.6.0.tgz",
"integrity": "sha512-/eVYs9VVVboX286mBK7bbKnO1yamUy2UCRjiY6MryhQL2PaaXCExsCQ2aO83OeYRhU2eCU/FMFP+tVMoOrzNrA==",
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-5.6.1.tgz",
"integrity": "sha512-hgrDtGWz368b7Wqf+v1Z69O3ZebNR0+GA7PtDdbmuz4rInFVUV9uw7whjZEiWyLzCjVb5Rs5WRN1TAS6eo7AYA==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
"ajv": "^6.5.3",
"chalk": "^2.1.0",
"cross-spawn": "^6.0.5",
"debug": "^3.1.0",
"debug": "^4.0.1",
"doctrine": "^2.1.0",
"eslint-scope": "^4.0.0",
"eslint-utils": "^1.3.1",
@@ -2497,9 +2497,9 @@
}
},
"debug": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz",
"integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz",
"integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==",
"dev": true,
"requires": {
"ms": "^2.1.1"
@@ -2544,9 +2544,9 @@
"dev": true
},
"regexpp": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.0.tgz",
"integrity": "sha512-g2FAVtR8Uh8GO1Nv5wpxW7VFVwHcCEr4wyA8/MHiRkO8uHoR5ntAA8Uq3P1vvMTX/BeQiRVSpDGLd+Wn5HNOTA==",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz",
"integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==",
"dev": true
},
"semver": {
@@ -2677,9 +2677,9 @@
}
},
"eslint-plugin-jest": {
"version": "21.22.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-21.22.1.tgz",
"integrity": "sha512-OcVizyXljSps3nVfuPomfK8RKt7jvDsIsDrO7l1ZA4bDbiO9bEGWpsdU/x5F0pymVDG7ME88VlTboxbYJumLGQ==",
"version": "21.24.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-21.24.1.tgz",
"integrity": "sha512-+lJ6nvwJtDRQtTumCs/9gMuteiopArpHJbRbWqPaScCzlhTu1pEilWTUlTgDEtY7GOx7FdOMD3BO/mdxFb4yDg==",
"dev": true
},
"eslint-plugin-node": {
@@ -2705,21 +2705,12 @@
}
},
"eslint-plugin-prettier": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-2.6.2.tgz",
"integrity": "sha512-tGek5clmW5swrAx1mdPYM8oThrBE83ePh7LeseZHBWfHVGrHPhKn7Y5zgRMbU/9D5Td9K4CEmUPjGxA7iw98Og==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.0.0.tgz",
"integrity": "sha512-4g11opzhqq/8+AMmo5Vc2Gn7z9alZ4JqrbZ+D4i8KlSyxeQhZHlmIrY8U9Akf514MoEhogPa87Jgkq87aZ2Ohw==",
"dev": true,
"requires": {
"fast-diff": "^1.1.1",
"jest-docblock": "^21.0.0"
},
"dependencies": {
"jest-docblock": {
"version": "21.2.0",
"resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-21.2.0.tgz",
"integrity": "sha512-5IZ7sY9dBAYSV+YjQ0Ovb540Ku7AO9Z5o2Cg789xj167iQuZ2cG+z0f3Uct6WeYLbU6aQiM2pCs7sZ+4dotydw==",
"dev": true
}
"prettier-linter-helpers": "^1.0.0"
}
},
"eslint-plugin-react": {
@@ -3374,14 +3365,12 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -3396,20 +3385,17 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"core-util-is": {
"version": "1.0.2",
@@ -3526,8 +3512,7 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"ini": {
"version": "1.3.5",
@@ -3539,7 +3524,6 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -3554,7 +3538,6 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -3562,14 +3545,12 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"minipass": {
"version": "2.2.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
@@ -3588,7 +3569,6 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -3669,8 +3649,7 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"object-assign": {
"version": "4.1.1",
@@ -3682,7 +3661,6 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@@ -3804,7 +3782,6 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -5662,9 +5639,9 @@
}
},
"kareem": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.2.1.tgz",
"integrity": "sha512-xpDFy8OxkFM+vK6pXy6JmH92ibeEFUuDWzas5M9L7MzVmHW3jzwAHxodCPV/BYkf4A31bVDLyonrMfp9RXb/oA=="
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.0.tgz",
"integrity": "sha512-6hHxsp9e6zQU8nXsP+02HGWXwTkOEw6IROhF2ZA28cYbUk4eJ6QbtZvdqZOdD9YPKghG3apk5eOCvs+tLl3lRg=="
},
"kind-of": {
"version": "3.2.2",
@@ -6170,9 +6147,9 @@
}
},
"mongodb-memory-server": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/mongodb-memory-server/-/mongodb-memory-server-2.2.0.tgz",
"integrity": "sha512-RYDf43oDO48eYPqZaYzsuTwH9Effgq3pdq1IlrV21sO8h3ah1xMfqc6tku9MvUkz3wsKUDOPeCvL7zCE2A6RWw==",
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/mongodb-memory-server/-/mongodb-memory-server-2.4.3.tgz",
"integrity": "sha512-i2ad5ngxTYqiGcCdanGb67sdxi83kvrh4d/zAeIXLRpYNMCU38p+VOMz7Dr3AhDiIUBnkfP4eZNuRvOfq7GYTg==",
"dev": true,
"requires": {
"@babel/runtime": "^7.0.0",
@@ -6189,9 +6166,9 @@
},
"dependencies": {
"debug": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.0.1.tgz",
"integrity": "sha512-K23FHJ/Mt404FSlp6gSZCevIbTMLX0j3fmHhUEhQ3Wq0FMODW3+cUSoLdy1Gx4polAf4t/lphhmHH35BB8cLYw==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz",
"integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==",
"dev": true,
"requires": {
"ms": "^2.1.1"
@@ -6206,13 +6183,13 @@
}
},
"mongoose": {
"version": "5.2.17",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.2.17.tgz",
"integrity": "sha512-AYmf+QYMUM3POzPen/tzuN9spJASHIuV5FUb1HNJurEAtKXL671zLXC60GPIMDVDZSvyGfGjbr4TI5Hy4UkVgw==",
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.3.2.tgz",
"integrity": "sha512-07fpCMqvCiSdKXIygt3aAeNFJYjQPjDXILbwBEmF0e8gy2hSKMNuP5QG4J6L8m9BhjVxcvoLiPzaGKN7I/7lLg==",
"requires": {
"async": "2.6.1",
"bson": "~1.0.5",
"kareem": "2.2.1",
"kareem": "2.3.0",
"lodash.get": "4.4.2",
"mongodb": "3.1.6",
"mongodb-core": "3.1.5",
@@ -7181,6 +7158,15 @@
}
}
},
"prettier-linter-helpers": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
"dev": true,
"requires": {
"fast-diff": "^1.1.2"
}
},
"pretty-format": {
"version": "23.6.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz",
@@ -8968,17 +8954,17 @@
}
},
"tar-stream": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.1.tgz",
"integrity": "sha512-IFLM5wp3QrJODQFPm6/to3LJZrONdBY/otxcvDIQzu217zKye6yVR3hhi9lAjrC2Z+m/j5oDxMPb1qcd8cIvpA==",
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz",
"integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==",
"dev": true,
"requires": {
"bl": "^1.0.0",
"buffer-alloc": "^1.1.0",
"buffer-alloc": "^1.2.0",
"end-of-stream": "^1.0.0",
"fs-constants": "^1.0.0",
"readable-stream": "^2.3.0",
"to-buffer": "^1.1.0",
"to-buffer": "^1.1.1",
"xtend": "^4.0.0"
}
},
@@ -9273,9 +9259,9 @@
"integrity": "sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I="
},
"unbzip2-stream": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.2.5.tgz",
"integrity": "sha512-izD3jxT8xkzwtXRUZjtmRwKnZoeECrfZ8ra/ketwOcusbZEp4mjULMnJOCfTDZBgGQAAY1AJ/IgxcwkavcX9Og==",
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.3.0.tgz",
"integrity": "sha512-kE2WkurNnPUMcryNioS68DDbhoPB8Qxsd8btHSj+sd5Pjh2GsjmeHLzMSqV9HHziAo8FzVxVCJl9ZYhk7yY1pA==",
"dev": true,
"requires": {
"buffer": "^3.0.1",

View File

@@ -30,9 +30,9 @@
"express-jwt": "^5.3.1",
"hsts": "^2.1.0",
"jsonwebtoken": "^8.3.0",
"mongoose": "^5.2.14",
"mongoose": "^5.3.2",
"mongoose-findorcreate": "^3.0.0",
"mongoose-unique-validator": "^2.0.1",
"mongoose-unique-validator": "^2.0.2",
"morgan": "^1.9.1",
"passport": "^0.4.0",
"passport-google-oauth": "^1.0.0",
@@ -42,17 +42,17 @@
"devDependencies": {
"concurrently": "^4.0.1",
"cross-env": "^5.2.0",
"eslint": "^5.6.0",
"eslint": "^5.6.1",
"eslint-config-airbnb-base": "^13.1.0",
"eslint-config-node": "^4.0.0",
"eslint-config-prettier": "^3.1.0",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-jest": "^21.22.1",
"eslint-plugin-jest": "^21.24.1",
"eslint-plugin-node": "^7.0.1",
"eslint-plugin-prettier": "^2.6.2",
"eslint-plugin-prettier": "^3.0.0",
"eslint-plugin-react": "^7.11.1",
"jest": "^23.6.0",
"mongodb-memory-server": "^2.2.0",
"mongodb-memory-server": "^2.4.3",
"nodemon": "^1.18.4",
"prettier-eslint": "^8.8.2",
"supertest": "^3.3.0"