From 4f7cac3542698adbe036b54cdcaa420e99a0712e Mon Sep 17 00:00:00 2001
From: Sebastian <sebastian.mihalache@thinslices.com>
Date: Wed, 14 Feb 2018 15:01:16 +0200
Subject: [PATCH] added teams for reviewers and HEs

---
 packages/component-admin/index.js             |   3 -
 packages/component-admin/src/User.js          | 150 ------------
 .../.gitignore                                |   0
 .../README.md                                 |   0
 packages/component-invite/index.js            |   3 +
 .../package.json                              |   4 +-
 packages/component-invite/src/Invite.js       | 218 ++++++++++++++++++
 packages/component-mail-service/src/Mail.js   |   9 +-
 packages/xpub-faraday/config/components.json  |   2 +-
 packages/xpub-faraday/config/default.js       |   6 +-
 packages/xpub-faraday/config/validations.js   |   4 +-
 packages/xpub-faraday/package.json            |   2 +-
 12 files changed, 233 insertions(+), 168 deletions(-)
 delete mode 100644 packages/component-admin/index.js
 delete mode 100644 packages/component-admin/src/User.js
 rename packages/{component-admin => component-invite}/.gitignore (100%)
 rename packages/{component-admin => component-invite}/README.md (100%)
 create mode 100644 packages/component-invite/index.js
 rename packages/{component-admin => component-invite}/package.json (84%)
 create mode 100644 packages/component-invite/src/Invite.js

