├── .gitignore
├── LICENSE
├── README.md
├── client
├── .env.example
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
├── setup.js
├── src
│ ├── components
│ │ ├── About.js
│ │ ├── Dashboard.css
│ │ ├── Dashboard.js
│ │ ├── Login.js
│ │ ├── SocialButtonList.css
│ │ ├── SocialButtonList.js
│ │ ├── SocialProfileList.css
│ │ └── SocialProfileList.js
│ ├── containers
│ │ ├── App.css
│ │ ├── App.js
│ │ ├── Layout.css
│ │ ├── Layout.js
│ │ └── withAuthentication.js
│ ├── firebase
│ │ ├── auth.js
│ │ ├── firebase.js
│ │ └── index.js
│ ├── index.css
│ ├── index.js
│ ├── initialButtonList.js
│ └── registerServiceWorker.js
└── yarn.lock
├── package.json
├── server.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | build
11 |
12 | # misc
13 | /misc
14 | /Tutorial
15 | Notes.md
16 | .DS_Store
17 | .env
18 | .env.local
19 | .env.development.local
20 | .env.test.local
21 | .env.production.local
22 |
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2018 Esau Silva
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # OAuth Authentication with Firebase
2 |
3 | > React app to showcase OAuth Authentication with GitHub, Twitter and Facebook using Firebase
4 |
5 | This app has been bootstrapped with Create React App so you will need `create-react-app` installed.
6 |
7 | To install
8 |
9 | ```
10 | $ npm install -g create-react-app
11 | ```
12 |
13 | ## Usage
14 |
15 | **Install dependencies**
16 |
17 | ```
18 | $ yarn
19 | $ cd client
20 | $ yarn
21 | $ cd ..
22 | ```
23 |
24 | **Run development server**
25 |
26 | - From root directory
27 |
28 | ```
29 | $ yarn dev:client
30 | ```
31 |
32 | - From client directory
33 |
34 | ```
35 | $ yarn start
36 | ```
37 |
38 | **Production**
39 |
40 | ```
41 | $ dev:server
42 | ```
43 |
44 | This will create the production build and Node will serve the app, then you can visit http://localhost:5000/
45 |
46 | ## Setup
47 |
48 | First create the required `.env` file, run
49 |
50 | ```
51 | $ cd client && yarn setup
52 | ```
53 |
54 | Before we begin working on the React app, we will need to create a project in Firebase, then setup new apps on GitHub, Twitter and Facebook.
55 |
56 | Follow the tutorial for detailed step-by-step guide.
57 |
58 | [https://esausilva.com/2018/08/13/react-oauth-authentication-with-firebase-tutorial/](https://esausilva.com/2018/08/13/react-oauth-authentication-with-firebase-tutorial/)
59 |
60 | ## Preview
61 |
62 | 
63 |
64 | ## Giving Back
65 |
66 | If you would like to support my work and the time I put into making tutorials, consider getting me a coffee by clicking on the image below. I would really appreciate it!
67 |
68 | [](https://www.buymeacoffee.com/esausilva)
69 |
70 | -Esau
71 |
--------------------------------------------------------------------------------
/client/.env.example:
--------------------------------------------------------------------------------
1 | REACT_APP_FIREBASE_API_KEY=AI...Qo
2 | REACT_APP_FIREBASE_AUTH_DOMAIN=app-name.firebaseapp.com
3 | REACT_APP_FIREBASE_DATABASE_URL=https://app-name.firebaseio.com
4 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-firebase-oauth",
3 | "version": "0.1.0",
4 | "author": "Esau Silva <@_esausilva>",
5 | "license": "MIT",
6 | "scripts": {
7 | "setup": "node setup.js",
8 | "start": "react-scripts start",
9 | "build": "react-scripts build",
10 | "test": "react-scripts test --env=jsdom",
11 | "eject": "react-scripts eject"
12 | },
13 | "dependencies": {
14 | "firebase": "^7.14.0",
15 | "prop-types": "^15.7.2",
16 | "react": "^16.13.1",
17 | "react-delay": "^0.1.0",
18 | "react-dom": "^16.13.1",
19 | "react-router-dom": "^5.1.2",
20 | "react-scripts": "3.4.1"
21 | },
22 | "repository": {
23 | "type": "git",
24 | "url": "https://github.com/esausilva/react-firebase-oauth.git"
25 | },
26 | "bugs": {
27 | "url": "https://github.com/esausilva/react-firebase-oauth/issues"
28 | },
29 | "keywords": [
30 | "react",
31 | "firebase",
32 | "github",
33 | "twitter",
34 | "facebook",
35 | "authentication",
36 | "oauth"
37 | ],
38 | "browserslist": {
39 | "production": [
40 | ">0.2%",
41 | "not dead",
42 | "not op_mini all"
43 | ],
44 | "development": [
45 | "last 1 chrome version",
46 | "last 1 firefox version",
47 | "last 1 safari version"
48 | ]
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/esausilva/react-firebase-oauth/74eb84d05b917aab2a6e2203b804d765a9b38c3c/client/public/favicon.ico
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/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 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/client/setup.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Copies the contents of .env.example to .env
4 | const fs = require('fs');
5 | fs.createReadStream('.env.example').pipe(fs.createWriteStream('.env'));
6 |
--------------------------------------------------------------------------------
/client/src/components/About.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Layout from '../containers/Layout';
4 |
5 | const About = () => {
6 | return (
7 |
8 | About
9 |
10 | Bacon ipsum dolor amet tail landjaeger corned beef chuck hamburger,
11 | salami strip steak. Pancetta kielbasa ham hock andouille. Tail cupim
12 | burgdoggen salami bacon jerky shankle strip steak turkey. Drumstick
13 | shoulder pork loin, filet mignon cupim alcatra tongue jowl. Cupim
14 | tenderloin rump t-bone. Picanha turducken short loin jowl, landjaeger
15 | shoulder t-bone buffalo spare ribs salami pastrami tri-tip ground round
16 | alcatra.
17 |
18 |
19 | );
20 | };
21 |
22 | export default About;
23 |
--------------------------------------------------------------------------------
/client/src/components/Dashboard.css:
--------------------------------------------------------------------------------
1 | .btn__logout {
2 | width: 70px;
3 | height: 33px;
4 | font-size: 0.7rem;
5 | background-color: rgb(172, 43, 43);
6 | }
7 |
8 | .btn__logout:hover {
9 | background-color: rgb(197, 63, 63);
10 | }
11 |
--------------------------------------------------------------------------------
/client/src/components/Dashboard.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import buttonList from '../initialButtonList';
5 | import Layout from '../containers/Layout';
6 | import SocialButtonList from './SocialButtonList';
7 | import SocialProfileList from './SocialProfileList';
8 | import { auth } from '../firebase';
9 |
10 | import './Dashboard.css';
11 |
12 | class Dashboard extends Component {
13 | static propTypes = {
14 | providerData: PropTypes.arrayOf(PropTypes.object).isRequired
15 | };
16 |
17 | // static defaultProps = {
18 | // providerData: []
19 | // };
20 |
21 | state = {
22 | buttonList: buttonList,
23 | providerData: this.props.providerData
24 | };
25 |
26 | componentDidMount() {
27 | this.updateProviders(this.state.providerData);
28 | }
29 |
30 | handleCurrentProviders = providerData => {
31 | this.updateProviders(providerData);
32 | };
33 |
34 | /**
35 | * Updates visibility of the Social Buttons and Social Profiles
36 | */
37 | updateProviders = providerData => {
38 | let buttonList = { ...this.state.buttonList };
39 |
40 | providerData.forEach(provider => {
41 | const providerName = provider.providerId.split('.')[0];
42 | buttonList = this.updateButtonList(buttonList, providerName, false);
43 | });
44 |
45 | this.setState({ buttonList, providerData });
46 | };
47 |
48 | /**
49 | * Unlinks a provider and if there are no more providers associated
50 | * with the account, delete the user account from Firebase
51 | */
52 | handleUnlinkedProvider = (providerName, providerData) => {
53 | if (providerData.length < 1) {
54 | auth
55 | .getAuth()
56 | .currentUser.delete()
57 | .then(() => console.log('User Deleted'))
58 | .catch(() => console.error('Error deleting user'));
59 | }
60 |
61 | let buttonList = { ...this.state.buttonList };
62 | buttonList = this.updateButtonList(buttonList, providerName, true);
63 |
64 | this.setState({ buttonList, providerData });
65 | };
66 |
67 | updateButtonList = (buttonList, providerName, visible) => ({
68 | ...buttonList,
69 | [providerName]: {
70 | ...buttonList[providerName],
71 | visible
72 | }
73 | });
74 |
75 | render() {
76 | return (
77 |
78 | Secure Area
79 |
84 |
85 | Connect Other Social Accounts
86 |
87 |
92 |
98 |
99 | );
100 | }
101 | }
102 |
103 | export default Dashboard;
104 |
--------------------------------------------------------------------------------
/client/src/components/Login.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | import buttonList from '../initialButtonList';
5 | import Layout from '../containers/Layout';
6 | import SocialButtonList from './SocialButtonList';
7 | import { auth } from '../firebase';
8 |
9 | class Login extends Component {
10 | /**
11 | * Send the user to 'Dashboard' if is authenticated
12 | */
13 | componentDidMount() {
14 | auth.getAuth().onAuthStateChanged(user => {
15 | if (user) {
16 | this.props.history.push('/dashboard');
17 | }
18 | });
19 | }
20 |
21 | render() {
22 | return (
23 |
24 | Connect With
25 |
26 | About
27 |
28 | );
29 | }
30 | }
31 |
32 | export default Login;
33 |
--------------------------------------------------------------------------------
/client/src/components/SocialButtonList.css:
--------------------------------------------------------------------------------
1 | .btn__social--list {
2 | display: flex;
3 | flex-wrap: wrap;
4 | justify-content: center;
5 | }
6 |
7 | .btn__social {
8 | width: 150px;
9 | }
10 |
11 | .btn--github {
12 | background-color: #1b1c1d;
13 | }
14 |
15 | .btn--github:hover {
16 | background-color: #232425;
17 | }
18 |
19 | .btn--twitter {
20 | background-color: #55acee;
21 | }
22 |
23 | .btn--twitter:hover {
24 | background-color: rgb(67, 158, 228);
25 | }
26 |
27 | .btn--facebook {
28 | background-color: #3b5998;
29 | }
30 |
31 | .btn--facebook:hover {
32 | background-color: rgb(45, 77, 146);
33 | }
34 |
--------------------------------------------------------------------------------
/client/src/components/SocialButtonList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { withRouter } from 'react-router-dom';
4 |
5 | import './SocialButtonList.css';
6 |
7 | const propTypes = {
8 | buttonList: PropTypes.shape({
9 | github: PropTypes.shape({
10 | visible: PropTypes.bool.isRequired,
11 | provider: PropTypes.func.isRequired
12 | }),
13 | twitter: PropTypes.shape({
14 | visible: PropTypes.bool.isRequired,
15 | provider: PropTypes.func.isRequired
16 | }),
17 | facebook: PropTypes.shape({
18 | visible: PropTypes.bool.isRequired,
19 | provider: PropTypes.func.isRequired
20 | })
21 | }).isRequired,
22 | auth: PropTypes.func.isRequired,
23 | currentProviders: PropTypes.func
24 | };
25 |
26 | const defaultProps = {
27 | currentProviders: null
28 | };
29 |
30 | const SocialButtonList = ({ history, buttonList, auth, currentProviders }) => {
31 | /**
32 | * Handles successfull authentication.
33 | * 'currentProviders' is null when the user is in Login page
34 | */
35 | const authHandler = authData => {
36 | if (authData) {
37 | if (currentProviders === null) {
38 | history.push('/dashboard');
39 | } else {
40 | currentProviders(authData.user.providerData);
41 | }
42 | } else {
43 | console.error('Error authenticating');
44 | }
45 | };
46 |
47 | /**
48 | * Authenticates the user with a social media provider.
49 | * Either creates a new user account in Firebase or links
50 | * a different provider to the same user account
51 | */
52 | const authenticate = (e, provider) => {
53 | const providerOAuth = buttonList[provider].provider();
54 |
55 | if (!auth().currentUser) {
56 | auth()
57 | .signInWithPopup(providerOAuth)
58 | .then(authHandler)
59 | .catch(err => console.error(err));
60 | } else {
61 | auth()
62 | .currentUser.linkWithPopup(providerOAuth)
63 | .then(authHandler)
64 | .catch(err => console.error(err));
65 | }
66 | };
67 |
68 | const renderButtonList = provder => {
69 | const visible = buttonList[provder].visible;
70 |
71 | return (
72 |
79 | );
80 | };
81 |
82 | return (
83 |
84 | {Object.keys(buttonList).map(renderButtonList)}
85 |
86 | );
87 | };
88 |
89 | SocialButtonList.propTypes = propTypes;
90 | SocialButtonList.defaultProps = defaultProps;
91 |
92 | export default withRouter(SocialButtonList);
93 |
--------------------------------------------------------------------------------
/client/src/components/SocialProfileList.css:
--------------------------------------------------------------------------------
1 | .btn__profiles--list {
2 | display: flex;
3 | flex-wrap: wrap;
4 | justify-content: center;
5 | }
6 |
7 | .container__profile {
8 | display: flex;
9 | flex-direction: column;
10 | align-items: center;
11 | margin: 10px;
12 | }
13 |
14 | .container__profile--photo {
15 | border-radius: 50%;
16 | width: 100px;
17 | height: 100px;
18 | }
19 |
20 | .container__profile--btn {
21 | font-size: 0.65rem;
22 | width: 80px;
23 | height: 28px;
24 | background-color: transparent;
25 | border: 1px solid red;
26 | color: red;
27 | }
28 |
29 | .container__profile--btn:hover {
30 | background-color: red;
31 | color: white;
32 | }
33 |
--------------------------------------------------------------------------------
/client/src/components/SocialProfileList.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent, Fragment } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import './SocialProfileList.css';
5 |
6 | class SocialProfileList extends PureComponent {
7 | static propTypes = {
8 | auth: PropTypes.func.isRequired,
9 | providerData: PropTypes.arrayOf(PropTypes.object).isRequired,
10 | unlinkedProvider: PropTypes.func.isRequired
11 | };
12 |
13 | /**
14 | * Unlinks a provider from the current user account
15 | */
16 | handleProviderUnlink = async (e, provider) => {
17 | const { auth, unlinkedProvider } = this.props;
18 |
19 | if (window.confirm(`Do you really want to unlink ${provider}?`)) {
20 | const providers = await auth()
21 | .currentUser.unlink(`${provider}.com`)
22 | .catch(err => console.error(err));
23 |
24 | unlinkedProvider(provider, providers.providerData);
25 | }
26 | };
27 |
28 | renderProfileList = ({ providerId, photoURL }) => {
29 | const providerName = providerId.split('.')[0];
30 |
31 | return (
32 |
33 |

38 |
{providerName}
39 |
45 |
46 | );
47 | };
48 |
49 | render() {
50 | return (
51 |
52 |
53 | Connected Social Accounts
54 |
55 |
56 | {this.props.providerData.map(this.renderProfileList)}
57 |
58 |
59 | );
60 | }
61 | }
62 |
63 | export default SocialProfileList;
64 |
--------------------------------------------------------------------------------
/client/src/containers/App.css:
--------------------------------------------------------------------------------
1 | button {
2 | border: none;
3 | border-radius: 4px;
4 | height: 45px;
5 | background-color: #aaaaaa;
6 | color: white;
7 | font-weight: bold;
8 | font-size: 1rem;
9 | cursor: pointer;
10 | letter-spacing: 0.07rem;
11 | padding: 3px;
12 | margin: 5px;
13 | }
14 |
15 | button:hover {
16 | background-color: #929292;
17 | }
18 |
19 | .hidden {
20 | display: none;
21 | }
22 |
23 | .text--center {
24 | text-align: center;
25 | }
26 |
--------------------------------------------------------------------------------
/client/src/containers/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
3 |
4 | import Login from '../components/Login';
5 | import Dashboard from '../components/Dashboard';
6 | import About from '../components/About';
7 | import withAuthentication from '../containers/withAuthentication';
8 |
9 | import './App.css';
10 |
11 | class App extends Component {
12 | render() {
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 | }
23 | }
24 |
25 | export default App;
26 |
--------------------------------------------------------------------------------
/client/src/containers/Layout.css:
--------------------------------------------------------------------------------
1 | section {
2 | display: grid;
3 | grid-template-columns: 1fr minmax(360px, 991px) 1fr;
4 | grid-template-areas: '. h .' '. c .' '. f .';
5 | grid-gap: 5px;
6 | margin-top: 20px;
7 | }
8 |
9 | header {
10 | grid-area: h;
11 | text-align: center;
12 | }
13 |
14 | main {
15 | grid-area: c;
16 | }
17 |
18 | main.content-center {
19 | display: grid;
20 | justify-items: center;
21 | }
22 |
23 | footer {
24 | grid-area: f;
25 | display: grid;
26 | justify-items: end;
27 | border-top: 1px solid rgba(34, 36, 38, 0.15);
28 | margin-top: 20px;
29 | }
30 |
--------------------------------------------------------------------------------
/client/src/containers/Layout.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import './Layout.css';
5 |
6 | const propTypes = {
7 | children: PropTypes.node.isRequired,
8 | contentCenter: PropTypes.bool
9 | };
10 |
11 | const defaultProps = {
12 | contentCenter: false
13 | };
14 |
15 | const Layout = ({ children, contentCenter }) => {
16 | return (
17 |
18 |
19 | OAuth Authentication with Firebase
20 |
21 | {children}
22 |
31 |
32 | );
33 | };
34 |
35 | Layout.propTypes = propTypes;
36 | Layout.defaultProps = defaultProps;
37 |
38 | export default Layout;
39 |
--------------------------------------------------------------------------------
/client/src/containers/withAuthentication.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Delay from 'react-delay';
3 |
4 | import { auth } from '../firebase';
5 |
6 | /**
7 | * HOC that verifies user is authenticated before returning the
8 | * protected component
9 | */
10 | export default WrappedComponent => {
11 | class WithAuthentication extends Component {
12 | state = {
13 | providerData: []
14 | };
15 |
16 | componentDidMount() {
17 | auth.getAuth().onAuthStateChanged(user => {
18 | if (user) {
19 | this.setState({ providerData: user.providerData });
20 | } else {
21 | console.info('Must be authenticated');
22 | this.props.history.push('/');
23 | }
24 | });
25 | }
26 |
27 | render() {
28 | return this.state.providerData.length > 0 ? (
29 |
33 | ) : (
34 |
35 | Loading...
36 |
37 | );
38 | }
39 | }
40 |
41 | return WithAuthentication;
42 | };
43 |
--------------------------------------------------------------------------------
/client/src/firebase/auth.js:
--------------------------------------------------------------------------------
1 | import firebase from './firebase';
2 |
3 | /**
4 | * Returns the Firebase Auth service
5 | */
6 | export const getAuth = () => {
7 | return firebase.auth();
8 | };
9 |
10 | /**
11 | * Returns a new instance of GitHub auth provider.
12 | */
13 | export const githubOAuth = () => {
14 | return new firebase.firebase_.auth.GithubAuthProvider();
15 | };
16 |
17 | /**
18 | * Returns a new instance of Twitter auth provider.
19 | */
20 | export const twitterOAuth = () => {
21 | return new firebase.firebase_.auth.TwitterAuthProvider();
22 | };
23 |
24 | /**
25 | * Returns a new instance of Facebook auth provider.
26 | */
27 | export const facebookOAuth = () => {
28 | return new firebase.firebase_.auth.FacebookAuthProvider();
29 | };
30 |
--------------------------------------------------------------------------------
/client/src/firebase/firebase.js:
--------------------------------------------------------------------------------
1 | import firebase from 'firebase/app';
2 | import 'firebase/auth';
3 | // import 'firebase/database';
4 |
5 | /**
6 | * Creates and initializes a Firebase app.
7 | */
8 | const app = firebase.initializeApp({
9 | apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
10 | authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN
11 | // databaseURL: process.env.REACT_APP_FIREBASE_DATABASE_URL
12 | });
13 |
14 | export default app;
15 |
--------------------------------------------------------------------------------
/client/src/firebase/index.js:
--------------------------------------------------------------------------------
1 | import firebase from './firebase';
2 | import * as auth from './auth';
3 |
4 | export { firebase, auth };
5 |
--------------------------------------------------------------------------------
/client/src/index.css:
--------------------------------------------------------------------------------
1 | html {
2 | height: 100%;
3 | box-sizing: border-box;
4 | font-size: 100%;
5 | }
6 |
7 | *,
8 | *:before,
9 | *:after {
10 | box-sizing: inherit;
11 | }
12 |
13 | body {
14 | margin: 0;
15 | padding: 0;
16 | overflow-x: hidden;
17 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial,
18 | sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
19 | }
20 |
--------------------------------------------------------------------------------
/client/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | import './index.css';
5 | import App from './containers/App';
6 | import registerServiceWorker from './registerServiceWorker';
7 |
8 | ReactDOM.render(, document.getElementById('root'));
9 | registerServiceWorker();
10 |
--------------------------------------------------------------------------------
/client/src/initialButtonList.js:
--------------------------------------------------------------------------------
1 | import { auth } from './firebase';
2 |
3 | /**
4 | * Initial state of the Social Buttons
5 | */
6 | export default {
7 | github: {
8 | visible: true,
9 | provider: () => {
10 | const provider = auth.githubOAuth();
11 | provider.addScope('user');
12 | return provider;
13 | }
14 | },
15 | twitter: {
16 | visible: true,
17 | provider: () => auth.twitterOAuth()
18 | },
19 | facebook: {
20 | visible: true,
21 | provider: () => auth.facebookOAuth()
22 | }
23 | };
24 |
--------------------------------------------------------------------------------
/client/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl);
38 |
39 | // Add some additional logging to localhost, pointing developers to the
40 | // service worker/PWA documentation.
41 | navigator.serviceWorker.ready.then(() => {
42 | console.log(
43 | 'This web app is being served cache-first by a service ' +
44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ'
45 | );
46 | });
47 | } else {
48 | // Is not local host. Just register service worker
49 | registerValidSW(swUrl);
50 | }
51 | });
52 | }
53 | }
54 |
55 | function registerValidSW(swUrl) {
56 | navigator.serviceWorker
57 | .register(swUrl)
58 | .then(registration => {
59 | registration.onupdatefound = () => {
60 | const installingWorker = registration.installing;
61 | installingWorker.onstatechange = () => {
62 | if (installingWorker.state === 'installed') {
63 | if (navigator.serviceWorker.controller) {
64 | // At this point, the old content will have been purged and
65 | // the fresh content will have been added to the cache.
66 | // It's the perfect time to display a "New content is
67 | // available; please refresh." message in your web app.
68 | console.log('New content is available; please refresh.');
69 | } else {
70 | // At this point, everything has been precached.
71 | // It's the perfect time to display a
72 | // "Content is cached for offline use." message.
73 | console.log('Content is cached for offline use.');
74 | }
75 | }
76 | };
77 | };
78 | })
79 | .catch(error => {
80 | console.error('Error during service worker registration:', error);
81 | });
82 | }
83 |
84 | function checkValidServiceWorker(swUrl) {
85 | // Check if the service worker can be found. If it can't reload the page.
86 | fetch(swUrl)
87 | .then(response => {
88 | // Ensure service worker exists, and that we really are getting a JS file.
89 | if (
90 | response.status === 404 ||
91 | response.headers.get('content-type').indexOf('javascript') === -1
92 | ) {
93 | // No service worker found. Probably a different app. Reload the page.
94 | navigator.serviceWorker.ready.then(registration => {
95 | registration.unregister().then(() => {
96 | window.location.reload();
97 | });
98 | });
99 | } else {
100 | // Service worker found. Proceed as normal.
101 | registerValidSW(swUrl);
102 | }
103 | })
104 | .catch(() => {
105 | console.log(
106 | 'No internet connection found. App is running in offline mode.'
107 | );
108 | });
109 | }
110 |
111 | export function unregister() {
112 | if ('serviceWorker' in navigator) {
113 | navigator.serviceWorker.ready.then(registration => {
114 | registration.unregister();
115 | });
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-firebase-oauth",
3 | "version": "1.0.0",
4 | "author": "Esau Silva <@_esausilva>",
5 | "license": "MIT",
6 | "scripts": {
7 | "dev:client": "cd client && yarn start",
8 | "dev:server": "cd client && yarn build && cd .. && yarn start",
9 | "start": "node server.js",
10 | "heroku-postbuild": "cd client && npm install && npm install --only=dev --no-shrinkwrap && npm run build"
11 | },
12 | "dependencies": {
13 | "express": "^4.17.1"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "https://github.com/esausilva/react-firebase-oauth.git"
18 | },
19 | "bugs": {
20 | "url": "https://github.com/esausilva/react-firebase-oauth/issues"
21 | },
22 | "keywords": [
23 | "react",
24 | "firebase",
25 | "github",
26 | "twitter",
27 | "facebook",
28 | "authentication",
29 | "oauth"
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const path = require('path');
3 |
4 | const app = express();
5 |
6 | const PORT = process.env.PORT || 5000;
7 |
8 | // Serve any static files
9 | app.use(express.static(path.join(__dirname, 'client/build')));
10 |
11 | // Handle React routing, return all requests to React app
12 | app.get('*', function(req, res) {
13 | res.sendFile(path.join(__dirname, 'client/build', 'index.html'));
14 | });
15 |
16 | app.listen(PORT, () => console.log(`Listening on port ${PORT}`));
17 |
--------------------------------------------------------------------------------
/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 | body-parser@1.19.0:
19 | version "1.19.0"
20 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
21 | integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
22 | dependencies:
23 | bytes "3.1.0"
24 | content-type "~1.0.4"
25 | debug "2.6.9"
26 | depd "~1.1.2"
27 | http-errors "1.7.2"
28 | iconv-lite "0.4.24"
29 | on-finished "~2.3.0"
30 | qs "6.7.0"
31 | raw-body "2.4.0"
32 | type-is "~1.6.17"
33 |
34 | bytes@3.1.0:
35 | version "3.1.0"
36 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
37 | integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
38 |
39 | content-disposition@0.5.3:
40 | version "0.5.3"
41 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
42 | integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==
43 | dependencies:
44 | safe-buffer "5.1.2"
45 |
46 | content-type@~1.0.4:
47 | version "1.0.4"
48 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
49 | integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
50 |
51 | cookie-signature@1.0.6:
52 | version "1.0.6"
53 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
54 | integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
55 |
56 | cookie@0.4.0:
57 | version "0.4.0"
58 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
59 | integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
60 |
61 | debug@2.6.9:
62 | version "2.6.9"
63 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
64 | integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
65 | dependencies:
66 | ms "2.0.0"
67 |
68 | depd@~1.1.2:
69 | version "1.1.2"
70 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
71 | integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
72 |
73 | destroy@~1.0.4:
74 | version "1.0.4"
75 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
76 | integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
77 |
78 | ee-first@1.1.1:
79 | version "1.1.1"
80 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
81 | integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
82 |
83 | encodeurl@~1.0.2:
84 | version "1.0.2"
85 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
86 | integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
87 |
88 | escape-html@~1.0.3:
89 | version "1.0.3"
90 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
91 | integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
92 |
93 | etag@~1.8.1:
94 | version "1.8.1"
95 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
96 | integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
97 |
98 | express@^4.17.1:
99 | version "4.17.1"
100 | resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
101 | integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==
102 | dependencies:
103 | accepts "~1.3.7"
104 | array-flatten "1.1.1"
105 | body-parser "1.19.0"
106 | content-disposition "0.5.3"
107 | content-type "~1.0.4"
108 | cookie "0.4.0"
109 | cookie-signature "1.0.6"
110 | debug "2.6.9"
111 | depd "~1.1.2"
112 | encodeurl "~1.0.2"
113 | escape-html "~1.0.3"
114 | etag "~1.8.1"
115 | finalhandler "~1.1.2"
116 | fresh "0.5.2"
117 | merge-descriptors "1.0.1"
118 | methods "~1.1.2"
119 | on-finished "~2.3.0"
120 | parseurl "~1.3.3"
121 | path-to-regexp "0.1.7"
122 | proxy-addr "~2.0.5"
123 | qs "6.7.0"
124 | range-parser "~1.2.1"
125 | safe-buffer "5.1.2"
126 | send "0.17.1"
127 | serve-static "1.14.1"
128 | setprototypeof "1.1.1"
129 | statuses "~1.5.0"
130 | type-is "~1.6.18"
131 | utils-merge "1.0.1"
132 | vary "~1.1.2"
133 |
134 | finalhandler@~1.1.2:
135 | version "1.1.2"
136 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
137 | integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==
138 | dependencies:
139 | debug "2.6.9"
140 | encodeurl "~1.0.2"
141 | escape-html "~1.0.3"
142 | on-finished "~2.3.0"
143 | parseurl "~1.3.3"
144 | statuses "~1.5.0"
145 | unpipe "~1.0.0"
146 |
147 | forwarded@~0.1.2:
148 | version "0.1.2"
149 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
150 | integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=
151 |
152 | fresh@0.5.2:
153 | version "0.5.2"
154 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
155 | integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
156 |
157 | http-errors@1.7.2:
158 | version "1.7.2"
159 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
160 | integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
161 | dependencies:
162 | depd "~1.1.2"
163 | inherits "2.0.3"
164 | setprototypeof "1.1.1"
165 | statuses ">= 1.5.0 < 2"
166 | toidentifier "1.0.0"
167 |
168 | http-errors@~1.7.2:
169 | version "1.7.3"
170 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
171 | integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
172 | dependencies:
173 | depd "~1.1.2"
174 | inherits "2.0.4"
175 | setprototypeof "1.1.1"
176 | statuses ">= 1.5.0 < 2"
177 | toidentifier "1.0.0"
178 |
179 | iconv-lite@0.4.24:
180 | version "0.4.24"
181 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
182 | integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
183 | dependencies:
184 | safer-buffer ">= 2.1.2 < 3"
185 |
186 | inherits@2.0.3:
187 | version "2.0.3"
188 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
189 | integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
190 |
191 | inherits@2.0.4:
192 | version "2.0.4"
193 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
194 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
195 |
196 | ipaddr.js@1.9.1:
197 | version "1.9.1"
198 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
199 | integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
200 |
201 | media-typer@0.3.0:
202 | version "0.3.0"
203 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
204 | integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
205 |
206 | merge-descriptors@1.0.1:
207 | version "1.0.1"
208 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
209 | integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
210 |
211 | methods@~1.1.2:
212 | version "1.1.2"
213 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
214 | integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
215 |
216 | mime-db@1.43.0:
217 | version "1.43.0"
218 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58"
219 | integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==
220 |
221 | mime-types@~2.1.24:
222 | version "2.1.26"
223 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06"
224 | integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==
225 | dependencies:
226 | mime-db "1.43.0"
227 |
228 | mime@1.6.0:
229 | version "1.6.0"
230 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
231 | integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
232 |
233 | ms@2.0.0:
234 | version "2.0.0"
235 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
236 | integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
237 |
238 | ms@2.1.1:
239 | version "2.1.1"
240 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
241 | integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
242 |
243 | negotiator@0.6.2:
244 | version "0.6.2"
245 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
246 | integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
247 |
248 | on-finished@~2.3.0:
249 | version "2.3.0"
250 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
251 | integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
252 | dependencies:
253 | ee-first "1.1.1"
254 |
255 | parseurl@~1.3.3:
256 | version "1.3.3"
257 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
258 | integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
259 |
260 | path-to-regexp@0.1.7:
261 | version "0.1.7"
262 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
263 | integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
264 |
265 | proxy-addr@~2.0.5:
266 | version "2.0.6"
267 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf"
268 | integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==
269 | dependencies:
270 | forwarded "~0.1.2"
271 | ipaddr.js "1.9.1"
272 |
273 | qs@6.7.0:
274 | version "6.7.0"
275 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
276 | integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
277 |
278 | range-parser@~1.2.1:
279 | version "1.2.1"
280 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
281 | integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
282 |
283 | raw-body@2.4.0:
284 | version "2.4.0"
285 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332"
286 | integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==
287 | dependencies:
288 | bytes "3.1.0"
289 | http-errors "1.7.2"
290 | iconv-lite "0.4.24"
291 | unpipe "1.0.0"
292 |
293 | safe-buffer@5.1.2:
294 | version "5.1.2"
295 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
296 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
297 |
298 | "safer-buffer@>= 2.1.2 < 3":
299 | version "2.1.2"
300 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
301 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
302 |
303 | send@0.17.1:
304 | version "0.17.1"
305 | resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
306 | integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==
307 | dependencies:
308 | debug "2.6.9"
309 | depd "~1.1.2"
310 | destroy "~1.0.4"
311 | encodeurl "~1.0.2"
312 | escape-html "~1.0.3"
313 | etag "~1.8.1"
314 | fresh "0.5.2"
315 | http-errors "~1.7.2"
316 | mime "1.6.0"
317 | ms "2.1.1"
318 | on-finished "~2.3.0"
319 | range-parser "~1.2.1"
320 | statuses "~1.5.0"
321 |
322 | serve-static@1.14.1:
323 | version "1.14.1"
324 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9"
325 | integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==
326 | dependencies:
327 | encodeurl "~1.0.2"
328 | escape-html "~1.0.3"
329 | parseurl "~1.3.3"
330 | send "0.17.1"
331 |
332 | setprototypeof@1.1.1:
333 | version "1.1.1"
334 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
335 | integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
336 |
337 | "statuses@>= 1.5.0 < 2", statuses@~1.5.0:
338 | version "1.5.0"
339 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
340 | integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
341 |
342 | toidentifier@1.0.0:
343 | version "1.0.0"
344 | resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
345 | integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
346 |
347 | type-is@~1.6.17, type-is@~1.6.18:
348 | version "1.6.18"
349 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
350 | integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
351 | dependencies:
352 | media-typer "0.3.0"
353 | mime-types "~2.1.24"
354 |
355 | unpipe@1.0.0, unpipe@~1.0.0:
356 | version "1.0.0"
357 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
358 | integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
359 |
360 | utils-merge@1.0.1:
361 | version "1.0.1"
362 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
363 | integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
364 |
365 | vary@~1.1.2:
366 | version "1.1.2"
367 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
368 | integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
369 |
--------------------------------------------------------------------------------