├── .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 |
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 |

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 |
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 |
--------------------------------------------------------------------------------