Skip to content

Commit

Permalink
Merge pull request #5 from Gnito/update-from-ftw-daily
Browse files Browse the repository at this point in the history
Update from ftw-daily
  • Loading branch information
Gnito authored Jan 25, 2021
2 parents 8aaf42a + 10661cc commit 1250cdd
Show file tree
Hide file tree
Showing 12 changed files with 184 additions and 20 deletions.
1 change: 1 addition & 0 deletions .env-template
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ SHARETRIBE_SDK_CLIENT_SECRET=
#

REACT_APP_SHARETRIBE_MARKETPLACE_CURRENCY=USD
# Host/domain - don't use trailing slash: "/"
REACT_APP_CANONICAL_ROOT_URL=http://localhost:3000

# Social logins && SSO
Expand Down
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,26 @@ way to update this template, but currently, we follow a pattern:

## Upcoming version 2020-XX-XX

## [v7.3.0] 2021-01-13

- [fix] Move well-known/\* endpoints related to OIDC proxy setup from `apiRouter` to new
`wellKnownRouter`so that they can be enabled outside the basic auth setup. It also makes it
simpler to set the identity provider url, because we can drop the `/api` part of the path. Also,
rename the `RSA_SECRET_KEY`to `RSA_PRIVATE_KEY` for consistency.
[#1399](https://github.com/sharetribe/ftw-daily/pull/1399)
- [fix] Make sure that the verify email API endpoint has been called successfully before redirecting
the user away from EmailVerificationPage.
[#1397](https://github.com/sharetribe/ftw-daily/pull/1397)

[v7.3.0]: https://github.com/sharetribe/ftw-daily/compare/v7.2.0...v7.3.0

## [v7.2.0] 2020-12-16

- [add] Add helper functions for setting up your own OIDC authentication and using FTW server as
proxy when needed. [#1383](https://github.com/sharetribe/ftw-daily/pull/1383)

[v7.2.0]: https://github.com/sharetribe/ftw-daily/compare/v7.1.0...v7.2.0

## [v7.1.0] 2020-12-15

- [change] Handle entity update with sparse attributes.
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "app",
"version": "7.1.0",
"version": "7.3.0",
"private": true,
"license": "Apache-2.0",
"dependencies": {
Expand Down Expand Up @@ -30,6 +30,7 @@
"full-icu": "^1.3.1",
"helmet": "^4.0.0",
"intl-pluralrules": "^1.0.3",
"jose": "3.1.0",
"lodash": "^4.17.19",
"mapbox-gl-multitouch": "^1.0.3",
"moment": "^2.22.2",
Expand Down
2 changes: 1 addition & 1 deletion scripts/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ const advancedSettings = settings => {
name: 'REACT_APP_CANONICAL_ROOT_URL',
message: `What is your canonical root URL?
${chalk.dim(
'Canonical root URL of the marketplace is needed for social media sharing and SEO optimization. When developing the template application locally URL is usually http://localhost:3000'
'Canonical root URL of the marketplace is needed for social media sharing, SEO optimization, and social logins. When developing the template application locally URL is usually http://localhost:3000 (Note: you should omit any trailing slash)'
)}
`,
default: function() {
Expand Down
100 changes: 100 additions & 0 deletions server/api-util/idToken.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
const crypto = require('crypto');
const { default: fromKeyLike } = require('jose/jwk/from_key_like');
const { default: SignJWT } = require('jose/jwt/sign');

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 useDevApiServer = process.env.NODE_ENV === 'development' && !!PORT;

const issuerUrl = useDevApiServer ? `http://localhost:${PORT}` : `${rootUrl}`;

/**
* Gets user information and creates the signed jwt for id token.
*
* @param {string} idpClientId the client id of the idp provider in Console
* @param {Object} options signing options containing signingAlg and required key information
* @param {Object} user user information containing at least firstName, lastName, email and emailVerified
*
* @return {Promise} idToken
*/
exports.createIdToken = (idpClientId, user, options) => {
if (!idpClientId) {
console.error('Missing idp client id!');
return;
}
if (!user) {
console.error('Missing user information!');
return;
}

const signingAlg = options.signingAlg;

// Currently Flex supports only RS256 signing algorithm.
if (signingAlg !== 'RS256') {
console.error(`${signingAlg} is not currently supported!`);
return;
}

const { rsaPrivateKey, keyId } = options;

if (!rsaPrivateKey) {
console.error('Missing RSA private key!');
return;
}

// We use jose library which requires the RSA key
// to be KeyLike format:
// https://github.com/panva/jose/blob/master/docs/types/_types_d_.keylike.md
const privateKey = crypto.createPrivateKey(rsaPrivateKey);

const { userId, firstName, lastName, email, emailVerified } = user;

const jwt = new SignJWT({
given_name: firstName,
family_name: lastName,
email: email,
email_verified: emailVerified,
})
.setProtectedHeader({ alg: signingAlg, kid: keyId })
.setIssuedAt()
.setIssuer(issuerUrl)
.setSubject(userId)
.setAudience(idpClientId)
.setExpirationTime('1h')
.sign(privateKey);

return jwt;
};

// Serves the discovery document in json format
// this document is expected to be found from
// api/.well-known/openid-configuration endpoint
exports.openIdConfiguration = (req, res) => {
res.json({
issuer: issuerUrl,
jwks_uri: `${issuerUrl}/.well-known/jwks.json`,
subject_types_supported: ['public'],
id_token_signing_alg_values_supported: ['RS256'],
});
};

/**
* @param {String} signingAlg signing algorithm, currently only RS256 is supported
* @param {Array} list containing keys to be served in json endpoint
*
* // Serves the RSA public key as JWK
// this document is expected to be found from
// api/.well-known/jwks.json endpoint as stated in discovery document
*/
exports.jwksUri = keys => (req, res) => {
const jwkKeys = keys.map(key => {
return fromKeyLike(crypto.createPublicKey(key.rsaPublicKey)).then(res => {
return { alg: key.alg, kid: key.keyId, ...res };
});
});

Promise.all(jwkKeys).then(resolvedJwkKeys => {
res.json({ keys: resolvedJwkKeys });
});
};
4 changes: 2 additions & 2 deletions server/api/auth/loginWithIdp.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const httpsAgent = new https.Agent({ keepAlive: true });

const baseUrl = BASE_URL ? { baseUrl: BASE_URL } : {};

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

Expand Down Expand Up @@ -87,7 +87,7 @@ module.exports = (err, user, req, res, clientID, idpId) => {
return sdk
.loginWithIdp({
idpId,
idpClientId: clientID,
idpClientId,
idpToken: user.idpToken,
})
.then(response => {
Expand Down
2 changes: 2 additions & 0 deletions server/apiServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
const cors = require('cors');
const apiRouter = require('./apiRouter');
const wellKnownRouter = require('./wellKnownRouter');

const radix = 10;
const PORT = parseInt(process.env.REACT_APP_DEV_API_SERVER_PORT, radix);
Expand All @@ -23,6 +24,7 @@ app.use(
})
);
app.use(cookieParser());
app.use('/.well-known', wellKnownRouter);
app.use('/api', apiRouter);

app.listen(PORT, () => {
Expand Down
11 changes: 9 additions & 2 deletions server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const sitemap = require('express-sitemap');
const passport = require('passport');
const auth = require('./auth');
const apiRouter = require('./apiRouter');
const wellKnownRouter = require('./wellKnownRouter');
const renderer = require('./renderer');
const dataLoader = require('./dataLoader');
const fs = require('fs');
Expand Down Expand Up @@ -131,9 +132,15 @@ app.use('/static', express.static(path.join(buildPath, 'static')));
app.use('/robots.txt', express.static(path.join(buildPath, 'robots.txt')));
app.use(cookieParser());

// These .well-known/* endpoints will be enabled if you are using FTW as OIDC proxy
// https://www.sharetribe.com/docs/cookbook-social-logins-and-sso/setup-open-id-connect-proxy/
// We need to handle these endpoints separately so that they are accessible by Flex
// even if you have enabled basic authentication e.g. in staging environment.
app.use('/.well-known', wellKnownRouter);

// Use basic authentication when not in dev mode. This is
// intentionally after the static middleware to skip basic auth for
// static resources.
// intentionally after the static middleware and /.well-known
// endpoints as those will bypass basic auth.
if (!dev) {
const USERNAME = process.env.BASIC_AUTH_USERNAME;
const PASSWORD = process.env.BASIC_AUTH_PASSWORD;
Expand Down
17 changes: 17 additions & 0 deletions server/wellKnownRouter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const express = require('express');
const { openIdConfiguration, jwksUri } = require('./api-util/idToken');

const rsaPrivateKey = process.env.RSA_PRIVATE_KEY;
const rsaPublicKey = process.env.RSA_PUBLIC_KEY;
const keyId = process.env.KEY_ID;

const router = express.Router();

// These .well-known/* endpoints will be enabled if you are using FTW as OIDC proxy
// https://www.sharetribe.com/docs/cookbook-social-logins-and-sso/setup-open-id-connect-proxy/
if (rsaPublicKey && rsaPrivateKey) {
router.get('/openid-configuration', openIdConfiguration);
router.get('/jwks.json', jwksUri([{ alg: 'RS256', rsaPublicKey, keyId }]));
}

module.exports = router;
16 changes: 10 additions & 6 deletions src/containers/EmailVerificationPage/EmailVerificationPage.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import { bool, func, shape, string } from 'prop-types';
import { compose } from 'redux';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
Expand Down Expand Up @@ -51,6 +51,7 @@ export const EmailVerificationPageComponent = props => {
intl,
scrollingDisabled,
submitVerification,
isVerified,
emailVerificationInProgress,
verificationError,
location,
Expand All @@ -62,9 +63,12 @@ export const EmailVerificationPageComponent = props => {
const initialValues = {
verificationToken: parseVerificationToken(location ? location.search : null),
};

const user = ensureCurrentUser(currentUser);
if (user && user.attributes.emailVerified) {

// The first attempt to verify email is done when the page is loaded
// If the verify API call is successfull and the user has verified email
// We can redirect user forward from email verification page.
if (isVerified && user && user.attributes.emailVerified) {
return <NamedRedirect name="LandingPage" />;
}

Expand Down Expand Up @@ -104,12 +108,11 @@ EmailVerificationPageComponent.defaultProps = {
verificationError: null,
};

const { bool, func, shape, string } = PropTypes;

EmailVerificationPageComponent.propTypes = {
currentUser: propTypes.currentUser,
scrollingDisabled: bool.isRequired,
submitVerification: func.isRequired,
isVerified: bool,
emailVerificationInProgress: bool.isRequired,
verificationError: propTypes.error,

Expand All @@ -124,8 +127,9 @@ EmailVerificationPageComponent.propTypes = {

const mapStateToProps = state => {
const { currentUser } = state.user;
const { verificationError, verificationInProgress } = state.EmailVerification;
const { isVerified, verificationError, verificationInProgress } = state.EmailVerification;
return {
isVerified,
verificationError,
emailVerificationInProgress: verificationInProgress,
currentUser,
Expand Down
2 changes: 1 addition & 1 deletion src/forms/EmailVerificationForm/EmailVerificationForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ const EmailVerificationFormComponent = props => (
</div>
);

return emailVerified && !pendingEmail ? alreadyVerified : verifyEmail;
return emailVerified && !pendingEmail && !verificationError ? alreadyVerified : verifyEmail;
}}
/>
);
Expand Down
26 changes: 19 additions & 7 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7634,6 +7634,11 @@ [email protected]:
import-local "^3.0.2"
jest-cli "^26.6.0"

[email protected]:
version "3.1.0"
resolved "https://registry.yarnpkg.com/jose/-/jose-3.1.0.tgz#31a48b76a2e0f5da4e9a1be261e430e0bfaa4a43"
integrity sha512-TLZFF0qAPlG0GZDrPw9HAiWKJcDuUbOp1WdjuS5cJ0reTzd1zS718zrUPOt7BIOViTA6PZpEnMt5cMQttJq3QA==

js-cookie@^2.1.3:
version "2.2.1"
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8"
Expand Down Expand Up @@ -8687,9 +8692,9 @@ node-modules-regexp@^1.0.0:
integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=

node-notifier@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.0.tgz#a7eee2d51da6d0f7ff5094bc7108c911240c1620"
integrity sha512-46z7DUmcjoYdaWyXouuFNNfUo6eFa94t23c53c+lG/9Cvauk4a98rAUp9672X5dxGdQmLpPzTxzu8f/OeEPaFA==
version "8.0.1"
resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.1.tgz#f86e89bbc925f2b068784b31f382afdc6ca56be1"
integrity sha512-BvEXF+UmsnAfYfoapKM9nGxnP+Wn7P91YfXmrKnfcYCx6VBeoN5Ez5Ogck6I8Bi5k4RlpqRYaw75pAwzX9OphA==
dependencies:
growly "^1.3.0"
is-wsl "^2.2.0"
Expand Down Expand Up @@ -11530,7 +11535,7 @@ [email protected]:
resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==

[email protected], semver@^7.2.1, semver@^7.3.2:
[email protected]:
version "7.3.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938"
integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==
Expand All @@ -11540,6 +11545,13 @@ semver@^6.0.0, semver@^6.2.0, semver@^6.3.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==

semver@^7.2.1, semver@^7.3.2:
version "7.3.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97"
integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==
dependencies:
lru-cache "^6.0.0"

[email protected]:
version "0.17.1"
resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
Expand Down Expand Up @@ -13092,9 +13104,9 @@ uuid@^3.3.2, uuid@^3.4.0:
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==

uuid@^8.3.0:
version "8.3.1"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31"
integrity sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==

v8-compile-cache@^2.0.3:
version "2.1.1"
Expand Down

0 comments on commit 1250cdd

Please sign in to comment.