Skip to content

Commit

Permalink
Merge pull request #122 from sharetribe/update-v6.5.0-from-upstream
Browse files Browse the repository at this point in the history
Update v6.5.0 from upstream
  • Loading branch information
OtterleyW authored Nov 16, 2020
2 parents 8c1be2b + 7d30cf7 commit d8be5f3
Show file tree
Hide file tree
Showing 19 changed files with 3,398 additions and 1,132 deletions.
3 changes: 3 additions & 0 deletions .env-template
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ REACT_APP_CANONICAL_ROOT_URL=http://localhost:3000
REACT_APP_FACEBOOK_APP_ID=
FACEBOOK_APP_SECRET=

REACT_APP_GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=

# This is overwritten by configuration in .env.development and
# .env.test. In production deployments use env variable and set it to
# 'production'
Expand Down
17 changes: 16 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,29 @@ https://github.com/sharetribe/flex-template-web/

## Upcoming version 2020-XX-XX

## [v8.5.0] 2020-11-16

### Updates from upstream (FTW-daily v6.5.0)

- [add] Add support for Google login. This works in the same way as Facebook flow so you can check
the [Facebook PR](https://github.com/sharetribe/ftw-daily/pull/1364) for the more details.
[#1376](https://github.com/sharetribe/ftw-daily/pull/1376)
- [fix] Routes component got double rendered due to Redux container HOC. Because navigation could
happen twice, loadData was also called twice.
[#1380](https://github.com/sharetribe/ftw-daily/pull/1380)
- [fix] 401 return code when rendering on SSR.
[#1379](https://github.com/sharetribe/ftw-daily/pull/1379)

[v8.5.0]: https://github.com/sharetribe/ftw-hourly/compare/v8.4.2...v8.5.0

## [v8.4.2] 2020-10-30

### Updates from upstream (FTW-daily v6.4.2)

- [fix] Fix the issue with form on AuthenticationPage not showing on smaller screens when using
Safari as browser. [#1377](https://github.com/sharetribe/ftw-daily/pull/1377)

[v6.4.2]: https://github.com/sharetribe/ftw-hourly/compare/v8.4.1...v8.4.2
[v8.4.2]: https://github.com/sharetribe/ftw-hourly/compare/v8.4.1...v8.4.2

## [v8.4.1] 2020-10-20

Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "app",
"version": "8.4.2",
"version": "8.5.0",
"private": true,
"license": "Apache-2.0",
"dependencies": {
Expand Down Expand Up @@ -38,6 +38,7 @@
"object.values": "^1.1.1",
"passport": "^0.4.1",
"passport-facebook": "^3.0.0",
"passport-google-oauth": "^2.0.0",
"path-to-regexp": "^6.1.0",
"prop-types": "^15.7.2",
"query-string": "^6.13.1",
Expand All @@ -57,7 +58,7 @@
"redux-thunk": "^2.3.0",
"seedrandom": "^3.0.5",
"sharetribe-flex-sdk": "1.13.0",
"sharetribe-scripts": "3.1.1",
"sharetribe-scripts": "3.4.4",
"smoothscroll-polyfill": "^0.4.0",
"source-map-support": "^0.5.9",
"url": "^0.11.0"
Expand Down
4 changes: 3 additions & 1 deletion server/api-util/sdk.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const http = require('http');
const https = require('https');
const Decimal = require('decimal.js');
const log = require('../log');
const sharetribeSdk = require('sharetribe-flex-sdk');

const CLIENT_ID = process.env.REACT_APP_SHARETRIBE_SDK_CLIENT_ID;
Expand Down Expand Up @@ -53,7 +54,8 @@ exports.deserialize = str => {
};

exports.handleError = (res, error) => {
console.error(error);
log.error(error, 'local-api-request-failed', error.data);

if (error.status && error.statusText && error.data) {
const { status, statusText, data } = error;

Expand Down
2 changes: 1 addition & 1 deletion server/api/auth/createUserWithIdp.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ module.exports = (req, res) => {
idpId === FACEBOOK_IDP_ID ? FACBOOK_APP_ID : idpId === GOOGLE_IDP_ID ? GOOGLE_CLIENT_ID : null;

sdk.currentUser
.createWithIdp({ idpId: FACEBOOK_IDP_ID, idpClientId, idpToken, ...rest })
.createWithIdp({ idpId, idpClientId, idpToken, ...rest })
.then(() =>
// After the user is created, we need to call loginWithIdp endpoint
// so that the user will be logged in.
Expand Down
2 changes: 1 addition & 1 deletion server/api/auth/facebook.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const verifyCallback = (req, accessToken, refreshToken, profile, done) => {
email,
firstName: first_name,
lastName: last_name,
accessToken,
idpToken: accessToken,
refreshToken,
from,
defaultReturn,
Expand Down
87 changes: 87 additions & 0 deletions server/api/auth/google.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
const loginWithIdp = require('./loginWithIdp');

const radix = 10;
const PORT = parseInt(process.env.REACT_APP_DEV_API_SERVER_PORT, radix);
const rootUrl = process.env.REACT_APP_CANONICAL_ROOT_URL;
const clientID = process.env.REACT_APP_GOOGLE_CLIENT_ID;
const clientSecret = process.env.GOOGLE_CLIENT_SECRET;

let callbackURL = null;

const useDevApiServer = process.env.NODE_ENV === 'development' && !!PORT;

if (useDevApiServer) {
callbackURL = `http://localhost:${PORT}/api/auth/google/callback`;
} else {
callbackURL = `${rootUrl}/api/auth/google/callback`;
}

const strategyOptions = {
clientID,
clientSecret,
callbackURL,
passReqToCallback: true,
};

const verifyCallback = (req, accessToken, refreshToken, rawReturn, profile, done) => {
// We need to add additional parameter `rawReturn` to the callback
// so that we can access the id_token coming from Google
// With Google we want to use that id_token instead of accessToken in Flex
const idpToken = rawReturn.id_token;

const { email, given_name, family_name } = profile._json;
const state = req.query.state;
const queryParams = JSON.parse(state);

const { from, defaultReturn, defaultConfirm } = queryParams;

const userData = {
email,
firstName: given_name,
lastName: family_name,
idpToken,
from,
defaultReturn,
defaultConfirm,
};

done(null, userData);
};

// ClientId is required when adding a new Google strategy to passport
if (clientID) {
passport.use(new GoogleStrategy(strategyOptions, verifyCallback));
}

exports.authenticateGoogle = (req, res, next) => {
const from = req.query.from ? req.query.from : null;
const defaultReturn = req.query.defaultReturn ? req.query.defaultReturn : null;
const defaultConfirm = req.query.defaultConfirm ? req.query.defaultConfirm : null;

const params = {
...(!!from && { from }),
...(!!defaultReturn && { defaultReturn }),
...(!!defaultConfirm && { defaultConfirm }),
};

const paramsAsString = JSON.stringify(params);

passport.authenticate('google', {
scope: [
'https://www.googleapis.com/auth/plus.login',
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/userinfo.email',
],
state: paramsAsString,
})(req, res, next);
};

// Use custom callback for calling loginWithIdp enpoint
// to log in the user to Flex with the data from Google
exports.authenticateGoogleCallback = (req, res, next) => {
passport.authenticate('google', function(err, user) {
loginWithIdp(err, user, req, res, clientID, 'google');
})(req, res, next);
};
18 changes: 13 additions & 5 deletions server/api/auth/loginWithIdp.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const http = require('http');
const https = require('https');
const sharetribeSdk = require('sharetribe-flex-sdk');
const log = require('../../log.js');
const sdkUtils = require('../../api-util/sdk');

const CLIENT_ID = process.env.REACT_APP_SHARETRIBE_SDK_CLIENT_ID;
Expand All @@ -21,7 +22,7 @@ const baseUrl = BASE_URL ? { baseUrl: BASE_URL } : {};

module.exports = (err, user, req, res, clientID, idpId) => {
if (err) {
console.error(err);
log.error(err, 'fetching-user-data-from-idp-failed');

// Save error details to cookie so that we can show
// relevant information in the frontend
Expand All @@ -41,7 +42,10 @@ module.exports = (err, user, req, res, clientID, idpId) => {
}

if (!user) {
console.error('Failed to fetch user details from identity provider!');
log.error(
new Error('Failed to fetch user details from identity provider'),
'fetching-user-data-from-idp-failed'
);

// Save error details to cookie so that we can show
// relevant information in the frontend
Expand Down Expand Up @@ -82,9 +86,9 @@ module.exports = (err, user, req, res, clientID, idpId) => {

return sdk
.loginWithIdp({
idpId: 'facebook',
idpId,
idpClientId: clientID,
idpToken: user ? user.accessToken : null,
idpToken: user.idpToken,
})
.then(response => {
if (response.status === 200) {
Expand All @@ -100,6 +104,10 @@ module.exports = (err, user, req, res, clientID, idpId) => {
}
})
.catch(() => {
console.log(
'Authenticating with idp failed. User needs to confirm creating sign up in frontend.'
);

// If authentication fails, we want to create a new user with idp
// For this we will need to pass some information to frontend so
// that we can use that information in createUserWithIdp api call.
Expand All @@ -112,7 +120,7 @@ module.exports = (err, user, req, res, clientID, idpId) => {
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
idpToken: `${user.accessToken}`,
idpToken: user.idpToken,
idpId,
from,
},
Expand Down
11 changes: 11 additions & 0 deletions server/apiRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const transitionPrivileged = require('./api/transition-privileged');
const createUserWithIdp = require('./api/auth/createUserWithIdp');

const { authenticateFacebook, authenticateFacebookCallback } = require('./api/auth/facebook');
const { authenticateGoogle, authenticateGoogleCallback } = require('./api/auth/google');

const router = express.Router();

Expand Down Expand Up @@ -69,4 +70,14 @@ router.get('/auth/facebook', authenticateFacebook);
// loginWithIdp endpoint in Flex API to authenticate user to Flex
router.get('/auth/facebook/callback', authenticateFacebookCallback);

// Google authentication endpoints

// This endpoint is called when user wants to initiate authenticaiton with Google
router.get('/auth/google', authenticateGoogle);

// This is the route for callback URL the user is redirected after authenticating
// with Google. In this route a Passport.js custom callback is used for calling
// loginWithIdp endpoint in Flex API to authenticate user to Flex
router.get('/auth/google/callback', authenticateGoogleCallback);

module.exports = router;
23 changes: 11 additions & 12 deletions server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,18 +224,17 @@ app.get('*', (req, res) => {
// Routes component injects the context.unauthorized when the
// user isn't logged in to view the page that requires
// authentication.

const token = tokenStore.getToken();
const scopes = !!token && token.scopes;
const isAnonymous = !!scopes && scopes.length === 1 && scopes[0] === 'public-read';
if (isAnonymous) {
res.status(401).send(html);
} else {
// If the token is associated with other than public-read scopes, we
// assume that client can handle the situation
// TODO: improve by checking if the token is valid (needs an API call)
res.status(200).send(html);
}
sdk.authInfo().then(authInfo => {
if (authInfo && authInfo.isAnonymous === false) {
// It looks like the user is logged in.
// Full verification would require actual call to API
// to refresh the access token
res.status(200).send(html);
} else {
// Current token is anonymous.
res.status(401).send(html);
}
});
} else if (context.forbidden) {
res.status(403).send(html);
} else if (context.url) {
Expand Down
Loading

0 comments on commit d8be5f3

Please sign in to comment.