From 1ca03fb0e2bbb8bc832065b26fe4f0b8481e86ce Mon Sep 17 00:00:00 2001
From: Alexandru Munteanu <alexandru.munt@gmail.com>
Date: Fri, 13 Jul 2018 14:00:38 +0300
Subject: [PATCH] feat(link-to-orcid): link a user account to orcid

---
 packages/component-user-manager/package.json  |  7 ++-
 packages/component-user-manager/src/Users.js  |  4 ++
 .../src/routes/users/linkOrcid.js             | 61 +++++++++++++++++++
 .../src/components/UserProfile/LinkOrcID.js   | 13 ++--
 .../components/UserProfile/UserProfilePage.js |  2 +-
 packages/xpub-faraday/config/default.js       |  9 ++-
 packages/xpub-faraday/config/validations.js   |  1 +
 yarn.lock                                     | 49 ++++++++++++++-
 8 files changed, 132 insertions(+), 14 deletions(-)
 create mode 100644 packages/component-user-manager/src/routes/users/linkOrcid.js

diff --git a/packages/component-user-manager/package.json b/packages/component-user-manager/package.json
index c65b0553d..52b907483 100644
--- a/packages/component-user-manager/package.json
+++ b/packages/component-user-manager/package.json
@@ -20,13 +20,14 @@
   },
   "dependencies": {
     "body-parser": "^1.17.2",
-    "chance": "^1.0.13"
+    "chance": "^1.0.13",
+    "passport-orcid": "^0.0.3"
   },
   "peerDependencies": {
     "@pubsweet/logger": "^0.0.1",
+    "pubsweet-component-helper-service": "0.0.1",
     "pubsweet-component-mail-service": "0.0.1",
-    "pubsweet-server": "^1.0.1",
-    "pubsweet-component-helper-service": "0.0.1"
+    "pubsweet-server": "^1.0.1"
   },
   "devDependencies": {
     "apidoc": "^0.17.6",
diff --git a/packages/component-user-manager/src/Users.js b/packages/component-user-manager/src/Users.js
index 1c5c159dd..993e1b428 100644
--- a/packages/component-user-manager/src/Users.js
+++ b/packages/component-user-manager/src/Users.js
@@ -1,4 +1,5 @@
 const bodyParser = require('body-parser')
+const orcidRoutes = require('./routes/users/linkOrcid')
 
 const Invite = app => {
   app.use(bodyParser.json())
@@ -88,6 +89,9 @@ const Invite = app => {
     '/api/users/forgot-password',
     require('./routes/users/forgotPassword')(app.locals.models),
   )
+
+  // register ORCID authentication strategy
+  orcidRoutes(app)
 }
 
 module.exports = Invite
diff --git a/packages/component-user-manager/src/routes/users/linkOrcid.js b/packages/component-user-manager/src/routes/users/linkOrcid.js
new file mode 100644
index 000000000..afb9a1e3d
--- /dev/null
+++ b/packages/component-user-manager/src/routes/users/linkOrcid.js
@@ -0,0 +1,61 @@
+const OrcidStrategy = require('passport-orcid')
+const config = require('config')
+const get = require('lodash/get')
+
+const { clientID, clientSecret, callbackPath, successPath } = config.get(
+  'orcid',
+)
+
+let userId
+const LinkOrcid = app => {
+  const { passport } = app.locals
+
+  passport.serializeUser((user, done) => {
+    done(null, user)
+  })
+
+  passport.deserializeUser((user, done) => {
+    done(null, user)
+  })
+
+  passport.use(
+    new OrcidStrategy(
+      {
+        sandbox: process.env.NODE_ENV !== 'production',
+        callbackURL: `${config.get('pubsweet-client.baseUrl')}${callbackPath}`,
+        clientID,
+        clientSecret,
+      },
+      (accessToken, refreshToken, params, profile, done) => {
+        profile = { orcid: params.orcid, name: params.name }
+        return done(null, profile)
+      },
+    ),
+  )
+
+  app.get(
+    '/api/users/orcid',
+    (req, res, next) => {
+      userId = get(req, 'query.userId')
+      next()
+    },
+    passport.authenticate('orcid'),
+  )
+
+  app.get(
+    callbackPath,
+    passport.authenticate('orcid', {
+      failureRedirect: successPath,
+    }),
+    linkOrcidHandler(app.locals.models),
+  )
+}
+
+const linkOrcidHandler = models => async (req, res) => {
+  const user = await models.User.find(userId)
+  user.orcid = req.user.orcid
+  await user.save()
+  res.redirect(successPath)
+}
+
+module.exports = LinkOrcid
diff --git a/packages/components-faraday/src/components/UserProfile/LinkOrcID.js b/packages/components-faraday/src/components/UserProfile/LinkOrcID.js
index 827c75eda..8b8532ee3 100644
--- a/packages/components-faraday/src/components/UserProfile/LinkOrcID.js
+++ b/packages/components-faraday/src/components/UserProfile/LinkOrcID.js
@@ -4,12 +4,12 @@ import styled from 'styled-components'
 import {
   Row,
   RowItem,
-  LabelHeader,
-  LabelTitle,
   LinkText,
+  LabelTitle,
+  LabelHeader,
 } from '../UIComponents/FormItems'
 
-const LinkOrcID = ({ orcid = '' }) => (
+const LinkOrcID = ({ orcid = '', id }) => (
   <Root>
     <Row noMargin>
       <RowItem>
@@ -19,14 +19,13 @@ const LinkOrcID = ({ orcid = '' }) => (
     {!orcid ? (
       <Row noMargin>
         <RowItem>
-          <LinkText>Link</LinkText>
+          <Link href={`/api/users/orcid?userId=${id}`}>Link</Link>
         </RowItem>
       </Row>
     ) : (
       <Row noMargin>
         <RowItem>
-          <LabelTitle> {orcid} </LabelTitle>
-          <LinkText>Unlink</LinkText>
+          <LabelTitle>{orcid}</LabelTitle>
         </RowItem>
       </Row>
     )}
@@ -41,4 +40,6 @@ const Root = styled.div`
   flex-direction: column;
   max-width: 30em;
 `
+
+const Link = LinkText.withComponent('a')
 // #endregion
diff --git a/packages/components-faraday/src/components/UserProfile/UserProfilePage.js b/packages/components-faraday/src/components/UserProfile/UserProfilePage.js
index 64dd9d28e..462abc4c4 100644
--- a/packages/components-faraday/src/components/UserProfile/UserProfilePage.js
+++ b/packages/components-faraday/src/components/UserProfile/UserProfilePage.js
@@ -21,7 +21,7 @@ const UserProfilePage = ({ history, user }) => (
     />
     <AccountDetails history={history} user={user} />
     <EmailNotifications subscribed={get(user, 'subscription')} />
-    <LinkOrcID orcid={get(user, 'orcid')} />
+    <LinkOrcID id={get(user, 'id')} orcid={get(user, 'orcid')} />
   </Root>
 )
 
diff --git a/packages/xpub-faraday/config/default.js b/packages/xpub-faraday/config/default.js
index 19ac346db..522a8f83e 100644
--- a/packages/xpub-faraday/config/default.js
+++ b/packages/xpub-faraday/config/default.js
@@ -44,10 +44,17 @@ module.exports = {
   },
   'pubsweet-client': {
     API_ENDPOINT: '/api',
+    baseUrl: process.env.CLIENT_BASE_URL || 'http://localhost:3000',
     'login-redirect': '/',
-    'redux-log': true,
+    'redux-log': false,
     theme: process.env.PUBSWEET_THEME,
   },
+  orcid: {
+    clientID: process.env.ORCID_CLIENT_ID,
+    clientSecret: process.env.ORCID_CLIENT_SECRET,
+    callbackPath: '/api/users/orcid/callback',
+    successPath: '/profile',
+  },
   'mail-transport': {
     sendmail: true,
   },
diff --git a/packages/xpub-faraday/config/validations.js b/packages/xpub-faraday/config/validations.js
index 35ebdd732..45de094ef 100644
--- a/packages/xpub-faraday/config/validations.js
+++ b/packages/xpub-faraday/config/validations.js
@@ -116,6 +116,7 @@ module.exports = {
     },
   ],
   user: {
+    orcid: Joi.string(),
     name: Joi.string(),
     username: Joi.string(),
     isConfirmed: Joi.boolean(),
diff --git a/yarn.lock b/yarn.lock
index efc87c5ed..3eb96e13f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -856,6 +856,13 @@ aws4@^1.2.1, aws4@^1.6.0:
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
 
+axios@^0.18.0:
+  version "0.18.0"
+  resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.0.tgz#32d53e4851efdc0a11993b6cd000789d70c05102"
+  dependencies:
+    follow-redirects "^1.3.0"
+    is-buffer "^1.1.5"
+
 axobject-query@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-0.1.0.tgz#62f59dbc59c9f9242759ca349960e7a2fe3c36c0"
@@ -2552,6 +2559,13 @@ core-util-is@1.0.2, core-util-is@~1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
 
+cors@^2.8.4:
+  version "2.8.4"
+  resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.4.tgz#2bd381f2eb201020105cd50ea59da63090694686"
+  dependencies:
+    object-assign "^4"
+    vary "^1"
+
 cosmiconfig@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-1.1.0.tgz#0dea0f9804efdfb929fbb1b188e25553ea053d37"
@@ -3959,6 +3973,12 @@ flush-write-stream@^1.0.0:
     inherits "^2.0.1"
     readable-stream "^2.0.4"
 
+follow-redirects@^1.3.0:
+  version "1.5.1"
+  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.1.tgz#67a8f14f5a1f67f962c2c46469c79eaec0a90291"
+  dependencies:
+    debug "^3.1.0"
+
 font-awesome@^4.7.0:
   version "4.7.0"
   resolved "https://registry.yarnpkg.com/font-awesome/-/font-awesome-4.7.0.tgz#8fa8cf0411a1a31afd07b06d2902bb9fc815a133"
@@ -6640,11 +6660,15 @@ oauth-sign@~0.8.1, oauth-sign@~0.8.2:
   version "0.8.2"
   resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
 
+oauth@0.9.x:
+  version "0.9.15"
+  resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1"
+
 object-assign@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2"
 
-object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
+object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
 
@@ -6942,6 +6966,21 @@ passport-local@^1.0.0:
   dependencies:
     passport-strategy "1.x.x"
 
+passport-oauth2@^1.4.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.4.0.tgz#f62f81583cbe12609be7ce6f160b9395a27b86ad"
+  dependencies:
+    oauth "0.9.x"
+    passport-strategy "1.x.x"
+    uid2 "0.0.x"
+    utils-merge "1.x.x"
+
+passport-orcid@^0.0.3:
+  version "0.0.3"
+  resolved "https://registry.yarnpkg.com/passport-orcid/-/passport-orcid-0.0.3.tgz#802126a4e0dac0dc1406b9521b29b0dd13644ec4"
+  dependencies:
+    passport-oauth2 "^1.4.0"
+
 passport-strategy@1.x.x:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4"
@@ -9742,6 +9781,10 @@ uid-number@^0.0.6:
   version "0.0.6"
   resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
 
+uid2@0.0.x:
+  version "0.0.3"
+  resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82"
+
 ultron@~1.1.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c"
@@ -9920,7 +9963,7 @@ utile@0.3.x:
     ncp "1.0.x"
     rimraf "2.x.x"
 
-utils-merge@1.0.1:
+utils-merge@1.0.1, utils-merge@1.x.x:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
 
@@ -9947,7 +9990,7 @@ value-equal@^0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-0.4.0.tgz#c5bdd2f54ee093c04839d71ce2e4758a6890abc7"
 
-vary@~1.1.2:
+vary@^1, vary@~1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
 
-- 
GitLab