fb + linkedin working

This commit is contained in:
2019-06-18 18:01:06 +02:00
parent 62c118519e
commit 8bcd62519d
16 changed files with 337 additions and 255 deletions
+8 -4
View File
@@ -1,9 +1,13 @@
SERVER_URL=http://localhost:80
MONGO_URI=mongodb://declic:declic42@ds115653.mlab.com:15653/next-auth
FACEBOOK_ID=291280511557629
FACEBOOK_SECRET=3f7071a009d9b1fcf8795437debb3bb1
LINKEDIN_ID=779iblimxla9kl
LINKEDIN_SECRET=WtROidGiqcDGjOpP
FACEBOOK_ID=2332071840142634
FACEBOOK_SECRET=afb8bbca72bd8c517c64f81e998e4f12
PROD_FACEBOOK_ID=1800752833559014
PROD_FACEBOOK_SECRET=454de729b1e58f506f94c8b50ca5c011
TT_LINKEDIN_ID=86m6hegwyj8lbo
TT_LINKEDIN_SECRET=LIOibqDszsP2vQSr
LINKEDIN_ID=77k1m8qx7w6ygj
LINKEDIN_SECRET=mbcnVjyy2vqAZz1B
GOOGLE_ID=
GOOGLE_SECRET=
TWITTER_KEY=
+6 -11
View File
@@ -1,7 +1,6 @@
import { USER_LOGGED_IN, USER_LOGGED_OUT, REQUEST_LOGIN, REQUEST_LOGIN_FAILURE,
REQUEST_LINKEDIN_LOGIN, REQUEST_LINKEDIN_LOGIN_FAILURE, REQUEST_LINKEDIN_LOGIN_SUCCESS} from "../types";
import api from "../data/api";
import setAuthorizationHeader from "../data/setAuthorizationHeader";
REQUEST_OAUTH_LOGIN, REQUEST_OAUTH_LOGIN_FAILURE
} from "../types";
export const userLoggedIn = user => ({
type: USER_LOGGED_IN,
@@ -22,19 +21,15 @@ export const requestLoginFailure = errors => ({
errors
});
export const requestLinkedInLogin = () => ({
type: REQUEST_LINKEDIN_LOGIN,
export const requestOauthLogin = () => ({
type: REQUEST_OAUTH_LOGIN,
});
export const requestLinkedInLoginFailure = errors => ({
type: REQUEST_LINKEDIN_LOGIN_FAILURE,
export const requestOauthLoginFailure = errors => ({
type: REQUEST_OAUTH_LOGIN_FAILURE,
errors
});
export const requestLinkedInLoginSuccess = errors => ({
type: REQUEST_LINKEDIN_LOGIN_SUCCESS,
errors
});
// export const login = credentials => dispatch =>
// api.user.login(credentials).then(user => {
+2 -2
View File
@@ -56,11 +56,11 @@ function Layout(props) {
<div id="C" className={classes.content}>
{props.children}
</div>
<div id="T" className={classes.foot}>
{/* <div id="T" className={classes.foot}>
<div className={classes.inner}>
<Footer />
</div>
</div>
</div> */}
</div>
)
}
+4 -2
View File
@@ -7,8 +7,10 @@ export default {
user: {
login: credentials =>
axios.post("/api/login_check", { username: credentials.email, password: credentials.password }).then(res => res.data),
linkedInLogin: () => {}
loginOauth: ({token,type}) =>
axios.post("/api/login_check_oauth", {'oauth-token':token,'oauth-type':type}),
fetchUser: () =>
axios.post("/api/user").then(res => res.data)
},
ads: {
+1 -1
View File
@@ -55,7 +55,7 @@ module.exports = () => {
providerOptions: {
scope: ['r_emailaddress', 'r_basicprofile']
},
Strategy: require('passport-linkedin-oauth2').Strategy,
Strategy: require('@sokratis/passport-linkedin-oauth2').Strategy,
strategyOptions: {
clientID: process.env.LINKEDIN_ID,
clientSecret: process.env.LINKEDIN_SECRET,
+1 -1
View File
@@ -10,6 +10,7 @@
"@material-ui/core": "^4.0.0",
"@material-ui/icons": "^3.0.1",
"@ostai/next-css": "^1.0.2",
"@sokratis/passport-linkedin-oauth2": "^2.1.0",
"axios": "^0.18.0",
"connect-mongo": "^2.0.1",
"cross-env": "^5.2.0",
@@ -34,7 +35,6 @@
"nodemailer-smtp-transport": "^2.7.4",
"passport": "^0.4.0",
"passport-facebook": "^3.0.0",
"passport-linkedin-oauth2": "^1.5.0",
"prop-types": "latest",
"react": "latest",
"react-dom": "latest",
+66
View File
@@ -0,0 +1,66 @@
import React, { Component } from 'react'
import Router from 'next/router'
import { connect } from "react-redux";
import { withStyles } from '@material-ui/core/styles';
import setAuthorizationHeader from "../data/setAuthorizationHeader";
import { userLoggedIn, requestLoginFailure } from "../actions/auth";
const styles = theme => ({
container: {
display: 'flex',
flexDirection: 'column',
height: '100%',
},
});
class AuthRouter extends Component {
componentDidMount()
{
let JWT = this.props.JWT;
if( JWT === null){
//free token
setAuthorizationHeader()
this.props.requestLoginFailure('JWT absent')
Router.push('/login');}
else
{
localStorage.setItem('ttJWT', JWT)
//set token to axios headers
setAuthorizationHeader(JWT)
var url = localStorage.getItem('loginRedirect')
console.log(url)
if (url === null)
url = '/offres'
Router.push(url);
this.props.userLoggedIn()
}
}
render() {
return (
<div>
Routing...
</div>
)
}
}
AuthRouter.getInitialProps = function (context) {
console.log("--AuthRouter--")
const { JWT } = context.query
console.log(context.query)
//context.store.dispatch(fetchAdRequest(id))
//console.log(url)
console.log(JWT)
return {JWT:JWT};
}
export default withStyles(styles)(connect(null, {userLoggedIn, requestLoginFailure})(AuthRouter));
+50 -16
View File
@@ -1,13 +1,15 @@
import React from 'react';
import React, { Fragment } from 'react';
import PropTypes from "prop-types";
import Layout from '../components/templates/Layout';
import { TextField, Button, Typography } from '@material-ui/core';
import Link from '@material-ui/core/Link';
import { withStyles } from '@material-ui/core/styles';
import CircularProgress from '@material-ui/core/CircularProgress';
import { connect } from "react-redux";
import Validator from "validator";
import Router from 'next/router'
import { requestLogin, requestLinkedInLogin} from "../actions/auth";
import { requestLogin, requestOauthLogin,requestLoginFailure } from "../actions/auth";
const styles = theme => ({
container: {
@@ -34,8 +36,15 @@ class LoginPage extends React.Component {
email: "",
password: ""
},
errors: {}
errors: {},
loading: false
};
componentWillReceiveProps(nextProps) {
this.setState({
errors: nextProps.errors,
loading: nextProps.loading
})
}
onChange = e =>
this.setState({
@@ -51,11 +60,6 @@ class LoginPage extends React.Component {
this.props.requestLogin(this.state.data)
// this.props.login(this.state.data).then(()=>alert('toto')).catch((err)=>console.debug("ERROR : : " + err))
// this.submit(this.state.data)
// .catch(err =>
// this.setState({ errors: err.response.data.errors, loading: false })
// );
}
};
@@ -68,18 +72,35 @@ class LoginPage extends React.Component {
return errors;
};
onLinkedInAuth = () => {this.props.requestLinkedInLogin()}
linkedInAuth = () => {
localStorage.setItem('loginRedirect', "/Offre?id=31")//pour le test
this.props.requestOauthLogin();
Router.push('/auth/linkedin')
}
facebookAuth = () => {
localStorage.setItem('loginRedirect', "/Offre?id=31")
this.props.requestOauthLogin();
Router.push('/auth/facebook')
}
render() {
const { classes } = this.props;
const { data, errors } = this.state;
const { data, errors, loading } = this.state;
return (
<Layout>
<div className={classes.container}>
{errors.global && (
<div className="alert alert-danger">{errors.global}</div>
{!!errors && (
<div className="alert alert-danger">{errors.message}</div>
)}
{loading && (
<div className="alert alert-info"><CircularProgress color="secondary" />Chargement...</div>
)}
{!loading && (
<Fragment>
<form className={classes.form} onSubmit={this.onSubmit}>
<TextField
value={data.email}
@@ -129,13 +150,13 @@ class LoginPage extends React.Component {
>Ou</Typography>
</form>
<Button variant="outlined" color="primary" onClick={this.onLinkedInAuth}>
<Button variant="outlined" color="primary" onClick={this.linkedInAuth}>
Connexion avec Linkedin
</Button>
<Button variant="outlined" color="primary">
<Button variant="outlined" color="primary" onClick={this.facebookAuth}>
Connexion avec Facebook
</Button>
<a href="http://localhost:3000/auth/facebook" >test</a>
<div className={classes.form}>
<Typography
paragraph={true}
@@ -147,10 +168,23 @@ class LoginPage extends React.Component {
</Typography>
</div>
</Fragment>
)}
</div>
</Layout>
);
}
}
export default withStyles(styles)(connect(null, {requestLogin,requestLinkedInLogin})(LoginPage));
function mapStateToProps(state) {
console.log('mapStateToProps Login')
console.log(state)
return {
loading: !!!state.user.loaded,
errors: state.user.errors
};
}
export default withStyles(styles)(connect(mapStateToProps, { requestLogin, requestOauthLogin, requestLoginFailure })(LoginPage));
-1
View File
@@ -10,7 +10,6 @@ import Card from '@material-ui/core/Card';
import CardActionArea from '@material-ui/core/CardActionArea';
import CardMedia from '@material-ui/core/CardMedia';
import CardContent from '@material-ui/core/CardContent';
import Layout from "../components/templates/Layout"
import { Typography } from '@material-ui/core';
import Router from 'next/router'
+2 -10
View File
@@ -2,21 +2,12 @@ import {
USER_LOGGED_IN,
USER_LOGGED_OUT,
REQUEST_LOGIN_FAILURE,
REQUEST_LOGIN
// FETCH_CURRENT_USER_SUCCESS,
// UPDATE_PROFILE_SUCCESS,
// FETCH_CURRENT_USER_FAILURE,
// SEARCH_USERS_SUCCESS,
// GET_USERS_BY_NAME_SUCCESS
REQUEST_LOGIN,
} from "../types";
export function user(state = {
loaded: false
}, action = {}) {
// console.log("User reducer :: " )
// console.log(action)
// console.log( " :: STATE BEFORE ::")
// console.log(state)
switch (action.type) {
case REQUEST_LOGIN:
return {
@@ -34,6 +25,7 @@ export function user(state = {
};
case REQUEST_LOGIN_FAILURE:
console.log("REQUEST_LOGIN_FAILURE")
return {
loaded: true,
errors: { ...action },
-56
View File
@@ -1,59 +1,3 @@
// import { actionTypes } from './actions'
// export const exampleInitialState = {
// count: 0,
// error: false,
// lastUpdate: 0,
// light: false,
// placeholderData: null
// }
// function reducer (state = exampleInitialState, action) {
// switch (action.type) {
// case actionTypes.FAILURE:
// return {
// ...state,
// ...{ error: action.error }
// }
// case actionTypes.INCREMENT:
// return {
// ...state,
// ...{ count: state.count + 1 }
// }
// case actionTypes.DECREMENT:
// return {
// ...state,
// ...{ count: state.count - 1 }
// }
// case actionTypes.RESET:
// return {
// ...state,
// ...{ count: exampleInitialState.count }
// }
// case actionTypes.LOAD_DATA_SUCCESS:
// return {
// ...state,
// ...{ placeholderData: action.data }
// }
// case actionTypes.TICK_CLOCK:
// return {
// ...state,
// ...{ lastUpdate: action.ts, light: !!action.light }
// }
// default:
// return state
// }
// }
// export default reducer
import { combineReducers } from "redux";
import {user} from "./reducers/user";
+3 -1
View File
@@ -3,11 +3,12 @@
import { all, call, delay, put, take, takeLatest } from 'redux-saga/effects'
import es6promise from 'es6-promise'
import { requestLoginSaga } from './sagas/userSaga.js'
import { requestLoginSaga, requestUserSaga } from './sagas/userSaga.js'
import { requestAdsSaga, requestAdSaga } from './sagas/offreSaga.js'
import {
REQUEST_LOGIN,
FETCH_CURRENT_USER_REQUEST,
FETCH_ADS_REQUEST,
FETCH_AD_REQUEST
}from "./types";
@@ -19,6 +20,7 @@ es6promise.polyfill()
function * rootSaga () {
yield all([
takeLatest(REQUEST_LOGIN, requestLoginSaga),
takeLatest(FETCH_CURRENT_USER_REQUEST, requestUserSaga),
takeLatest(FETCH_ADS_REQUEST, requestAdsSaga),
takeLatest(FETCH_AD_REQUEST, requestAdSaga),
])
+10 -19
View File
@@ -9,8 +9,8 @@ import Router from 'next/router';
export function* requestLoginSaga(action) {
try {
const res = yield call(api.user.login, action.data);
// console.log("requestLoginSaga")
// console.log(res)
console.log("requestLoginSaga")
console.log(res)
// console.log(!!res.token)
// console.log("---")
if (!!res.token) {
@@ -34,31 +34,22 @@ export function* requestLoginSaga(action) {
}
}
export function* requestLinkedInLoginSaga(action) {
export function* requestUserSaga(action) {
try {
const res = yield call(api.user.login, action.data);
// console.log("requestLoginSaga")
// console.log(res)
// console.log(!!res.token)
// console.log("---")
if (!!res.token) {
localStorage.setItem('ttJWT', res.token)
// console.log("-token is good-")
setAuthorizationHeader(res.token)
// console.debug(localStorage.ttJWT)
// console.log("-localStorage.ttJWT-")
const res = yield call(api.user.fetchUser);
if (!!res.status === "success") {
yield put(userLoggedIn(res));
//TODO: temporary redirect
Router.push('/Offres');
//Router.push('/Offres');
} else
throw new Error("no token")
throw new Error("no user")
} catch (error) { console.log(error)
} catch (error) {
console.log(error)
localStorage.removeItem("ttJWT");
setAuthorizationHeader();
yield put(requestLoginFailure(error.response));
}
}
+80 -37
View File
@@ -1,6 +1,9 @@
const express = require('express')
const next = require('next')
require('dotenv').config()
const axios = require('axios')
// const nextAuth = require('next-auth')
// const nextAuthConfig = require('./next-auth.config')
@@ -10,62 +13,102 @@ const handle = app.getRequestHandler()
const passport = require('passport')
const FacebookStrategy = require('passport-facebook').Strategy
const LinkedInStrategy = require('@sokratis/passport-linkedin-oauth2').Strategy
const bodyParser = require('body-parser');
passport.use(new FacebookStrategy({
clientID: process.env.FACEBOOK_ID,
clientSecret: process.env.FACEBOOK_SECRET,
callbackURL: "http://localhost:3000/auth/facebook/callback",
enableProof: true
enableProof: true,
passReqToCallback: true
},
function (accessToken, refreshToken, profile, cb) {
// User.findOrCreate({ facebookId: profile.id }, function (err, user) {
// return cb(err, user);
// });
console.log(profile)
function (req, token, tokenSecret, profile, done) {
process.nextTick(function () {
console.log("fb verifyfunction")
// To keep the example simple, the user's Linkedin profile is returned to
// represent the logged-in user. In a typical application, you would want
// to associate the Linkedin account with a user record in your database,
// and return that user instead.
// console.log(token)
// console.log(profile)
console.log("accessToken : " + accessToken);
console.log("refreshToken" + refreshToken);//****not needed because we only use accesstoken to connect to our api*/
// ! as we are in pure JWT strategie we pass token to authentify on our web services
return done(null, token);
});
}
));
passport.use(new LinkedInStrategy({
clientID: process.env.LINKEDIN_ID,
clientSecret: process.env.LINKEDIN_SECRET,
callbackURL: "http://127.0.0.1:3000/auth/linkedin/callback",
scope: ['r_emailaddress', 'r_liteprofile'],
profileFields: ['id', 'first-name', 'last-name', 'location', 'email-address', 'picture-urls::(original)'],
passReqToCallback: true
},
function (req, token, tokenSecret, profile, done) {
console.log("linkedin verifyfunction")
process.nextTick(function () {
// To keep the example simple, the user's Linkedin profile is returned to
// represent the logged-in user. In a typical application, you would want
// to associate the Linkedin account with a user record in your database,
// and return that user instead.
//console.log(token)
// ! as we are in pure JWT strategie we pass token to authentify on our web services
return done(null, token);
});
return cb(null, profile);
}
));
app.prepare()
// .then(() => {
// // Load configuration and return config object
// return nextAuthConfig()
// })
// .then(nextAuthOptions => {
// // Pass Next.js App instance and NextAuth options to NextAuth
// return nextAuth(app, nextAuthOptions)
// })
.then(() => {
.then(() => {
// Get Express and instance of Express from NextAuth
const server = express()
server.use(passport.initialize());
//const expressApp = nextAuthOptions.express
// express.get('/Offre?id', (req, res) => {
// const actualPage = '/Offre'
// const queryParams = { id: req.params.id }
// app.render(req, res, actualPage, queryParams)
// })
server.get('/auth/facebook',
passport.authorize('facebook', { scope: ['email', 'public_profile'], session: false }));
server.get('/auth/facebook/callback',
passport.authorize('facebook', { failureRedirect: '/login', session: false }),
function (req, res) {
console.log(req.app);
console.log("authenticate")
//TODO:request API JWT with accesstoken
console.log(req.user);
res.redirect('/private');
});
(req, res) => {
// Successful authentication, redirect home.
console.log('facebookcallback')
console.log(req.account)//don't understand why !
// console.log(req.accessToken)
// console.log(req)
axios.post("http://localhost/app_dev.php/api/login_check_oauth", { 'oauth-token': req.account, 'oauth-type': 'facebook' })
.then((resApi) => {
console.log('$$$$$$$$$$$retreive user from token validation$$$$$$$$$$$$$$')
console.log(resApi)
return res.redirect('/AuthRouter?JWT=' + resApi.data.token)
}
).catch(err => { console.log(err) })
})
server.get('/auth/linkedin',
passport.authorize('linkedin', { scope: ['r_basicprofile', 'r_emailaddress', 'r_liteprofile'], session: false }));
server.get('/auth/linkedin/callback',
passport.authorize('linkedin', { failureRedirect: '/login', session: false }),
function (req, res) {
// Successful authentication, redirect home.
console.log('linkedincallback')
console.log(req.accessToken)
console.log(req)
axios.post("http://localhost/app_dev.php/api/login_check_oauth", { 'oauth-token': req.accessToken, 'oauth-type': 'linkedin' })
.then((resApi) => {
console.log('$$$$$$$$$$$retreive user from token validation$$$$$$$$$$$$$$')
console.log(resApi)
return res.redirect('/AuthRouter?JWT=' + resApi.data.token)
}
).catch(err => { console.log(err) })
});
server.get('/Offre/:id', (req, res) => {
const actualPage = '/Offre'
@@ -79,11 +122,11 @@ app.prepare()
server.listen(process.env.PORT, (err) => {
if (err) throw err
console.log('> Ready on localhost:'+process.env.PORT)
console.log('> Ready on http://localhost:' + process.env.PORT)
})
})
.catch((ex) => {
})
.catch((ex) => {
console.error(ex.stack)
process.exit(1)
})
})
+3 -4
View File
@@ -5,6 +5,9 @@ export const USER_LOGGED_OUT = "USER_LOGGED_OUT";
export const REQUEST_LOGIN = "REQUEST_LOGIN";
export const REQUEST_LOGIN_FAILURE = "REQUEST_LOGIN_FAILURE";
export const REQUEST_OAUTH_LOGIN = "REQUEST_OAUTH_LOGIN";
export const REQUEST_OAUTH_LOGIN_FAILURE = "REQUEST_OAUTH_LOGIN_FAILURE";
export const FETCH_CURRENT_USER_REQUEST = "FETCH_CURRENT_USER_REQUEST";
//ADS
@@ -15,7 +18,3 @@ export const FETCH_ADS_FAILURE = "FETCH_ADS_FAILURE";
export const FETCH_AD_REQUEST = "FETCH_AD_REQUEST";
export const FETCH_AD_SUCCESS = "FETCH_AD_SUCCESS";
export const FETCH_AD_FAILURE = "FETCH_AD_FAILURE";
export const REQUEST_LINKEDIN_LOGIN = "REQUEST_LINKEDIN_LOGIN"
export const REQUEST_LINKEDIN_LOGIN_FAILURE = "REQUEST_LINKEDIN_LOGIN_FAILURE"
export const REQUEST_LINKEDIN_LOGIN_SUCCESS = "REQUEST_LINKEDIN_LOGIN_SUCCESS"
+20 -9
View File
@@ -925,6 +925,14 @@
resolved "https://registry.yarnpkg.com/@redux-saga/types/-/types-1.0.2.tgz#1d94f02800b094753f9271c206a26c2a06ca14ee"
integrity sha512-8/qcMh15507AnXJ3lBeuhsdFwnWQqnp68EpUuHlYPixJ5vjVmls7/Jq48cnUlrZI8Jd9U1jkhfCl0gaT5KMgVw==
"@sokratis/passport-linkedin-oauth2@^2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@sokratis/passport-linkedin-oauth2/-/passport-linkedin-oauth2-2.1.0.tgz#4108354f7a9dbb34d45230a5e655d346aee239fa"
integrity sha512-+Yimvm4wm++eezRft3WFywTyDwHVTiDPnRR9wqr8oNXigcymDni7o/eh9zAFVTGXYeUnPPmp+eLGQRHuDYmZQg==
dependencies:
passport-oauth2 "1.x.x"
underscore "^1.7.0"
"@types/cookie@^0.3.1":
version "0.3.1"
resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.3.1.tgz#720a756ea8e760a258708b52441bd341f1ef4296"
@@ -1785,6 +1793,11 @@ base64-js@^1.0.2:
version "1.3.0"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3"
base64url@3.x.x:
version "3.0.1"
resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d"
integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==
base@^0.11.1:
version "0.11.2"
resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f"
@@ -6482,6 +6495,7 @@ number-is-nan@^1.0.0:
oauth@0.9.x:
version "0.9.15"
resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1"
integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE=
object-assign@^2.0.0:
version "2.1.1"
@@ -6798,17 +6812,12 @@ passport-facebook@^3.0.0:
dependencies:
passport-oauth2 "1.x.x"
passport-linkedin-oauth2@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/passport-linkedin-oauth2/-/passport-linkedin-oauth2-1.5.0.tgz#06640b36a6f68299fedb8a6a3acfef4dd04d23b9"
dependencies:
passport-oauth2 "1.x.x"
underscore "^1.7.0"
passport-oauth2@1.x.x:
version "1.4.0"
resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.4.0.tgz#f62f81583cbe12609be7ce6f160b9395a27b86ad"
version "1.5.0"
resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.5.0.tgz#64babbb54ac46a4dcab35e7f266ed5294e3c4108"
integrity sha512-kqBt6vR/5VlCK8iCx1/KpY42kQ+NEHZwsSyt4Y6STiNjU+wWICG1i8ucc1FapXDGO15C5O5VZz7+7vRzrDPXXQ==
dependencies:
base64url "3.x.x"
oauth "0.9.x"
passport-strategy "1.x.x"
uid2 "0.0.x"
@@ -9121,6 +9130,7 @@ uid-safe@~2.1.5:
uid2@0.0.x:
version "0.0.3"
resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82"
integrity sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I=
unbzip2-stream@^1.0.9:
version "1.3.0"
@@ -9132,6 +9142,7 @@ unbzip2-stream@^1.0.9:
underscore@^1.7.0:
version "1.9.1"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961"
integrity sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==
underscore@~1.4.4:
version "1.4.4"