├── .gitignore ├── .prettierrc ├── .vscode └── settings.json ├── README.md ├── client ├── .gitignore ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt ├── src │ ├── components │ │ ├── App.js │ │ ├── Dashboard │ │ │ ├── index.css │ │ │ └── index.js │ │ ├── Header │ │ │ ├── index.css │ │ │ └── index.js │ │ ├── Landing │ │ │ ├── index.css │ │ │ └── index.js │ │ ├── Login │ │ │ ├── index.css │ │ │ └── index.js │ │ ├── NotFound │ │ │ ├── index.css │ │ │ ├── index.js │ │ │ └── notFound.png │ │ └── Register │ │ │ ├── index.css │ │ │ └── index.js │ ├── index.css │ ├── index.js │ ├── machine │ │ ├── createAuthMachine.js │ │ ├── events.js │ │ ├── index.js │ │ └── states.js │ ├── provider │ │ └── index.js │ ├── service │ │ ├── authService.js │ │ ├── config.js │ │ ├── index.js │ │ └── validators.js │ └── utils │ │ ├── PrivateRoute.js │ │ └── validate-email.js └── yarn.lock ├── package-lock.json ├── package.json └── server ├── index.js ├── package-lock.json ├── package.json ├── src ├── data │ ├── User.js │ ├── index.js │ └── schemas │ │ ├── User.js │ │ └── index.js ├── logic │ └── index.js ├── routes │ ├── index.js │ └── userRouter.js └── utils │ ├── jwt-validation.js │ ├── permission-validation.js │ └── validate-email.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/node 3 | 4 | ### Node ### 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | 24 | # nyc test coverage 25 | .nyc_output 26 | 27 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 28 | .grunt 29 | 30 | # Bower dependency directory (https://bower.io/) 31 | bower_components 32 | 33 | # node-waf configuration 34 | .lock-wscript 35 | 36 | # Compiled binary addons (https://nodejs.org/api/addons.html) 37 | build/Release 38 | 39 | # Dependency directories 40 | node_modules/ 41 | jspm_packages/ 42 | 43 | # TypeScript v1 declaration files 44 | typings/ 45 | 46 | # Optional npm cache directory 47 | .npm 48 | 49 | # Optional eslint cache 50 | .eslintcache 51 | 52 | # Optional REPL history 53 | .node_repl_history 54 | 55 | # Output of 'npm pack' 56 | *.tgz 57 | 58 | # dotenv environment variables file 59 | .env -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "printWidth": 120 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.insertSpaces": false, 4 | "editor.renderWhitespace": "none", 5 | "editor.trimAutoWhitespace": true, 6 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React xstate auth demo 2 | 3 | Basic demo to show the usage of React and Xstate with authentication flow 4 | 5 | You can visualize the auth machine here: https://xstate.js.org/viz/?gist=94e9a29e1ab016e06b8b354b9d558cf2 6 | 7 | Project Structure: 8 | 9 | ```sh 10 | -server 11 | -src 12 | -data 13 | -schemas 14 | -logic 15 | -routes 16 | -utils 17 | -index.js 18 | -.env 19 | 20 | -client 21 | -src 22 | -components 23 | -machine 24 | -provider 25 | -service 26 | -utils 27 | -index.js 28 | -.env 29 | ``` 30 | 31 | System Requirements: 32 | 33 | - Node 34 | - Mongodb 35 | 36 | Server: 37 | 38 | 1. Install the project dependencies 39 | 40 | ```sh 41 | $ npm i 42 | ``` 43 | 44 | 2. Create the .env file on the root of the server/ folder 45 | 46 | ```sh 47 | $ touch .env 48 | ``` 49 | 50 | .env 51 | 52 | ```sh 53 | DB_URL=mongodb://localhost:27017/your-database 54 | PORT=5000 55 | TOKEN_SECRET=your-secret 56 | TOKEN_EXP=3h 57 | ``` 58 | 59 | 3. Start the API 60 | 61 | ```sh 62 | $ npm start 63 | ``` 64 | 65 | Client: 66 | 67 | 1. Download dependencies 68 | 69 | ```sh 70 | $ npm i 71 | ``` 72 | 73 | 2. Create the .env file on the root of the client/ folder 74 | 75 | ```sh 76 | $ touch .env 77 | ``` 78 | 79 | .env 80 | 81 | ```sh 82 | REACT_APP_API_BASE_URL=http://localhost:5000/api 83 | ``` 84 | 85 | \*Note: In order to enviroment variables work with this react project without touching any config file they all have to start with REACT_APP 86 | 87 | 3. Start the app 88 | 89 | ```sh 90 | $ npm start 91 | ``` 92 | 93 | Author: [http://github.com/mikelpmc](http://github.com/mikelpmc) 94 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^4.2.4", 7 | "@testing-library/react": "^9.3.2", 8 | "@testing-library/user-event": "^7.1.2", 9 | "@xstate/react": "^0.8.1", 10 | "normalize.css": "^8.0.1", 11 | "react": "^16.13.1", 12 | "react-dom": "^16.13.1", 13 | "react-router-dom": "^5.1.2", 14 | "react-scripts": "^4.0.1", 15 | "xstate": "^4.8.0" 16 | }, 17 | "scripts": { 18 | "start": "react-scripts start", 19 | "build": "react-scripts build", 20 | "test": "react-scripts test", 21 | "eject": "react-scripts eject" 22 | }, 23 | "eslintConfig": { 24 | "extends": "react-app" 25 | }, 26 | "browserslist": { 27 | "production": [ 28 | ">0.2%", 29 | "not dead", 30 | "not op_mini all" 31 | ], 32 | "development": [ 33 | "last 1 chrome version", 34 | "last 1 firefox version", 35 | "last 1 safari version" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikelpmc/react-xstate-api-auth/ed99aad0fa5acb11343464d4f218456c60d05476/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /client/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikelpmc/react-xstate-api-auth/ed99aad0fa5acb11343464d4f218456c60d05476/client/public/logo192.png -------------------------------------------------------------------------------- /client/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikelpmc/react-xstate-api-auth/ed99aad0fa5acb11343464d4f218456c60d05476/client/public/logo512.png -------------------------------------------------------------------------------- /client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /client/src/components/App.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { Route, Switch, Redirect } from 'react-router-dom'; 3 | import { Context } from '../provider'; 4 | import PrivateRoute from './../utils/PrivateRoute'; 5 | import Header from './Header/'; 6 | import Dashboard from './Dashboard/'; 7 | import Landing from './Landing/'; 8 | import Login from './Login/'; 9 | import Register from './Register/'; 10 | import NotFound from './NotFound/'; 11 | 12 | const App = () => { 13 | const { store } = useContext(Context); 14 | 15 | return ( 16 |
17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | (store.context.isLoggedIn ? : )} /> 25 | (store.context.isLoggedIn ? : )} 28 | /> 29 | 30 | 31 |
32 | ); 33 | }; 34 | 35 | export default App; 36 | -------------------------------------------------------------------------------- /client/src/components/Dashboard/index.css: -------------------------------------------------------------------------------- 1 | .dashboard { 2 | display: grid; 3 | grid-template-areas: 'title' 'info'; 4 | text-align: center; 5 | } 6 | 7 | .dashboard__title { 8 | grid-area: title; 9 | } 10 | 11 | .dashboard__info { 12 | grid-area: info; 13 | } 14 | -------------------------------------------------------------------------------- /client/src/components/Dashboard/index.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect } from 'react'; 2 | import { Context } from '../../provider'; 3 | import './index.css'; 4 | 5 | const Dashboard = () => { 6 | const { 7 | store, 8 | actions: { onRetrieveUser }, 9 | } = useContext(Context); 10 | 11 | useEffect(() => { 12 | onRetrieveUser(); 13 | }, [store, onRetrieveUser]); 14 | 15 | const loading = 'login.success.retrieve_user.loading'; 16 | const failure = 'login.success.retrieve_user.failure'; 17 | const success = 'login.success.retrieve_user.success'; 18 | 19 | return ( 20 |
21 |

Dashboard

22 | 23 | {store.matches(failure) &&

{store.context.error}

} 24 | {store.matches(loading) &&

Retrieving user info...

} 25 | {store.matches(success) && ( 26 |
27 |

Name: {store.context.user.name}

28 |

Email: {store.context.user.email}

29 |
30 | )} 31 |
32 | ); 33 | }; 34 | 35 | export default Dashboard; 36 | -------------------------------------------------------------------------------- /client/src/components/Header/index.css: -------------------------------------------------------------------------------- 1 | .navbar { 2 | list-style: none; 3 | display: flex; 4 | background-color: black; 5 | box-shadow: 5px solid darkslategray; 6 | margin: 0; 7 | } 8 | 9 | .navbar__item { 10 | padding: 10px; 11 | margin-left: 5px; 12 | font-size: 1.5em; 13 | transition: box-shadow 0.3s; 14 | } 15 | 16 | .navbar__item:hover { 17 | box-shadow: inset 0 -4px 0 0 white; 18 | } 19 | 20 | .navbar__item a { 21 | color: whitesmoke; 22 | text-decoration: none; 23 | } 24 | 25 | .navbar__item--right { 26 | margin-left: auto; 27 | } 28 | -------------------------------------------------------------------------------- /client/src/components/Header/index.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import './index.css'; 4 | import { Context } from '../../provider'; 5 | 6 | const Header = () => { 7 | const { 8 | store: { 9 | context: { isLoggedIn } 10 | }, 11 | actions 12 | } = useContext(Context); 13 | 14 | return ( 15 |
16 | 42 |
43 | ); 44 | }; 45 | 46 | export default Header; 47 | -------------------------------------------------------------------------------- /client/src/components/Landing/index.css: -------------------------------------------------------------------------------- 1 | .landing { 2 | display: grid; 3 | justify-content: center; 4 | margin-top: 20px; 5 | text-align: center; 6 | } 7 | 8 | .landing__title { 9 | font-size: 2.5em; 10 | } 11 | 12 | .landing__button { 13 | padding: 10px; 14 | min-width: 200px; 15 | border: 5px solid darkblue; 16 | margin: 5px; 17 | color: darkslategrey; 18 | text-decoration: none; 19 | text-transform: uppercase; 20 | font-size: 1.5em; 21 | transition: color 0.5s, background-color 0.5s; 22 | } 23 | 24 | .landing__button:hover { 25 | color: white; 26 | background-color: black; 27 | } 28 | -------------------------------------------------------------------------------- /client/src/components/Landing/index.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { Context } from '../../provider'; 4 | import './index.css'; 5 | 6 | const Landing = () => { 7 | const { store } = useContext(Context); 8 | 9 | return ( 10 |
11 |

React Xstate auth demo

12 | 13 | {!store.context.isLoggedIn ? ( 14 | 15 | 16 | Login 17 | 18 | 19 | 20 | Register 21 | 22 | 23 | ) : ( 24 | 25 |

Welcome back!

26 | 27 | Go to Dashboard 28 | 29 |
30 | )} 31 |
32 | ); 33 | }; 34 | 35 | export default Landing; 36 | -------------------------------------------------------------------------------- /client/src/components/Login/index.css: -------------------------------------------------------------------------------- 1 | .login { 2 | display: grid; 3 | height: 100%; 4 | grid-template-columns: 1fr; 5 | grid-template-rows: 0.1fr 0.5fr 1fr; 6 | grid-gap: 1px 1px; 7 | grid-template-areas: 'login__title' 'login__error' 'login__form'; 8 | text-align: center; 9 | padding: 50px; 10 | } 11 | 12 | .login__title { 13 | grid-area: login__title; 14 | } 15 | 16 | .login__form { 17 | grid-area: login__form; 18 | } 19 | 20 | input[type='text'], 21 | input[type='password'] { 22 | width: 50%; 23 | padding: 12px 20px; 24 | margin: 8px 0; 25 | display: inline-block; 26 | border: 1px solid #ccc; 27 | border-radius: 4px; 28 | box-sizing: border-box; 29 | } 30 | 31 | .login__error { 32 | grid-area: login__error; 33 | color: tomato; 34 | font-size: 1.5em; 35 | } 36 | 37 | .login__button { 38 | padding: 10px; 39 | min-width: 200px; 40 | border: 5px solid darkblue; 41 | margin: 5px; 42 | color: darkslategrey; 43 | text-decoration: none; 44 | text-transform: uppercase; 45 | font-size: 1.5em; 46 | transition: color 0.5s, background-color 0.5s; 47 | } 48 | 49 | .login__button:hover { 50 | color: white; 51 | background-color: black; 52 | } 53 | -------------------------------------------------------------------------------- /client/src/components/Login/index.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from 'react'; 2 | import { Link, Redirect } from 'react-router-dom'; 3 | import { Context } from '../../provider'; 4 | import { STATES } from '../../machine'; 5 | import './index.css'; 6 | 7 | const Login = () => { 8 | const { store, actions } = useContext(Context); 9 | 10 | const [email, setEmail] = useState(''); 11 | const [password, setPassword] = useState(''); 12 | 13 | const handleSubmit = e => { 14 | e.preventDefault(); 15 | 16 | actions.onLogin(email, password); 17 | }; 18 | 19 | return ( 20 |
21 |

Login

22 | 23 | {store.matches({ [STATES.LOGIN.NODE_NAME]: STATES.LOGIN.FAILURE }) &&

{store.context.error}

} 24 | {store.matches({ [STATES.LOGIN.NODE_NAME]: STATES.LOGIN.LOADING }) &&

Authorizing....

} 25 | {store.matches({ [STATES.LOGIN.NODE_NAME]: STATES.LOGIN.SUCCESS }) && } 26 | 27 |
28 |
29 | setEmail(e.target.value)} 34 | placeholder="Enter your email" 35 | /> 36 |
37 | 38 |
39 | setPassword(e.target.value)} 44 | placeholder="Enter your password" 45 | /> 46 |
47 | 48 | 51 | 52 | Register 53 |
54 |
55 | ); 56 | }; 57 | 58 | export default Login; 59 | -------------------------------------------------------------------------------- /client/src/components/NotFound/index.css: -------------------------------------------------------------------------------- 1 | .notFound { 2 | display: grid; 3 | grid-template-rows: 100px 1fr; 4 | justify-content: center; 5 | } 6 | 7 | .notFound__title { 8 | font-size: 2.5em; 9 | } 10 | 11 | .notFound__image { 12 | height: 250px; 13 | } 14 | -------------------------------------------------------------------------------- /client/src/components/NotFound/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import image from './notFound.png'; 3 | import './index.css'; 4 | 5 | const NotFound = () => { 6 | return ( 7 |
8 |

Sorry! Page not found

9 | 10 | Page not found 11 |
12 | ); 13 | }; 14 | 15 | export default NotFound; 16 | -------------------------------------------------------------------------------- /client/src/components/NotFound/notFound.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikelpmc/react-xstate-api-auth/ed99aad0fa5acb11343464d4f218456c60d05476/client/src/components/NotFound/notFound.png -------------------------------------------------------------------------------- /client/src/components/Register/index.css: -------------------------------------------------------------------------------- 1 | .register { 2 | display: grid; 3 | height: 100%; 4 | grid-template-columns: 1fr; 5 | grid-template-rows: 0.1fr 0.5fr 1fr; 6 | grid-gap: 1px 1px; 7 | grid-template-areas: 'register__title' 'register__error' 'register__form'; 8 | text-align: center; 9 | padding: 50px; 10 | } 11 | 12 | .register__title { 13 | grid-area: register__title; 14 | } 15 | 16 | .register__form { 17 | grid-area: register__form; 18 | } 19 | 20 | input[type='text'], 21 | input[type='email'], 22 | input[type='password'] { 23 | width: 50%; 24 | padding: 12px 20px; 25 | margin: 8px 0; 26 | display: inline-block; 27 | border: 1px solid #ccc; 28 | border-radius: 4px; 29 | box-sizing: border-box; 30 | } 31 | 32 | .register__error { 33 | grid-area: register__error; 34 | color: tomato; 35 | font-size: 1.5em; 36 | } 37 | 38 | .register__button { 39 | padding: 10px; 40 | min-width: 200px; 41 | border: 5px solid darkblue; 42 | margin: 5px; 43 | color: darkslategrey; 44 | text-decoration: none; 45 | text-transform: uppercase; 46 | font-size: 1.5em; 47 | transition: color 0.5s, background-color 0.5s; 48 | } 49 | 50 | .register__button:hover { 51 | color: white; 52 | background-color: black; 53 | } 54 | -------------------------------------------------------------------------------- /client/src/components/Register/index.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from 'react'; 2 | import { Link, Redirect } from 'react-router-dom'; 3 | import { Context } from '../../provider'; 4 | import { STATES } from '../../machine'; 5 | import './index.css'; 6 | 7 | const Register = () => { 8 | const { store, actions } = useContext(Context); 9 | 10 | const [name, setName] = useState(''); 11 | const [email, setEmail] = useState(''); 12 | const [password, setPassword] = useState(''); 13 | 14 | const handleSubmit = e => { 15 | e.preventDefault(); 16 | 17 | actions.onRegister(name, email, password); 18 | }; 19 | 20 | return ( 21 |
22 |

Register

23 | 24 | {store.matches({ [STATES.REGISTER.NODE_NAME]: STATES.REGISTER.FAILURE }) &&

Error! {store.context.error}

} 25 | {store.matches({ [STATES.REGISTER.NODE_NAME]: STATES.REGISTER.LOADING }) &&

Registering your data...

} 26 | {store.matches({ [STATES.REGISTER.NODE_NAME]: STATES.REGISTER.SUCCESS }) && } 27 | 28 |
29 | setName(e.target.value)} 34 | placeholder="Enter your name" 35 | /> 36 |
37 | 38 | setEmail(e.target.value)} 43 | placeholder="Enter your email" 44 | /> 45 |
46 | 47 | setPassword(e.target.value)} 52 | placeholder="Enter your password" 53 | /> 54 |
55 | 56 | 59 | 60 | Login 61 |
62 |
63 | ); 64 | }; 65 | 66 | export default Register; 67 | -------------------------------------------------------------------------------- /client/src/index.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Yanone+Kaffeesatz'); 2 | 3 | body { 4 | font-family: 'Yanone Kaffeesatz', sans-serif; 5 | background-color: #fcfcfc; 6 | } 7 | -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { BrowserRouter as Router } from 'react-router-dom'; 4 | 5 | import AuthProvider from './provider/'; 6 | import App from './components/App'; 7 | 8 | import 'normalize.css'; 9 | import './index.css'; 10 | 11 | ReactDOM.render( 12 | 13 | 14 | 15 | 16 | , 17 | document.getElementById('root') 18 | ); 19 | -------------------------------------------------------------------------------- /client/src/machine/createAuthMachine.js: -------------------------------------------------------------------------------- 1 | import { Machine, assign } from 'xstate'; 2 | import STATES from './states'; 3 | import EVENTS from './events'; 4 | 5 | // https://xstate.js.org/viz/?gist=94e9a29e1ab016e06b8b354b9d558cf2 6 | 7 | const createAuthMachine = ({ authService }) => 8 | Machine({ 9 | id: 'authMachine', 10 | initial: authService.isLoggedIn() ? 'login' : 'idle', 11 | context: { 12 | isLoggedIn: authService.isLoggedIn(), 13 | isRegistered: false, 14 | user: {}, 15 | error: '' 16 | }, 17 | states: { 18 | [STATES.IDLE]: { 19 | on: { 20 | [EVENTS.REGISTER]: { target: STATES.REGISTER.NODE_NAME }, 21 | [EVENTS.LOGIN]: { target: STATES.LOGIN.NODE_NAME } 22 | } 23 | }, 24 | [STATES.REGISTER.NODE_NAME]: { 25 | initial: STATES.REGISTER.LOADING, 26 | states: { 27 | [STATES.REGISTER.LOADING]: { 28 | invoke: { 29 | id: 'registerService', 30 | src: (_, event) => authService.register(event.name, event.email, event.password), 31 | onDone: { 32 | target: STATES.REGISTER.SUCCESS, 33 | actions: assign({ 34 | isRegistered: (_, event) => { 35 | return event.data; 36 | } 37 | }) 38 | }, 39 | onError: { 40 | target: STATES.REGISTER.FAILURE, 41 | actions: assign({ 42 | isRegistered: false, 43 | error: (_, event) => { 44 | return event.data.message; 45 | } 46 | }) 47 | } 48 | } 49 | }, 50 | [STATES.REGISTER.SUCCESS]: { 51 | on: { 52 | [EVENTS.LOGIN]: { 53 | target: `#authMachine.${STATES.LOGIN.NODE_NAME}.${STATES.LOGIN.LOADING}` 54 | } 55 | } 56 | }, 57 | [STATES.REGISTER.FAILURE]: { 58 | on: { 59 | [EVENTS.REGISTER]: { 60 | target: `#authMachine.${STATES.REGISTER.NODE_NAME}.${STATES.REGISTER.LOADING}` 61 | } 62 | } 63 | } 64 | } 65 | }, 66 | [STATES.LOGIN.NODE_NAME]: { 67 | initial: STATES.LOGIN.LOADING, 68 | states: { 69 | [STATES.LOGIN.LOADING]: { 70 | invoke: { 71 | id: 'loginService', 72 | src: (_, event) => authService.login(event.email, event.password), 73 | onDone: { 74 | target: STATES.LOGIN.SUCCESS, 75 | actions: assign({ 76 | isLoggedIn: (_, event) => { 77 | return event.data; 78 | } 79 | }) 80 | }, 81 | onError: { 82 | target: STATES.LOGIN.FAILURE, 83 | actions: assign({ 84 | isRegistered: false, 85 | error: (_, event) => { 86 | return event.data.message; 87 | } 88 | }) 89 | } 90 | } 91 | }, 92 | [STATES.LOGIN.SUCCESS]: { 93 | initial: 'idle', 94 | states: { 95 | idle: { 96 | on: { 97 | [EVENTS.RETRIEVE_USER]: { 98 | target: STATES.RETRIEVE_USER.NODE_NAME 99 | } 100 | } 101 | }, 102 | [STATES.RETRIEVE_USER.NODE_NAME]: { 103 | initial: STATES.RETRIEVE_USER.LOADING, 104 | states: { 105 | [STATES.RETRIEVE_USER.LOADING]: { 106 | invoke: { 107 | id: 'retrieveUserService', 108 | src: () => authService.retrieveUser(), 109 | onDone: { 110 | target: STATES.RETRIEVE_USER.SUCCESS, 111 | actions: assign({ 112 | user: (_, event) => { 113 | return event.data; 114 | } 115 | }) 116 | }, 117 | onError: { 118 | target: STATES.RETRIEVE_USER.FAILURE, 119 | actions: assign({ 120 | error: (_, event) => { 121 | return event.data.message; 122 | } 123 | }) 124 | } 125 | } 126 | }, 127 | [STATES.RETRIEVE_USER.SUCCESS]: {}, 128 | [STATES.RETRIEVE_USER.FAILURE]: { 129 | on: { 130 | [EVENTS.RETRY_RETRIEVE_USER]: { 131 | target: STATES.RETRIEVE_USER.LOADING 132 | } 133 | } 134 | } 135 | } 136 | } 137 | }, 138 | on: { 139 | [EVENTS.LOGOUT]: { 140 | target: 'logout' 141 | } 142 | } 143 | }, 144 | [STATES.LOGIN.FAILURE]: { 145 | on: { 146 | [EVENTS.LOGIN]: { 147 | target: `#authMachine.${STATES.LOGIN.NODE_NAME}.${STATES.LOGIN.LOADING}` 148 | } 149 | } 150 | }, 151 | logout: { 152 | invoke: { 153 | id: 'logoutService', 154 | src: () => authService.logout(), 155 | onDone: { 156 | target: '#authMachine.idle', 157 | actions: assign({ 158 | isLoggedIn: false, 159 | isRegistered: false, 160 | error: false, 161 | user: {} 162 | }) 163 | }, 164 | onError: { 165 | target: '#authMachine.idle', 166 | actions: assign({ 167 | isLoggedIn: false, 168 | isRegistered: false, 169 | error: false, 170 | user: {} 171 | }) 172 | } 173 | } 174 | } 175 | } 176 | } 177 | } 178 | }); 179 | 180 | export default createAuthMachine; 181 | -------------------------------------------------------------------------------- /client/src/machine/events.js: -------------------------------------------------------------------------------- 1 | const EVENTS = { 2 | REGISTER: 'register', 3 | LOGIN: 'login', 4 | LOGOUT: 'logout', 5 | RETRIEVE_USER: 'retrieve_user', 6 | RETRY_RETRIEVE_USER: 'retry_retrieve_user' 7 | }; 8 | 9 | export default EVENTS; 10 | -------------------------------------------------------------------------------- /client/src/machine/index.js: -------------------------------------------------------------------------------- 1 | import STATES from './states'; 2 | import EVENTS from './events'; 3 | 4 | import createAuthMachine from './createAuthMachine'; 5 | import { authService } from '../service'; 6 | 7 | const authMachine = createAuthMachine({ authService }); 8 | 9 | export { authMachine, STATES, EVENTS }; 10 | -------------------------------------------------------------------------------- /client/src/machine/states.js: -------------------------------------------------------------------------------- 1 | const STATES = { 2 | IDLE: 'idle', 3 | REGISTER: { 4 | NODE_NAME: 'register', 5 | LOADING: 'loading', 6 | SUCCESS: 'success', 7 | FAILURE: 'failure' 8 | }, 9 | LOGIN: { 10 | NODE_NAME: 'login', 11 | LOADING: 'loading', 12 | SUCCESS: 'success', 13 | FAILURE: 'failure' 14 | }, 15 | RETRIEVE_USER: { 16 | NODE_NAME: 'retrieve_user', 17 | LOADING: 'loading', 18 | SUCCESS: 'success', 19 | FAILURE: 'failure' 20 | } 21 | }; 22 | 23 | export default STATES; 24 | -------------------------------------------------------------------------------- /client/src/provider/index.js: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react'; 2 | import { useMachine } from '@xstate/react'; 3 | import { authMachine, EVENTS } from '../machine'; 4 | 5 | export const Context = React.createContext(); 6 | 7 | const AuthProvider = ({ children }) => { 8 | const [state, send] = useMachine(authMachine); 9 | 10 | const handleRegister = useCallback( 11 | (name, email, password) => { 12 | send(EVENTS.REGISTER, { name, email, password }); 13 | }, 14 | [send] 15 | ); 16 | 17 | const handleLogin = useCallback( 18 | (email, password) => { 19 | send(EVENTS.LOGIN, { email, password }); 20 | }, 21 | [send] 22 | ); 23 | 24 | const handleLogout = useCallback(() => { 25 | send(EVENTS.LOGOUT); 26 | }, [send]); 27 | 28 | const handleRetrieveUser = useCallback(() => { 29 | send(EVENTS.RETRIEVE_USER); 30 | }, [send]); 31 | 32 | return ( 33 | 44 | {children} 45 | 46 | ); 47 | }; 48 | 49 | export default AuthProvider; 50 | -------------------------------------------------------------------------------- /client/src/service/authService.js: -------------------------------------------------------------------------------- 1 | class AuthService { 2 | constructor({ config, validators }) { 3 | this.config = config; 4 | this.validators = validators; 5 | } 6 | 7 | _validateStringField(field, value) { 8 | if (typeof value !== 'string' || !value.trim().length) throw Error(`${field} is not valid`); 9 | } 10 | 11 | _validateEmail(email) { 12 | const validateEmail = this.validators.get('validateEmail'); 13 | 14 | if (!validateEmail(email)) throw Error(`${email} is not a valid email`); 15 | } 16 | 17 | _userId(userId) { 18 | if (typeof userId !== 'undefined') { 19 | sessionStorage.setItem('userId', userId); 20 | 21 | return; 22 | } 23 | 24 | return sessionStorage.getItem('userId'); 25 | } 26 | 27 | _token(token) { 28 | if (typeof token !== 'undefined') { 29 | sessionStorage.setItem('token', token); 30 | 31 | return; 32 | } 33 | 34 | return sessionStorage.getItem('token'); 35 | } 36 | 37 | isLoggedIn() { 38 | const res = !!(this._userId() && this._token()); 39 | 40 | return res; 41 | } 42 | 43 | register(name, email, password) { 44 | return Promise.resolve().then(() => { 45 | this._validateStringField('name', name); 46 | this._validateEmail(email); 47 | this._validateStringField('password', password); 48 | 49 | return fetch(`${this.config.get('API_URL')}/register`, { 50 | method: 'POST', 51 | body: JSON.stringify({ name, email, password }), 52 | headers: { 53 | 'content-type': 'application/json' 54 | } 55 | }) 56 | .then(res => { 57 | if (res.status === 201) { 58 | return res; 59 | } 60 | 61 | return res.json().then(({ message }) => { 62 | throw Error(message); 63 | }); 64 | }) 65 | .then(res => res.json()) 66 | .then(() => true); 67 | }); 68 | } 69 | 70 | login(email, password) { 71 | const API_URL = this.config.get('API_URL'); 72 | 73 | if (this.isLoggedIn()) return Promise.resolve().then(() => true); 74 | 75 | return Promise.resolve().then(() => { 76 | this._validateEmail(email); 77 | this._validateStringField('password', password); 78 | 79 | return fetch(`${API_URL}/authenticate`, { 80 | method: 'POST', 81 | body: JSON.stringify({ email, password }), 82 | headers: { 83 | 'content-type': 'application/json' 84 | } 85 | }) 86 | .then(res => { 87 | if (res.status === 200) { 88 | return res; 89 | } 90 | 91 | return res.json().then(({ message }) => { 92 | throw Error(message); 93 | }); 94 | }) 95 | .then(res => res.json()) 96 | .then(({ token, user }) => { 97 | this._token(token); 98 | this._userId(user.id); 99 | 100 | return true; 101 | }); 102 | }); 103 | } 104 | 105 | logout() { 106 | return Promise.resolve().then(() => { 107 | delete this.token; 108 | delete this.userId; 109 | 110 | sessionStorage.clear(); 111 | }); 112 | } 113 | 114 | retrieveUser() { 115 | const API_URL = this.config.get('API_URL'); 116 | 117 | return fetch(`${API_URL}/user/${this._userId()}`, { 118 | method: 'GET', 119 | headers: { 120 | 'content-type': 'application/json', 121 | authorization: `Bearer ${this._token()}` 122 | } 123 | }) 124 | .then(res => { 125 | if (res.status === 200) { 126 | return res; 127 | } 128 | 129 | return res.json().then(({ message }) => { 130 | throw Error(message); 131 | }); 132 | }) 133 | .then(res => res.json()) 134 | .then(({ user }) => user); 135 | } 136 | } 137 | 138 | export default AuthService; 139 | -------------------------------------------------------------------------------- /client/src/service/config.js: -------------------------------------------------------------------------------- 1 | class Config { 2 | constructor() { 3 | this._config = { 4 | API_URL: process.env.REACT_APP_API_BASE_URL 5 | }; 6 | } 7 | 8 | get(key) { 9 | return this._config[key]; 10 | } 11 | } 12 | 13 | export default Config; 14 | -------------------------------------------------------------------------------- /client/src/service/index.js: -------------------------------------------------------------------------------- 1 | import Config from './config'; 2 | import Validators from './validators'; 3 | import AuthService from './authService'; 4 | 5 | import { validateEmail } from '../utils/validate-email'; 6 | 7 | const authService = new AuthService({ 8 | config: new Config(), 9 | validators: new Validators(validateEmail) 10 | }); 11 | 12 | export { authService }; 13 | -------------------------------------------------------------------------------- /client/src/service/validators.js: -------------------------------------------------------------------------------- 1 | class Validators { 2 | constructor(validateEmail) { 3 | this._validators = { 4 | validateEmail 5 | }; 6 | } 7 | 8 | get(key) { 9 | return this._validators[key]; 10 | } 11 | } 12 | 13 | export default Validators; 14 | -------------------------------------------------------------------------------- /client/src/utils/PrivateRoute.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { Route, Redirect } from 'react-router-dom'; 3 | import { Context } from '../provider'; 4 | 5 | const PrivateRoute = ({ children, ...rest }) => { 6 | const { store } = useContext(Context); 7 | 8 | return ( 9 | 12 | store.context.isLoggedIn ? ( 13 | children 14 | ) : ( 15 | 21 | ) 22 | } 23 | /> 24 | ); 25 | }; 26 | 27 | export default PrivateRoute; 28 | -------------------------------------------------------------------------------- /client/src/utils/validate-email.js: -------------------------------------------------------------------------------- 1 | const validateEmail = email => { 2 | const regex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; 3 | 4 | return regex.test(String(email).toLowerCase()); 5 | }; 6 | 7 | module.exports = { validateEmail }; 8 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-xstate-api-auth", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-xstate-api-auth", 3 | "version": "1.0.0", 4 | "description": "Basic demo to show the usage of React and Xstate with authentication flow", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC" 12 | } 13 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('dotenv').config(); 4 | 5 | const mongoose = require('mongoose'); 6 | const express = require('express'); 7 | const router = require('./src/routes/'); 8 | const cors = require('cors'); 9 | 10 | const { userRouter } = require('./src/routes/'); 11 | 12 | const { 13 | env: { PORT, DB_URL } 14 | } = process; 15 | 16 | mongoose 17 | .connect( 18 | DB_URL, 19 | { useNewUrlParser: true } 20 | ) 21 | .then(() => { 22 | const port = PORT || process.argv[2] || 5000; 23 | 24 | const app = express(); 25 | 26 | app.use(cors()); 27 | 28 | app.use('/api', userRouter); 29 | 30 | app.listen(port, () => console.log(`server running on port ${port}`)); 31 | 32 | process.on('SIGINT', () => { 33 | console.log('\nstopping server'); 34 | 35 | mongoose.connection.close(() => { 36 | console.log('db connection closed'); 37 | 38 | process.exit(); 39 | }); 40 | }); 41 | }) 42 | .catch(console.error); 43 | -------------------------------------------------------------------------------- /server/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/bson": { 8 | "version": "4.0.3", 9 | "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.3.tgz", 10 | "integrity": "sha512-mVRvYnTOZJz3ccpxhr3wgxVmSeiYinW+zlzQz3SXWaJmD1DuL05Jeq7nKw3SnbKmbleW5qrLG5vdyWe/A9sXhw==", 11 | "requires": { 12 | "@types/node": "*" 13 | } 14 | }, 15 | "@types/mongodb": { 16 | "version": "3.6.3", 17 | "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.3.tgz", 18 | "integrity": "sha512-6YNqGP1hk5bjUFaim+QoFFuI61WjHiHE1BNeB41TA00Xd2K7zG4lcWyLLq/XtIp36uMavvS5hoAUJ+1u/GcX2Q==", 19 | "requires": { 20 | "@types/bson": "*", 21 | "@types/node": "*" 22 | } 23 | }, 24 | "@types/node": { 25 | "version": "14.14.16", 26 | "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.16.tgz", 27 | "integrity": "sha512-naXYePhweTi+BMv11TgioE2/FXU4fSl29HAH1ffxVciNsH3rYXjNP2yM8wqmSm7jS20gM8TIklKiTen+1iVncw==" 28 | }, 29 | "accepts": { 30 | "version": "1.3.7", 31 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 32 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 33 | "requires": { 34 | "mime-types": "~2.1.24", 35 | "negotiator": "0.6.2" 36 | } 37 | }, 38 | "array-flatten": { 39 | "version": "1.1.1", 40 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 41 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 42 | }, 43 | "bl": { 44 | "version": "2.2.1", 45 | "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", 46 | "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", 47 | "requires": { 48 | "readable-stream": "^2.3.5", 49 | "safe-buffer": "^5.1.1" 50 | } 51 | }, 52 | "bluebird": { 53 | "version": "3.5.1", 54 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", 55 | "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" 56 | }, 57 | "body-parser": { 58 | "version": "1.19.0", 59 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 60 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 61 | "requires": { 62 | "bytes": "3.1.0", 63 | "content-type": "~1.0.4", 64 | "debug": "2.6.9", 65 | "depd": "~1.1.2", 66 | "http-errors": "1.7.2", 67 | "iconv-lite": "0.4.24", 68 | "on-finished": "~2.3.0", 69 | "qs": "6.7.0", 70 | "raw-body": "2.4.0", 71 | "type-is": "~1.6.17" 72 | } 73 | }, 74 | "bson": { 75 | "version": "1.1.5", 76 | "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz", 77 | "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg==" 78 | }, 79 | "buffer-equal-constant-time": { 80 | "version": "1.0.1", 81 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", 82 | "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" 83 | }, 84 | "bytes": { 85 | "version": "3.1.0", 86 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 87 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 88 | }, 89 | "content-disposition": { 90 | "version": "0.5.3", 91 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 92 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 93 | "requires": { 94 | "safe-buffer": "5.1.2" 95 | } 96 | }, 97 | "content-type": { 98 | "version": "1.0.4", 99 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 100 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 101 | }, 102 | "cookie": { 103 | "version": "0.4.0", 104 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 105 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 106 | }, 107 | "cookie-signature": { 108 | "version": "1.0.6", 109 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 110 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 111 | }, 112 | "core-util-is": { 113 | "version": "1.0.2", 114 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 115 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 116 | }, 117 | "cors": { 118 | "version": "2.8.5", 119 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 120 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 121 | "requires": { 122 | "object-assign": "^4", 123 | "vary": "^1" 124 | } 125 | }, 126 | "debug": { 127 | "version": "2.6.9", 128 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 129 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 130 | "requires": { 131 | "ms": "2.0.0" 132 | } 133 | }, 134 | "denque": { 135 | "version": "1.4.1", 136 | "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", 137 | "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==" 138 | }, 139 | "depd": { 140 | "version": "1.1.2", 141 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 142 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 143 | }, 144 | "destroy": { 145 | "version": "1.0.4", 146 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 147 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 148 | }, 149 | "dotenv": { 150 | "version": "6.2.0", 151 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.2.0.tgz", 152 | "integrity": "sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==" 153 | }, 154 | "ecdsa-sig-formatter": { 155 | "version": "1.0.11", 156 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", 157 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", 158 | "requires": { 159 | "safe-buffer": "^5.0.1" 160 | } 161 | }, 162 | "ee-first": { 163 | "version": "1.1.1", 164 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 165 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 166 | }, 167 | "encodeurl": { 168 | "version": "1.0.2", 169 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 170 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 171 | }, 172 | "escape-html": { 173 | "version": "1.0.3", 174 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 175 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 176 | }, 177 | "etag": { 178 | "version": "1.8.1", 179 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 180 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 181 | }, 182 | "express": { 183 | "version": "4.17.1", 184 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 185 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 186 | "requires": { 187 | "accepts": "~1.3.7", 188 | "array-flatten": "1.1.1", 189 | "body-parser": "1.19.0", 190 | "content-disposition": "0.5.3", 191 | "content-type": "~1.0.4", 192 | "cookie": "0.4.0", 193 | "cookie-signature": "1.0.6", 194 | "debug": "2.6.9", 195 | "depd": "~1.1.2", 196 | "encodeurl": "~1.0.2", 197 | "escape-html": "~1.0.3", 198 | "etag": "~1.8.1", 199 | "finalhandler": "~1.1.2", 200 | "fresh": "0.5.2", 201 | "merge-descriptors": "1.0.1", 202 | "methods": "~1.1.2", 203 | "on-finished": "~2.3.0", 204 | "parseurl": "~1.3.3", 205 | "path-to-regexp": "0.1.7", 206 | "proxy-addr": "~2.0.5", 207 | "qs": "6.7.0", 208 | "range-parser": "~1.2.1", 209 | "safe-buffer": "5.1.2", 210 | "send": "0.17.1", 211 | "serve-static": "1.14.1", 212 | "setprototypeof": "1.1.1", 213 | "statuses": "~1.5.0", 214 | "type-is": "~1.6.18", 215 | "utils-merge": "1.0.1", 216 | "vary": "~1.1.2" 217 | } 218 | }, 219 | "finalhandler": { 220 | "version": "1.1.2", 221 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 222 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 223 | "requires": { 224 | "debug": "2.6.9", 225 | "encodeurl": "~1.0.2", 226 | "escape-html": "~1.0.3", 227 | "on-finished": "~2.3.0", 228 | "parseurl": "~1.3.3", 229 | "statuses": "~1.5.0", 230 | "unpipe": "~1.0.0" 231 | } 232 | }, 233 | "forwarded": { 234 | "version": "0.1.2", 235 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 236 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 237 | }, 238 | "fresh": { 239 | "version": "0.5.2", 240 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 241 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 242 | }, 243 | "http-errors": { 244 | "version": "1.7.2", 245 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 246 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 247 | "requires": { 248 | "depd": "~1.1.2", 249 | "inherits": "2.0.3", 250 | "setprototypeof": "1.1.1", 251 | "statuses": ">= 1.5.0 < 2", 252 | "toidentifier": "1.0.0" 253 | } 254 | }, 255 | "iconv-lite": { 256 | "version": "0.4.24", 257 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 258 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 259 | "requires": { 260 | "safer-buffer": ">= 2.1.2 < 3" 261 | } 262 | }, 263 | "inherits": { 264 | "version": "2.0.3", 265 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 266 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 267 | }, 268 | "ipaddr.js": { 269 | "version": "1.9.1", 270 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 271 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 272 | }, 273 | "isarray": { 274 | "version": "1.0.0", 275 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 276 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 277 | }, 278 | "jsonwebtoken": { 279 | "version": "8.5.1", 280 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", 281 | "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", 282 | "requires": { 283 | "jws": "^3.2.2", 284 | "lodash.includes": "^4.3.0", 285 | "lodash.isboolean": "^3.0.3", 286 | "lodash.isinteger": "^4.0.4", 287 | "lodash.isnumber": "^3.0.3", 288 | "lodash.isplainobject": "^4.0.6", 289 | "lodash.isstring": "^4.0.1", 290 | "lodash.once": "^4.0.0", 291 | "ms": "^2.1.1", 292 | "semver": "^5.6.0" 293 | }, 294 | "dependencies": { 295 | "ms": { 296 | "version": "2.1.3", 297 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 298 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 299 | } 300 | } 301 | }, 302 | "jwa": { 303 | "version": "1.4.1", 304 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", 305 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", 306 | "requires": { 307 | "buffer-equal-constant-time": "1.0.1", 308 | "ecdsa-sig-formatter": "1.0.11", 309 | "safe-buffer": "^5.0.1" 310 | } 311 | }, 312 | "jws": { 313 | "version": "3.2.2", 314 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", 315 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", 316 | "requires": { 317 | "jwa": "^1.4.1", 318 | "safe-buffer": "^5.0.1" 319 | } 320 | }, 321 | "kareem": { 322 | "version": "2.3.2", 323 | "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", 324 | "integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ==" 325 | }, 326 | "lodash.includes": { 327 | "version": "4.3.0", 328 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", 329 | "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" 330 | }, 331 | "lodash.isboolean": { 332 | "version": "3.0.3", 333 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", 334 | "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" 335 | }, 336 | "lodash.isinteger": { 337 | "version": "4.0.4", 338 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", 339 | "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" 340 | }, 341 | "lodash.isnumber": { 342 | "version": "3.0.3", 343 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", 344 | "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" 345 | }, 346 | "lodash.isplainobject": { 347 | "version": "4.0.6", 348 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", 349 | "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" 350 | }, 351 | "lodash.isstring": { 352 | "version": "4.0.1", 353 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", 354 | "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" 355 | }, 356 | "lodash.once": { 357 | "version": "4.1.1", 358 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", 359 | "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" 360 | }, 361 | "media-typer": { 362 | "version": "0.3.0", 363 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 364 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 365 | }, 366 | "memory-pager": { 367 | "version": "1.5.0", 368 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", 369 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", 370 | "optional": true 371 | }, 372 | "merge-descriptors": { 373 | "version": "1.0.1", 374 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 375 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 376 | }, 377 | "methods": { 378 | "version": "1.1.2", 379 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 380 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 381 | }, 382 | "mime": { 383 | "version": "1.6.0", 384 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 385 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 386 | }, 387 | "mime-db": { 388 | "version": "1.44.0", 389 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", 390 | "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" 391 | }, 392 | "mime-types": { 393 | "version": "2.1.27", 394 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", 395 | "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", 396 | "requires": { 397 | "mime-db": "1.44.0" 398 | } 399 | }, 400 | "mongodb": { 401 | "version": "3.6.3", 402 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.3.tgz", 403 | "integrity": "sha512-rOZuR0QkodZiM+UbQE5kDsJykBqWi0CL4Ec2i1nrGrUI3KO11r6Fbxskqmq3JK2NH7aW4dcccBuUujAP0ERl5w==", 404 | "requires": { 405 | "bl": "^2.2.1", 406 | "bson": "^1.1.4", 407 | "denque": "^1.4.1", 408 | "require_optional": "^1.0.1", 409 | "safe-buffer": "^5.1.2", 410 | "saslprep": "^1.0.0" 411 | } 412 | }, 413 | "mongoose": { 414 | "version": "5.11.9", 415 | "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.11.9.tgz", 416 | "integrity": "sha512-lmG6R64jtGGxqtn88BkkY+v470LUfGgyTKUyjswQ5c01GNgQvxA0kQd8h+tm0hZb639hKNRxL9ZBQlLleUpuIQ==", 417 | "requires": { 418 | "@types/mongodb": "^3.5.27", 419 | "bson": "^1.1.4", 420 | "kareem": "2.3.2", 421 | "mongodb": "3.6.3", 422 | "mongoose-legacy-pluralize": "1.0.2", 423 | "mpath": "0.8.1", 424 | "mquery": "3.2.3", 425 | "ms": "2.1.2", 426 | "regexp-clone": "1.0.0", 427 | "safe-buffer": "5.2.1", 428 | "sift": "7.0.1", 429 | "sliced": "1.0.1" 430 | }, 431 | "dependencies": { 432 | "ms": { 433 | "version": "2.1.2", 434 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 435 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 436 | }, 437 | "safe-buffer": { 438 | "version": "5.2.1", 439 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 440 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 441 | } 442 | } 443 | }, 444 | "mongoose-legacy-pluralize": { 445 | "version": "1.0.2", 446 | "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", 447 | "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==" 448 | }, 449 | "mpath": { 450 | "version": "0.8.1", 451 | "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.1.tgz", 452 | "integrity": "sha512-norEinle9aFc05McBawVPwqgFZ7npkts9yu17ztIVLwPwO9rq0OTp89kGVTqvv5rNLMz96E5iWHpVORjI411vA==" 453 | }, 454 | "mquery": { 455 | "version": "3.2.3", 456 | "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.3.tgz", 457 | "integrity": "sha512-cIfbP4TyMYX+SkaQ2MntD+F2XbqaBHUYWk3j+kqdDztPWok3tgyssOZxMHMtzbV1w9DaSlvEea0Iocuro41A4g==", 458 | "requires": { 459 | "bluebird": "3.5.1", 460 | "debug": "3.1.0", 461 | "regexp-clone": "^1.0.0", 462 | "safe-buffer": "5.1.2", 463 | "sliced": "1.0.1" 464 | }, 465 | "dependencies": { 466 | "debug": { 467 | "version": "3.1.0", 468 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 469 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 470 | "requires": { 471 | "ms": "2.0.0" 472 | } 473 | } 474 | } 475 | }, 476 | "ms": { 477 | "version": "2.0.0", 478 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 479 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 480 | }, 481 | "negotiator": { 482 | "version": "0.6.2", 483 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 484 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 485 | }, 486 | "object-assign": { 487 | "version": "4.1.1", 488 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 489 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 490 | }, 491 | "on-finished": { 492 | "version": "2.3.0", 493 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 494 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 495 | "requires": { 496 | "ee-first": "1.1.1" 497 | } 498 | }, 499 | "parseurl": { 500 | "version": "1.3.3", 501 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 502 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 503 | }, 504 | "path-to-regexp": { 505 | "version": "0.1.7", 506 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 507 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 508 | }, 509 | "process-nextick-args": { 510 | "version": "2.0.1", 511 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 512 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 513 | }, 514 | "proxy-addr": { 515 | "version": "2.0.6", 516 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", 517 | "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", 518 | "requires": { 519 | "forwarded": "~0.1.2", 520 | "ipaddr.js": "1.9.1" 521 | } 522 | }, 523 | "qs": { 524 | "version": "6.7.0", 525 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 526 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 527 | }, 528 | "range-parser": { 529 | "version": "1.2.1", 530 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 531 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 532 | }, 533 | "raw-body": { 534 | "version": "2.4.0", 535 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 536 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 537 | "requires": { 538 | "bytes": "3.1.0", 539 | "http-errors": "1.7.2", 540 | "iconv-lite": "0.4.24", 541 | "unpipe": "1.0.0" 542 | } 543 | }, 544 | "readable-stream": { 545 | "version": "2.3.7", 546 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 547 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 548 | "requires": { 549 | "core-util-is": "~1.0.0", 550 | "inherits": "~2.0.3", 551 | "isarray": "~1.0.0", 552 | "process-nextick-args": "~2.0.0", 553 | "safe-buffer": "~5.1.1", 554 | "string_decoder": "~1.1.1", 555 | "util-deprecate": "~1.0.1" 556 | } 557 | }, 558 | "regexp-clone": { 559 | "version": "1.0.0", 560 | "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", 561 | "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==" 562 | }, 563 | "require_optional": { 564 | "version": "1.0.1", 565 | "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", 566 | "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", 567 | "requires": { 568 | "resolve-from": "^2.0.0", 569 | "semver": "^5.1.0" 570 | } 571 | }, 572 | "resolve-from": { 573 | "version": "2.0.0", 574 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", 575 | "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" 576 | }, 577 | "safe-buffer": { 578 | "version": "5.1.2", 579 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 580 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 581 | }, 582 | "safer-buffer": { 583 | "version": "2.1.2", 584 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 585 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 586 | }, 587 | "saslprep": { 588 | "version": "1.0.3", 589 | "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", 590 | "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", 591 | "optional": true, 592 | "requires": { 593 | "sparse-bitfield": "^3.0.3" 594 | } 595 | }, 596 | "semver": { 597 | "version": "5.7.1", 598 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 599 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 600 | }, 601 | "send": { 602 | "version": "0.17.1", 603 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 604 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 605 | "requires": { 606 | "debug": "2.6.9", 607 | "depd": "~1.1.2", 608 | "destroy": "~1.0.4", 609 | "encodeurl": "~1.0.2", 610 | "escape-html": "~1.0.3", 611 | "etag": "~1.8.1", 612 | "fresh": "0.5.2", 613 | "http-errors": "~1.7.2", 614 | "mime": "1.6.0", 615 | "ms": "2.1.1", 616 | "on-finished": "~2.3.0", 617 | "range-parser": "~1.2.1", 618 | "statuses": "~1.5.0" 619 | }, 620 | "dependencies": { 621 | "ms": { 622 | "version": "2.1.1", 623 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 624 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 625 | } 626 | } 627 | }, 628 | "serve-static": { 629 | "version": "1.14.1", 630 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 631 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 632 | "requires": { 633 | "encodeurl": "~1.0.2", 634 | "escape-html": "~1.0.3", 635 | "parseurl": "~1.3.3", 636 | "send": "0.17.1" 637 | } 638 | }, 639 | "setprototypeof": { 640 | "version": "1.1.1", 641 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 642 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 643 | }, 644 | "sift": { 645 | "version": "7.0.1", 646 | "resolved": "https://registry.npmjs.org/sift/-/sift-7.0.1.tgz", 647 | "integrity": "sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g==" 648 | }, 649 | "sliced": { 650 | "version": "1.0.1", 651 | "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", 652 | "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" 653 | }, 654 | "sparse-bitfield": { 655 | "version": "3.0.3", 656 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", 657 | "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", 658 | "optional": true, 659 | "requires": { 660 | "memory-pager": "^1.0.2" 661 | } 662 | }, 663 | "statuses": { 664 | "version": "1.5.0", 665 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 666 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 667 | }, 668 | "string_decoder": { 669 | "version": "1.1.1", 670 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 671 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 672 | "requires": { 673 | "safe-buffer": "~5.1.0" 674 | } 675 | }, 676 | "toidentifier": { 677 | "version": "1.0.0", 678 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 679 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 680 | }, 681 | "type-is": { 682 | "version": "1.6.18", 683 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 684 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 685 | "requires": { 686 | "media-typer": "0.3.0", 687 | "mime-types": "~2.1.24" 688 | } 689 | }, 690 | "unpipe": { 691 | "version": "1.0.0", 692 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 693 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 694 | }, 695 | "util-deprecate": { 696 | "version": "1.0.2", 697 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 698 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 699 | }, 700 | "utils-merge": { 701 | "version": "1.0.1", 702 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 703 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 704 | }, 705 | "vary": { 706 | "version": "1.1.2", 707 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 708 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 709 | } 710 | } 711 | } 712 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node .", 8 | "watch": "nodemon .", 9 | "debug": "node inspect .", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "body-parser": "^1.18.3", 17 | "cors": "^2.8.4", 18 | "dotenv": "^6.0.0", 19 | "express": "^4.16.3", 20 | "jsonwebtoken": "^8.3.0", 21 | "mongodb": "^3.1.4", 22 | "mongoose": "^5.2.14" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /server/src/data/User.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const { UserSchema } = require('./schemas/'); 3 | 4 | module.exports = mongoose.model('User', UserSchema); 5 | -------------------------------------------------------------------------------- /server/src/data/index.js: -------------------------------------------------------------------------------- 1 | const User = require('./User'); 2 | 3 | module.exports = { 4 | User 5 | }; 6 | -------------------------------------------------------------------------------- /server/src/data/schemas/User.js: -------------------------------------------------------------------------------- 1 | const { Schema } = require('mongoose'); 2 | 3 | const UserSchema = new Schema({ 4 | email: { 5 | type: String, 6 | required: true 7 | }, 8 | password: { 9 | type: String, 10 | required: true 11 | }, 12 | name: { 13 | type: String 14 | } 15 | }); 16 | 17 | module.exports = UserSchema; 18 | -------------------------------------------------------------------------------- /server/src/data/schemas/index.js: -------------------------------------------------------------------------------- 1 | const UserSchema = require('./User'); 2 | 3 | module.exports = { 4 | UserSchema 5 | }; 6 | -------------------------------------------------------------------------------- /server/src/logic/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mongoose = require('mongoose'); 4 | const { User } = require('./../data/'); 5 | 6 | const { validateEmail } = require('./../utils/validate-email'); 7 | 8 | const logic = { 9 | _validateStringField(field, value) { 10 | if (typeof field !== 'string' || !field.trim().length) 11 | throw Error(`${field} is not valid`); 12 | }, 13 | 14 | /** 15 | * 16 | * @param {*} value 17 | */ 18 | _validateEmail(email) { 19 | if (!validateEmail(email)) throw Error(`${email} is not a valid email`); 20 | }, 21 | 22 | /** 23 | * Register a user 24 | */ 25 | register(name, email, password) { 26 | return Promise.resolve() 27 | .then(() => { 28 | this._validateEmail(email); 29 | this._validateStringField('password', password); 30 | this._validateStringField('name', name); 31 | 32 | return User.findOne({ email }); 33 | }) 34 | .then(user => { 35 | if (user) 36 | throw Error(`User with email ${email} already exists`); 37 | 38 | return User.create({ email, password, name }); 39 | }) 40 | .then(({ email, name }) => ({ email, name })); 41 | }, 42 | 43 | /** 44 | * Authenticates a user 45 | */ 46 | authenticate(email, password) { 47 | return Promise.resolve() 48 | .then(() => { 49 | this._validateEmail(email); 50 | this._validateStringField('password', password); 51 | 52 | return User.findOne({ email, password }); 53 | }) 54 | .then(user => { 55 | if (!user) throw Error(`Wrong credentials`); 56 | 57 | return user; 58 | }); 59 | }, 60 | 61 | /** 62 | * Retrieves a user by its ID 63 | */ 64 | retrieveUserById(userId) { 65 | return Promise.resolve() 66 | .then(() => { 67 | this._validateStringField('userId', userId); 68 | 69 | return User.findById(userId).select('email name -_id'); 70 | }) 71 | .then(user => { 72 | if (!user) throw Error(`No data found for user ${userId}`); 73 | 74 | return user; 75 | }); 76 | } 77 | }; 78 | 79 | module.exports = logic; 80 | -------------------------------------------------------------------------------- /server/src/routes/index.js: -------------------------------------------------------------------------------- 1 | const userRouter = require('./userRouter'); 2 | 3 | module.exports = { 4 | userRouter 5 | }; 6 | -------------------------------------------------------------------------------- /server/src/routes/userRouter.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | 4 | const jwt = require('jsonwebtoken'); 5 | const jwtValidation = require('./../utils/jwt-validation'); 6 | 7 | const userRouter = express.Router(); 8 | 9 | const jsonBodyParser = bodyParser.json(); 10 | 11 | const logic = require('./../logic/'); 12 | 13 | const { 14 | env: { TOKEN_SECRET, TOKEN_EXP } 15 | } = process; 16 | 17 | const jwtValidator = jwtValidation(TOKEN_SECRET); 18 | 19 | // Register 20 | userRouter.post('/register', jsonBodyParser, (req, res) => { 21 | const { 22 | body: { name, email, password } 23 | } = req; 24 | 25 | logic 26 | .register(name, email, password) 27 | .then(user => 28 | res.status(201).json({ 29 | status: 'OK', 30 | message: 'User registered succesfully', 31 | user 32 | }) 33 | ) 34 | .catch(({ message }) => 35 | res.status(400).json({ status: 'KO', message }) 36 | ); 37 | }); 38 | 39 | // Authenticate 40 | userRouter.post('/authenticate', jsonBodyParser, (req, res) => { 41 | const { 42 | body: { email, password } 43 | } = req; 44 | 45 | logic 46 | .authenticate(email, password) 47 | .then(({ _id: id, email }) => { 48 | const token = jwt.sign({ id }, TOKEN_SECRET, { 49 | expiresIn: TOKEN_EXP 50 | }); 51 | 52 | res.json({ 53 | status: 'OK', 54 | message: 'User authenticated succesfully', 55 | user: { id, email }, 56 | token 57 | }); 58 | }) 59 | .catch(({ message }) => 60 | res.status(403).json({ status: 'KO', message }) 61 | ); 62 | }); 63 | 64 | // Retrieve user data 65 | userRouter.get('/user/:userId', jwtValidator, (req, res) => { 66 | const { userId } = req.params; 67 | 68 | logic 69 | .retrieveUserById(userId) 70 | .then(user => 71 | res.json({ 72 | status: 'OK', 73 | message: 'User data retrieved correctly', 74 | user 75 | }) 76 | ) 77 | .catch(({ message }) => 78 | res.status(404).json({ status: 'KO', message }) 79 | ); 80 | }); 81 | 82 | module.exports = userRouter; 83 | -------------------------------------------------------------------------------- /server/src/utils/jwt-validation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const jwt = require('jsonwebtoken'); 4 | 5 | let _secret = 'NO-SECRET'; 6 | 7 | function jwtValidator(req, res, next) { 8 | let message; 9 | 10 | const userId = req.params.userId; 11 | 12 | try { 13 | const auth = req.get('authorization'); 14 | 15 | const token = auth.split(' ')[1]; 16 | 17 | const { id } = jwt.verify(token, _secret); 18 | 19 | if (userId && id !== userId) 20 | message = `user id ${userId} does not match token user id ${id}`; 21 | 22 | if (!message) return next(); 23 | } catch (err) { 24 | message = 'invalid token'; 25 | } 26 | 27 | res.status(401); 28 | res.json({ status: 'KO', error: message }); 29 | } 30 | 31 | module.exports = function(secret) { 32 | _secret = secret; 33 | 34 | return jwtValidator; 35 | }; 36 | -------------------------------------------------------------------------------- /server/src/utils/permission-validation.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken'); 2 | 3 | let _secret = 'NO-SECRET'; 4 | 5 | const permitValidator = (...allowed) => { 6 | const isAllowed = role => allowed.indexOf(role) > -1; 7 | 8 | return (req, res, next) => { 9 | const auth = req.get('authorization'); 10 | 11 | const token = auth.split(' ')[1]; 12 | 13 | const { role } = jwt.verify(token, _secret); 14 | 15 | if (isAllowed(role[0])) next(); 16 | else { 17 | res.status(403).json({ 18 | status: 'KO', 19 | error: 'No tienes permisos para realizar esta acción.' 20 | }); 21 | } 22 | }; 23 | }; 24 | 25 | module.exports = function(secret) { 26 | _secret = secret; 27 | 28 | return permitValidator; 29 | }; 30 | -------------------------------------------------------------------------------- /server/src/utils/validate-email.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const validateEmail = email => { 4 | const regex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; 5 | 6 | return regex.test(String(email).toLowerCase()); 7 | }; 8 | 9 | module.exports = { validateEmail }; 10 | -------------------------------------------------------------------------------- /server/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | accepts@~1.3.7: 6 | version "1.3.7" 7 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" 8 | integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== 9 | dependencies: 10 | mime-types "~2.1.24" 11 | negotiator "0.6.2" 12 | 13 | array-flatten@1.1.1: 14 | version "1.1.1" 15 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 16 | integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= 17 | 18 | bl@^2.2.0: 19 | version "2.2.1" 20 | resolved "https://registry.yarnpkg.com/bl/-/bl-2.2.1.tgz#8c11a7b730655c5d56898cdc871224f40fd901d5" 21 | integrity sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g== 22 | dependencies: 23 | readable-stream "^2.3.5" 24 | safe-buffer "^5.1.1" 25 | 26 | bluebird@3.5.1: 27 | version "3.5.1" 28 | resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" 29 | integrity sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA== 30 | 31 | body-parser@1.19.0, body-parser@^1.18.3: 32 | version "1.19.0" 33 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" 34 | integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== 35 | dependencies: 36 | bytes "3.1.0" 37 | content-type "~1.0.4" 38 | debug "2.6.9" 39 | depd "~1.1.2" 40 | http-errors "1.7.2" 41 | iconv-lite "0.4.24" 42 | on-finished "~2.3.0" 43 | qs "6.7.0" 44 | raw-body "2.4.0" 45 | type-is "~1.6.17" 46 | 47 | bson@^1.1.1, bson@~1.1.1: 48 | version "1.1.4" 49 | resolved "https://registry.yarnpkg.com/bson/-/bson-1.1.4.tgz#f76870d799f15b854dffb7ee32f0a874797f7e89" 50 | integrity sha512-S/yKGU1syOMzO86+dGpg2qGoDL0zvzcb262G+gqEy6TgP6rt6z6qxSFX/8X6vLC91P7G7C3nLs0+bvDzmvBA3Q== 51 | 52 | buffer-equal-constant-time@1.0.1: 53 | version "1.0.1" 54 | resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" 55 | integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= 56 | 57 | bytes@3.1.0: 58 | version "3.1.0" 59 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" 60 | integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== 61 | 62 | content-disposition@0.5.3: 63 | version "0.5.3" 64 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" 65 | integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== 66 | dependencies: 67 | safe-buffer "5.1.2" 68 | 69 | content-type@~1.0.4: 70 | version "1.0.4" 71 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" 72 | integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== 73 | 74 | cookie-signature@1.0.6: 75 | version "1.0.6" 76 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 77 | integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= 78 | 79 | cookie@0.4.0: 80 | version "0.4.0" 81 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" 82 | integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== 83 | 84 | core-util-is@~1.0.0: 85 | version "1.0.2" 86 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 87 | integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= 88 | 89 | cors@^2.8.4: 90 | version "2.8.5" 91 | resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" 92 | integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== 93 | dependencies: 94 | object-assign "^4" 95 | vary "^1" 96 | 97 | debug@2.6.9: 98 | version "2.6.9" 99 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 100 | integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== 101 | dependencies: 102 | ms "2.0.0" 103 | 104 | debug@3.1.0: 105 | version "3.1.0" 106 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" 107 | integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== 108 | dependencies: 109 | ms "2.0.0" 110 | 111 | denque@^1.4.1: 112 | version "1.4.1" 113 | resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.1.tgz#6744ff7641c148c3f8a69c307e51235c1f4a37cf" 114 | integrity sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ== 115 | 116 | depd@~1.1.2: 117 | version "1.1.2" 118 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" 119 | integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= 120 | 121 | destroy@~1.0.4: 122 | version "1.0.4" 123 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" 124 | integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= 125 | 126 | dotenv@^6.0.0: 127 | version "6.2.0" 128 | resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-6.2.0.tgz#941c0410535d942c8becf28d3f357dbd9d476064" 129 | integrity sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w== 130 | 131 | ecdsa-sig-formatter@1.0.11: 132 | version "1.0.11" 133 | resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" 134 | integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== 135 | dependencies: 136 | safe-buffer "^5.0.1" 137 | 138 | ee-first@1.1.1: 139 | version "1.1.1" 140 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 141 | integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= 142 | 143 | encodeurl@~1.0.2: 144 | version "1.0.2" 145 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" 146 | integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= 147 | 148 | escape-html@~1.0.3: 149 | version "1.0.3" 150 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 151 | integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= 152 | 153 | etag@~1.8.1: 154 | version "1.8.1" 155 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" 156 | integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= 157 | 158 | express@^4.16.3: 159 | version "4.17.1" 160 | resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" 161 | integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== 162 | dependencies: 163 | accepts "~1.3.7" 164 | array-flatten "1.1.1" 165 | body-parser "1.19.0" 166 | content-disposition "0.5.3" 167 | content-type "~1.0.4" 168 | cookie "0.4.0" 169 | cookie-signature "1.0.6" 170 | debug "2.6.9" 171 | depd "~1.1.2" 172 | encodeurl "~1.0.2" 173 | escape-html "~1.0.3" 174 | etag "~1.8.1" 175 | finalhandler "~1.1.2" 176 | fresh "0.5.2" 177 | merge-descriptors "1.0.1" 178 | methods "~1.1.2" 179 | on-finished "~2.3.0" 180 | parseurl "~1.3.3" 181 | path-to-regexp "0.1.7" 182 | proxy-addr "~2.0.5" 183 | qs "6.7.0" 184 | range-parser "~1.2.1" 185 | safe-buffer "5.1.2" 186 | send "0.17.1" 187 | serve-static "1.14.1" 188 | setprototypeof "1.1.1" 189 | statuses "~1.5.0" 190 | type-is "~1.6.18" 191 | utils-merge "1.0.1" 192 | vary "~1.1.2" 193 | 194 | finalhandler@~1.1.2: 195 | version "1.1.2" 196 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" 197 | integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== 198 | dependencies: 199 | debug "2.6.9" 200 | encodeurl "~1.0.2" 201 | escape-html "~1.0.3" 202 | on-finished "~2.3.0" 203 | parseurl "~1.3.3" 204 | statuses "~1.5.0" 205 | unpipe "~1.0.0" 206 | 207 | forwarded@~0.1.2: 208 | version "0.1.2" 209 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" 210 | integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= 211 | 212 | fresh@0.5.2: 213 | version "0.5.2" 214 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" 215 | integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= 216 | 217 | http-errors@1.7.2: 218 | version "1.7.2" 219 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" 220 | integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== 221 | dependencies: 222 | depd "~1.1.2" 223 | inherits "2.0.3" 224 | setprototypeof "1.1.1" 225 | statuses ">= 1.5.0 < 2" 226 | toidentifier "1.0.0" 227 | 228 | http-errors@~1.7.2: 229 | version "1.7.3" 230 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" 231 | integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== 232 | dependencies: 233 | depd "~1.1.2" 234 | inherits "2.0.4" 235 | setprototypeof "1.1.1" 236 | statuses ">= 1.5.0 < 2" 237 | toidentifier "1.0.0" 238 | 239 | iconv-lite@0.4.24: 240 | version "0.4.24" 241 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" 242 | integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== 243 | dependencies: 244 | safer-buffer ">= 2.1.2 < 3" 245 | 246 | inherits@2.0.3: 247 | version "2.0.3" 248 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 249 | integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= 250 | 251 | inherits@2.0.4, inherits@~2.0.3: 252 | version "2.0.4" 253 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 254 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 255 | 256 | ipaddr.js@1.9.1: 257 | version "1.9.1" 258 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" 259 | integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== 260 | 261 | isarray@~1.0.0: 262 | version "1.0.0" 263 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 264 | integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= 265 | 266 | jsonwebtoken@^8.3.0: 267 | version "8.5.1" 268 | resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" 269 | integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== 270 | dependencies: 271 | jws "^3.2.2" 272 | lodash.includes "^4.3.0" 273 | lodash.isboolean "^3.0.3" 274 | lodash.isinteger "^4.0.4" 275 | lodash.isnumber "^3.0.3" 276 | lodash.isplainobject "^4.0.6" 277 | lodash.isstring "^4.0.1" 278 | lodash.once "^4.0.0" 279 | ms "^2.1.1" 280 | semver "^5.6.0" 281 | 282 | jwa@^1.4.1: 283 | version "1.4.1" 284 | resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" 285 | integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== 286 | dependencies: 287 | buffer-equal-constant-time "1.0.1" 288 | ecdsa-sig-formatter "1.0.11" 289 | safe-buffer "^5.0.1" 290 | 291 | jws@^3.2.2: 292 | version "3.2.2" 293 | resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" 294 | integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== 295 | dependencies: 296 | jwa "^1.4.1" 297 | safe-buffer "^5.0.1" 298 | 299 | kareem@2.3.1: 300 | version "2.3.1" 301 | resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.3.1.tgz#def12d9c941017fabfb00f873af95e9c99e1be87" 302 | integrity sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw== 303 | 304 | lodash.includes@^4.3.0: 305 | version "4.3.0" 306 | resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" 307 | integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= 308 | 309 | lodash.isboolean@^3.0.3: 310 | version "3.0.3" 311 | resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" 312 | integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= 313 | 314 | lodash.isinteger@^4.0.4: 315 | version "4.0.4" 316 | resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" 317 | integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= 318 | 319 | lodash.isnumber@^3.0.3: 320 | version "3.0.3" 321 | resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" 322 | integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= 323 | 324 | lodash.isplainobject@^4.0.6: 325 | version "4.0.6" 326 | resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" 327 | integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= 328 | 329 | lodash.isstring@^4.0.1: 330 | version "4.0.1" 331 | resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" 332 | integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= 333 | 334 | lodash.once@^4.0.0: 335 | version "4.1.1" 336 | resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" 337 | integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= 338 | 339 | media-typer@0.3.0: 340 | version "0.3.0" 341 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 342 | integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= 343 | 344 | memory-pager@^1.0.2: 345 | version "1.5.0" 346 | resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5" 347 | integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg== 348 | 349 | merge-descriptors@1.0.1: 350 | version "1.0.1" 351 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 352 | integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= 353 | 354 | methods@~1.1.2: 355 | version "1.1.2" 356 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 357 | integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= 358 | 359 | mime-db@1.43.0: 360 | version "1.43.0" 361 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" 362 | integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== 363 | 364 | mime-types@~2.1.24: 365 | version "2.1.26" 366 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" 367 | integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== 368 | dependencies: 369 | mime-db "1.43.0" 370 | 371 | mime@1.6.0: 372 | version "1.6.0" 373 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" 374 | integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== 375 | 376 | mongodb@3.5.5, mongodb@^3.1.4: 377 | version "3.5.5" 378 | resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-3.5.5.tgz#1334c3e5a384469ac7ef0dea69d59acc829a496a" 379 | integrity sha512-GCjDxR3UOltDq00Zcpzql6dQo1sVry60OXJY3TDmFc2SWFY6c8Gn1Ardidc5jDirvJrx2GC3knGOImKphbSL3A== 380 | dependencies: 381 | bl "^2.2.0" 382 | bson "^1.1.1" 383 | denque "^1.4.1" 384 | require_optional "^1.0.1" 385 | safe-buffer "^5.1.2" 386 | optionalDependencies: 387 | saslprep "^1.0.0" 388 | 389 | mongoose-legacy-pluralize@1.0.2: 390 | version "1.0.2" 391 | resolved "https://registry.yarnpkg.com/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz#3ba9f91fa507b5186d399fb40854bff18fb563e4" 392 | integrity sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ== 393 | 394 | mongoose@^5.2.14: 395 | version "5.9.7" 396 | resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-5.9.7.tgz#03c581860d0e2f60f6008f9457ab0c2905609875" 397 | integrity sha512-WJOBh9WMvivqBK8my9HFtSzSySKdUxJPNGAwswEakAasWUcPXJl3yHMtZ4ngGnKbwTT9KnAr75xamlt/PouR9w== 398 | dependencies: 399 | bson "~1.1.1" 400 | kareem "2.3.1" 401 | mongodb "3.5.5" 402 | mongoose-legacy-pluralize "1.0.2" 403 | mpath "0.6.0" 404 | mquery "3.2.2" 405 | ms "2.1.2" 406 | regexp-clone "1.0.0" 407 | safe-buffer "5.1.2" 408 | sift "7.0.1" 409 | sliced "1.0.1" 410 | 411 | mpath@0.6.0: 412 | version "0.6.0" 413 | resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.6.0.tgz#aa922029fca4f0f641f360e74c5c1b6a4c47078e" 414 | integrity sha512-i75qh79MJ5Xo/sbhxrDrPSEG0H/mr1kcZXJ8dH6URU5jD/knFxCVqVC/gVSW7GIXL/9hHWlT9haLbCXWOll3qw== 415 | 416 | mquery@3.2.2: 417 | version "3.2.2" 418 | resolved "https://registry.yarnpkg.com/mquery/-/mquery-3.2.2.tgz#e1383a3951852ce23e37f619a9b350f1fb3664e7" 419 | integrity sha512-XB52992COp0KP230I3qloVUbkLUxJIu328HBP2t2EsxSFtf4W1HPSOBWOXf1bqxK4Xbb66lfMJ+Bpfd9/yZE1Q== 420 | dependencies: 421 | bluebird "3.5.1" 422 | debug "3.1.0" 423 | regexp-clone "^1.0.0" 424 | safe-buffer "5.1.2" 425 | sliced "1.0.1" 426 | 427 | ms@2.0.0: 428 | version "2.0.0" 429 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 430 | integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= 431 | 432 | ms@2.1.1: 433 | version "2.1.1" 434 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" 435 | integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== 436 | 437 | ms@2.1.2, ms@^2.1.1: 438 | version "2.1.2" 439 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 440 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 441 | 442 | negotiator@0.6.2: 443 | version "0.6.2" 444 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" 445 | integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== 446 | 447 | object-assign@^4: 448 | version "4.1.1" 449 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 450 | integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= 451 | 452 | on-finished@~2.3.0: 453 | version "2.3.0" 454 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" 455 | integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= 456 | dependencies: 457 | ee-first "1.1.1" 458 | 459 | parseurl@~1.3.3: 460 | version "1.3.3" 461 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" 462 | integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== 463 | 464 | path-to-regexp@0.1.7: 465 | version "0.1.7" 466 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 467 | integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= 468 | 469 | process-nextick-args@~2.0.0: 470 | version "2.0.1" 471 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" 472 | integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== 473 | 474 | proxy-addr@~2.0.5: 475 | version "2.0.6" 476 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" 477 | integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw== 478 | dependencies: 479 | forwarded "~0.1.2" 480 | ipaddr.js "1.9.1" 481 | 482 | qs@6.7.0: 483 | version "6.7.0" 484 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" 485 | integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== 486 | 487 | range-parser@~1.2.1: 488 | version "1.2.1" 489 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" 490 | integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== 491 | 492 | raw-body@2.4.0: 493 | version "2.4.0" 494 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" 495 | integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== 496 | dependencies: 497 | bytes "3.1.0" 498 | http-errors "1.7.2" 499 | iconv-lite "0.4.24" 500 | unpipe "1.0.0" 501 | 502 | readable-stream@^2.3.5: 503 | version "2.3.7" 504 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" 505 | integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== 506 | dependencies: 507 | core-util-is "~1.0.0" 508 | inherits "~2.0.3" 509 | isarray "~1.0.0" 510 | process-nextick-args "~2.0.0" 511 | safe-buffer "~5.1.1" 512 | string_decoder "~1.1.1" 513 | util-deprecate "~1.0.1" 514 | 515 | regexp-clone@1.0.0, regexp-clone@^1.0.0: 516 | version "1.0.0" 517 | resolved "https://registry.yarnpkg.com/regexp-clone/-/regexp-clone-1.0.0.tgz#222db967623277056260b992626354a04ce9bf63" 518 | integrity sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw== 519 | 520 | require_optional@^1.0.1: 521 | version "1.0.1" 522 | resolved "https://registry.yarnpkg.com/require_optional/-/require_optional-1.0.1.tgz#4cf35a4247f64ca3df8c2ef208cc494b1ca8fc2e" 523 | integrity sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g== 524 | dependencies: 525 | resolve-from "^2.0.0" 526 | semver "^5.1.0" 527 | 528 | resolve-from@^2.0.0: 529 | version "2.0.0" 530 | resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57" 531 | integrity sha1-lICrIOlP+h2egKgEx+oUdhGWa1c= 532 | 533 | safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: 534 | version "5.1.2" 535 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 536 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== 537 | 538 | safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2: 539 | version "5.2.1" 540 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 541 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 542 | 543 | "safer-buffer@>= 2.1.2 < 3": 544 | version "2.1.2" 545 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 546 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== 547 | 548 | saslprep@^1.0.0: 549 | version "1.0.3" 550 | resolved "https://registry.yarnpkg.com/saslprep/-/saslprep-1.0.3.tgz#4c02f946b56cf54297e347ba1093e7acac4cf226" 551 | integrity sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag== 552 | dependencies: 553 | sparse-bitfield "^3.0.3" 554 | 555 | semver@^5.1.0, semver@^5.6.0: 556 | version "5.7.1" 557 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" 558 | integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== 559 | 560 | send@0.17.1: 561 | version "0.17.1" 562 | resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" 563 | integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== 564 | dependencies: 565 | debug "2.6.9" 566 | depd "~1.1.2" 567 | destroy "~1.0.4" 568 | encodeurl "~1.0.2" 569 | escape-html "~1.0.3" 570 | etag "~1.8.1" 571 | fresh "0.5.2" 572 | http-errors "~1.7.2" 573 | mime "1.6.0" 574 | ms "2.1.1" 575 | on-finished "~2.3.0" 576 | range-parser "~1.2.1" 577 | statuses "~1.5.0" 578 | 579 | serve-static@1.14.1: 580 | version "1.14.1" 581 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" 582 | integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== 583 | dependencies: 584 | encodeurl "~1.0.2" 585 | escape-html "~1.0.3" 586 | parseurl "~1.3.3" 587 | send "0.17.1" 588 | 589 | setprototypeof@1.1.1: 590 | version "1.1.1" 591 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" 592 | integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== 593 | 594 | sift@7.0.1: 595 | version "7.0.1" 596 | resolved "https://registry.yarnpkg.com/sift/-/sift-7.0.1.tgz#47d62c50b159d316f1372f8b53f9c10cd21a4b08" 597 | integrity sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g== 598 | 599 | sliced@1.0.1: 600 | version "1.0.1" 601 | resolved "https://registry.yarnpkg.com/sliced/-/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41" 602 | integrity sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E= 603 | 604 | sparse-bitfield@^3.0.3: 605 | version "3.0.3" 606 | resolved "https://registry.yarnpkg.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11" 607 | integrity sha1-/0rm5oZWBWuks+eSqzM004JzyhE= 608 | dependencies: 609 | memory-pager "^1.0.2" 610 | 611 | "statuses@>= 1.5.0 < 2", statuses@~1.5.0: 612 | version "1.5.0" 613 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" 614 | integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= 615 | 616 | string_decoder@~1.1.1: 617 | version "1.1.1" 618 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" 619 | integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== 620 | dependencies: 621 | safe-buffer "~5.1.0" 622 | 623 | toidentifier@1.0.0: 624 | version "1.0.0" 625 | resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" 626 | integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== 627 | 628 | type-is@~1.6.17, type-is@~1.6.18: 629 | version "1.6.18" 630 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" 631 | integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== 632 | dependencies: 633 | media-typer "0.3.0" 634 | mime-types "~2.1.24" 635 | 636 | unpipe@1.0.0, unpipe@~1.0.0: 637 | version "1.0.0" 638 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 639 | integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= 640 | 641 | util-deprecate@~1.0.1: 642 | version "1.0.2" 643 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 644 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= 645 | 646 | utils-merge@1.0.1: 647 | version "1.0.1" 648 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" 649 | integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= 650 | 651 | vary@^1, vary@~1.1.2: 652 | version "1.1.2" 653 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" 654 | integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= 655 | --------------------------------------------------------------------------------