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