diff --git a/packages/component-admin/index.js b/packages/component-admin/index.js
deleted file mode 100644
index f569b273a..000000000
--- a/packages/component-admin/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-module.exports = {
-  backend: () => app => require('./src/User')(app),
-}
diff --git a/packages/component-admin/src/User.js b/packages/component-admin/src/User.js
deleted file mode 100644
index f72f01649..000000000
--- a/packages/component-admin/src/User.js
+++ /dev/null
@@ -1,150 +0,0 @@
-const bodyParser = require('body-parser')
-const logger = require('@pubsweet/logger')
-const uuid = require('uuid')
-const crypto = require('crypto')
-const mailService = require('pubsweet-component-mail-service')
-
-const User = app => {
-  app.use(bodyParser.json())
-  const authBearer = app.locals.passport.authenticate('bearer', {
-    session: false,
-  })
-  app.post('/api/admin/users', authBearer, async (req, res) => {
-    const reqUser = await app.locals.models.User.find(req.user)
-    if (reqUser.admin !== true) {
-      res.status(403).json({ error: 'Unauthorized' })
-      logger.error('unauthorized request')
-      return
-    }
-    const { email, role } = req.body
-    if (email === undefined || role === undefined) {
-      res.status(400).json({ error: 'all parameters are required' })
-      logger.error('some parameters are missing')
-      return
-    }
-
-    try {
-      const user = await app.locals.models.User.findByEmail(email)
-      if (user) {
-        res.status(400).json({ error: 'User already exists' })
-        logger.error('admin tried to invite existing user')
-        return
-      }
-    } catch (e) {
-      if (e.name === 'NotFoundError') {
-        const userBody = {
-          username: uuid.v4().slice(0, 7),
-          email,
-          password: uuid.v4(),
-          roles: { role },
-          passwordResetToken: crypto.randomBytes(32).toString('hex'),
-          isConfirmed: false,
-        }
-        let newUser = new app.locals.models.User(userBody)
-        newUser = await newUser.save()
-        let emailType
-        switch (newUser.roles.role) {
-          case 'editorInChief':
-            emailType = 'invite-editor-in-chief'
-            break
-          case 'handlingEditor':
-            emailType = 'invite-handling-editor'
-            break
-          case 'reviewer':
-            emailType = 'invite-reviewer'
-            break
-          default:
-            break
-        }
-        await mailService.setupEmail(
-          newUser.email,
-          emailType,
-          newUser.passwordResetToken,
-        )
-      }
-    }
-
-    res.status(204).json()
-  })
-  app.post(
-    '/api/admin/users/password-reset',
-    bodyParser.json(),
-    async (req, res) => {
-      const {
-        token,
-        password,
-        email,
-        firstName,
-        lastName,
-        username,
-        middleName,
-        affiliation,
-        position,
-        title,
-      } = req.body
-
-      if (
-        !checkForUndefinedParams(
-          token,
-          password,
-          email,
-          firstName,
-          lastName,
-          username,
-        )
-      ) {
-        res.status(400).json({ error: 'missing required params' })
-        return
-      }
-
-      const updateFields = {
-        password,
-        firstName,
-        lastName,
-        username,
-        middleName,
-        affiliation,
-        position,
-        title,
-        isConfirmed: true,
-      }
-
-      try {
-        const user = await app.locals.models.User.findByEmail(email)
-        if (user) {
-          if (token !== user.passwordResetToken) {
-            res.status(400).json({ error: 'invalid request' })
-            logger.error('admin pw reset tokens do not match')
-            return
-          }
-
-          let newUser = Object.assign(user, updateFields, user)
-          delete newUser.passwordResetToken
-
-          newUser = await newUser.save()
-          res.status(200).json(newUser)
-        }
-      } catch (e) {
-        if (e.name === 'NotFoundError') {
-          res.status(404).json({ error: 'user not found' })
-          logger.error('admin pw reset on non-existing user')
-        } else if (e.name === 'ValidationError') {
-          res.status(400).json({ error: e.details[0].message })
-          logger.error('admin pw reset validation error')
-        }
-        res.status(400).json({ error: e })
-        logger.error(e)
-      }
-    },
-  )
-}
-
-const checkForUndefinedParams = (...params) => {
-  if (params.includes(undefined)) {
-    return false
-  }
-
-  return true
-}
-
-module.exports = User
diff --git a/packages/component-admin/.gitignore b/packages/component-invite/.gitignore
similarity index 100%
rename from packages/component-admin/.gitignore
rename to packages/component-invite/.gitignore
diff --git a/packages/component-admin/README.md b/packages/component-invite/README.md
similarity index 100%
rename from packages/component-admin/README.md
rename to packages/component-invite/README.md
diff --git a/packages/component-invite/index.js b/packages/component-invite/index.js
new file mode 100644
index 000000000..1bdf5faed
--- /dev/null
+++ b/packages/component-invite/index.js
@@ -0,0 +1,3 @@
+module.exports = {
+  backend: () => app => require('./src/Invite')(app),
+}
diff --git a/packages/component-admin/package.json b/packages/component-invite/package.json
similarity index 84%
rename from packages/component-admin/package.json
rename to packages/component-invite/package.json
index 12bc8f996..8f205cd7a 100644
--- a/packages/component-admin/package.json
+++ b/packages/component-invite/package.json
@@ -1,7 +1,7 @@
 {
-  "name": "pubsweet-component-admin",
+  "name": "pubsweet-component-invite",
   "version": "0.0.1",
-  "description": "admin component for pubsweet",
+  "description": "invite component for pubsweet",
   "license": "MIT",
   "files": [
     "src"
diff --git a/packages/component-invite/src/Invite.js b/packages/component-invite/src/Invite.js
new file mode 100644
index 000000000..3c92f5950
--- /dev/null
+++ b/packages/component-invite/src/Invite.js
@@ -0,0 +1,218 @@
+const bodyParser = require('body-parser')
+const logger = require('@pubsweet/logger')
+const uuid = require('uuid')
+const crypto = require('crypto')
+const mailService = require('pubsweet-component-mail-service')
+const get = require('lodash/get')
+
+const Invite = app => {
+  app.use(bodyParser.json())
+  const authBearer = app.locals.passport.authenticate('bearer', {
+    session: false,
+  })
+  app.post('/api/users/invite/:collectionId?', authBearer, async (req, res) => {
+    const { email, role } = req.body
+    if (!checkForUndefinedParams(email, role)) {
+      res.status(400).json({ error: 'Email and role are required' })
+      logger.error('some parameters are missing')
+      return
+    }
+
+    const collectionId = get(req, 'params.collectionId')
+    const reqUser = await app.locals.models.User.find(req.user)
+    let collection
+    if (collectionId) {
+      try {
+        if (role !== 'reviewer' && role !== 'handlingEditor') {
+          res.status(400).json({ error: 'Role does not exist for collections' })
+          logger.error(
+            `invitation has been attempted with invalid role: ${role}`,
+          )
+          return
+        }
+        if (reqUser.roles === undefined) {
+          res
+            .status(403)
+            .json({ error: 'Only HE or EiC can invite users to collection' })
+          logger.error(`request user does not have any defined roles`)
+          return
+        }
+        if (role === 'reviewer' && !reqUser.roles.includes('handlingEditor')) {
+          res.status(403).json({ error: 'Only HE can invite reviewers' })
+          logger.error(`incorrect role when inviting a reviewer`)
+          return
+        } else if (
+          role === 'handlingEditor' &&
+          !reqUser.roles.includes('editorInChief')
+        ) {
+          res.status(403).json({ error: 'Only EiC can invite HE' })
+          logger.error(`incorrect role when inviting a handling editor`)
+          return
+        }
+        collection = await app.locals.models.Collection.find(collectionId)
+      } catch (e) {
+        if (e.name === 'NotFoundError') {
+          res.status(404).json({ error: 'Collection not found' })
+          logger.error(`invalid collection id when inviting ${role}`)
+          return
+        }
+
+        res.status(500).json({ error: 'Something went wrong' })
+        logger.error(e)
+        return
+      }
+    } else if (role !== 'editorInChief') {
+      res.status(400).json({ error: 'Collection id is required' })
+      logger.error('missing collection id when trying to invite reviewer/HE')
+      return
+    } else if (reqUser.admin !== true) {
+      res.status(403).json({ error: 'Only an admin can invite EiC' })
+      logger.error('non-admin user tried to invite an EiC')
+      return
+    }
+
+    try {
+      const user = await app.locals.models.User.findByEmail(email)
+
+      if (user) {
+        res.status(400).json({ error: 'User already exists' })
+        logger.error('admin tried to invite existing user')
+        return
+      }
+    } catch (e) {
+      if (e.name !== 'NotFoundError') {
+        res.status(500).json({ error: e })
+        logger.error(e)
+        return
+      }
+
+      const userBody = {
+        username: uuid.v4().slice(0, 7),
+        email,
+        password: uuid.v4(),
+        roles: [role],
+        passwordResetToken: crypto.randomBytes(32).toString('hex'),
+        isConfirmed: false,
+      }
+      let newUser = new app.locals.models.User(userBody)
+      newUser = await newUser.save()
+
+      let emailType = 'invite-editor-in-chief'
+      if (collection) {
+        let permissions, group, name
+        switch (newUser.roles[0]) {
+          case 'handlingEditor':
+            emailType = 'invite-handling-editor'
+            permissions = 'editor'
+            group = 'editor'
+            name = 'Handling Editor'
+            break
+          case 'reviewer':
+            emailType = 'invite-reviewer'
+            permissions = 'reviewer'
+            group = 'reviewer'
+            name = 'Reviewer'
+            break
+          default:
+            break
+        }
+
+        const teamBody = {
+          teamType: {
+            name: newUser.roles[0],
+            permissions,
+          },
+          group,
+          name,
+          object: {
+            type: 'collection',
+            id: collection.id,
+          },
+          members: [newUser.id],
+        }
+        const team = new app.locals.models.Team(teamBody)
+        await team.save()
+      }
+
+      await mailService.setupEmail(
+        newUser.email,
+        emailType,
+        newUser.passwordResetToken,
+      )
+
+      res.status(200).json(newUser)
+    }
+  })
+  app.post(
+    '/api/users/invite/password/reset',
+    bodyParser.json(),
+    async (req, res) => {
+      const {
+        token,
+        password,
+        email,
+        firstName,
+        lastName,
+        middleName,
+        affiliation,
+        position,
+        title,
+      } = req.body
+
+      if (
+        !checkForUndefinedParams(token, password, email, firstName, lastName)
+      ) {
+        res.status(400).json({ error: 'missing required params' })
+        return
+      }
+
+      const updateFields = {
+        password,
+        firstName,
+        lastName,
+        middleName,
+        affiliation,
+        position,
+        title,
+        isConfirmed: true,
+      }
+
+      try {
+        const user = await app.locals.models.User.findByEmail(email)
+        if (user) {
+          if (token !== user.passwordResetToken) {
+            res.status(400).json({ error: 'invalid request' })
+            logger.error('admin pw reset tokens do not match')
+            return
+          }
+
+          let newUser = Object.assign(user, updateFields, user)
+          delete newUser.passwordResetToken
+
+          newUser = await newUser.save()
+          res.status(200).json(newUser)
+        }
+      } catch (e) {
+        if (e.name === 'NotFoundError') {
+          res.status(404).json({ error: 'user not found' })
+          logger.error('admin pw reset on non-existing user')
+        } else if (e.name === 'ValidationError') {
+          res.status(400).json({ error: e.details[0].message })
+          logger.error('admin pw reset validation error')
+        }
+        res.status(400).json({ error: e })
+        logger.error(e)
+      }
+    },
+  )
+}
+
+const checkForUndefinedParams = (...params) => {
+  if (params.includes(undefined)) {
+    return false
+  }
+
+  return true
+}
+
+module.exports = Invite
diff --git a/packages/component-mail-service/src/Mail.js b/packages/component-mail-service/src/Mail.js
index 23644ec83..2b053dfda 100644
--- a/packages/component-mail-service/src/Mail.js
+++ b/packages/component-mail-service/src/Mail.js
@@ -4,7 +4,7 @@ const querystring = require('querystring')
 const SES = require('pubsweet-components-aws-ses')
 const config = require('config')
 
-const resetUrl = config.get('admin-reset-password.url')
+const resetUrl = config.get('invite-reset-password.url')
 
 module.exports = {
   setupEmail: async (email, emailType, token, comment = '') => {
@@ -37,14 +37,11 @@ module.exports = {
   },
 }
 
-const readFile = path => {
-  const file = fs.readFileSync(path, { encoding: 'utf-8' }, (err, file) => {
+const readFile = path =>
+  fs.readFileSync(path, { encoding: 'utf-8' }, (err, file) => {
     if (err) {
       throw err
     } else {
       return file
     }
   })
-
-  return file
-}
diff --git a/packages/xpub-faraday/config/components.json b/packages/xpub-faraday/config/components.json
index d64253bf2..0219f3fda 100644
--- a/packages/xpub-faraday/config/components.json
+++ b/packages/xpub-faraday/config/components.json
@@ -7,5 +7,5 @@
   "pubsweet-component-wizard",
   "pubsweet-components-faraday",
   "pubsweet-components-aws-s3",
-  "pubsweet-component-admin"
+  "pubsweet-component-invite"
 ]
diff --git a/packages/xpub-faraday/config/default.js b/packages/xpub-faraday/config/default.js
index d3eb301bd..8c441a8ed 100644
--- a/packages/xpub-faraday/config/default.js
+++ b/packages/xpub-faraday/config/default.js
@@ -60,10 +60,10 @@ module.exports = {
     region: process.env.AWS_SES_REGION,
     sender: process.env.EMAIL_SENDER,
   },
-  'admin-reset-password': {
+  'invite-reset-password': {
     url:
-      process.env.PUBSWEET_ADMIN_PASSWORD_RESET_URL ||
-      'http://localhost:3000/admin/password-reset',
+      process.env.PUBSWEET_INVITE_PASSWORD_RESET_URL ||
+      'http://localhost:3000/invite/password-reset',
   },
   publicKeys: [
     'pubsweet-client',
diff --git a/packages/xpub-faraday/config/validations.js b/packages/xpub-faraday/config/validations.js
index 8b98883df..879eeb43e 100644
--- a/packages/xpub-faraday/config/validations.js
+++ b/packages/xpub-faraday/config/validations.js
@@ -86,8 +86,8 @@ module.exports = {
     },
   ],
   user: {
-    name: Joi.string(), // TODO: add "name" to the login form
-    roles: Joi.object(),
+    name: Joi.string(),
+    roles: Joi.array(),
     isConfirmed: Joi.boolean(),
     firstName: Joi.string().allow(''),
     lastName: Joi.string().allow(''),
diff --git a/packages/xpub-faraday/package.json b/packages/xpub-faraday/package.json
index e3b80d41b..951446c58 100644
--- a/packages/xpub-faraday/package.json
+++ b/packages/xpub-faraday/package.json
@@ -31,7 +31,7 @@
     "pubsweet-component-xpub-submit": "^0.0.2",
     "pubsweet-server": "1.0.5",
     "pubsweet-components-aws-s3": "^0.0.1",
-    "pubsweet-component-admin": "^0.0.1",
+    "pubsweet-component-invite": "^0.0.1",
     "react": "^15.6.1",
     "react-dnd": "^2.5.4",
     "react-dnd-html5-backend": "^2.5.4",
-- 
GitLab