summaryrefslogblamecommitdiffstats
path: root/server/api/users.js
blob: 2edac8d5526e5556daafeb27f7ddcac04dfdf291 (plain) (tree)
1
2
3
4
5
6
7
8
9
                     
                          
                                                         


                                                   
                                                                          
                                                      
 
                              
                                                   

                                                                              
                                                                    
















                                                                                                                                                             






                                                                               
                                                                                                                              







                                                                      
                                                                                              
             
                              

                         
   




                                                                               
                                          
                                                

                                

                                                                                     
              
                                                                         







                                                     
                                                                                                                                






                                                                                                            
            
















                                                                                                                                  





                         
 
                                                         
                                                     
                       
                   
                                                                       























































                                                                                                    

   
                    
                                    

                                                    
























                                                                                              

                                        
          
                
                                                                        
                                                           

                              














                                                                                                                     


                      
                        



                                                        
                                  
                                                    











                                                                                                                                                                    

                                                
                                                                                                      











                                                                                                                               




                                    








                                                                                              



                                                                                                   






















                                                                                                                        
                                     
       
     
                         
   


                                          
                                                       
                                                                      
                       
                                




                                                                                                                                                                                   













                                                                                

                                                                                                                                

  
                                      
                                                 
                                                                      
                                                           
                                           
























                                                                                            

  

                                                                               
 
                              
/* global __appdir */
var path = require('path')
var db = require(path.join(__appdir, 'lib', 'sequelize'))
var express = require('express')
const { decorateApp } = require('@awaitjs/express')
var router = decorateApp(express.Router())
var authentication = require(path.join(__appdir, 'lib', 'authentication'))
const log = require(path.join(__appdir, 'lib', 'log'))

// Permission check middleware
router.all(['', '/:x'], async (req, res, next) => {
  // User is allowed to edit his own information even without any permissions.
  let currentInfo = false
  if (req.params.x && req.params.x === 'current') currentInfo = true

  switch (req.method) {
    case 'GET':
      if (!await req.user.hasPermission('users.view') && !currentInfo) return res.status(403).send({ error: 'Missing permission', permission: 'users.view' })
      break

    case 'POST': case 'DELETE':
      if (!await req.user.hasPermission('users.edit') && !currentInfo) return res.status(403).send({ error: 'Missing permission', permission: 'users.edit' })
      break

    default:
      return res.status(400).send()
  }

  next()
})

// ############################################################################
// ###########################  GET requests  #################################

/*
 * @return: Returns a list of all users in the database and their given roles.
 */
router.getAsync('', async (req, res) => {
  const users = await db.user.findAll({ attributes: { exclude: ['password'] }, include: ['roles'], order: [['name', 'ASC']] })
  res.status(200).send(users)
})

/*
 * @return: Returns information about a specific user.
 */
router.getAsync('/:id', async (req, res) => {
  const id = req.params.id === 'current' ? req.user.id : req.params.id
  const user = await db.user.findOne({ where: { id }, attributes: { exclude: ['password'] } })
  if (user) {
    res.status(200).send(user)
  } else {
    res.status(404).end()
  }
})

// ############################################################################
// ##########################  POST requests  #################################

// Post request for adding roles to users.
router.postAsync('/roles', async (req, res) => {
  const userIds = req.body.users
  const roleIds = req.body.roles
  const users = await db.user.findAll({ where: { id: userIds }, include: ['roles'] })
  const userDb = await db.user.findOne({ where: { id: req.user.id } })
  if (users) {
    if (req.query.delete !== undefined && req.query.delete !== 'false') {
      for (let index in users) {
        const user = users[index]
        const roles = user.roles
        const count = await user.removeRoles(roleIds)
        let roleString = 'role'
        if (count > 1) roleString += 's'
        log({
          category: 'USER_REVOKE_ROLE',
          description: '[' + user.id + '] ' + 'Successfully removed ' + count + ' ' + roleString + ' from' + user.name + '.\n' +
                       'ID: ' + user.id + '\n' +
                       'Name: ' + user.name + '\n' +
                       'Removed Roles: ' + roleIds.filter(y => { return roles.map(x => x.id).includes(y) }),
          userDb,
          userId: req.user.id
        })
      }
    } else {
      for (let index in users) {
        const user = users[index]
        const count = await user.addRoles(roleIds)
        if (count.length > 0) {
          let roleString = 'role'
          if (count[0].length > 1) roleString += 's'
          log({
            category: 'USER_GRANT_ROLE',
            description: '[' + user.id + '] ' + user.name + ': Successfully added ' + count[0].length + ' ' + roleString + '.\n' +
                         'ID: ' + user.id + '\n' +
                         'Name: ' + user.name + '\n' +
                         'Added Roles: ' + count[0].map(x => x.roleId),
            userDb,
            userId: req.user.id
          })
        }
      }
    }
    res.status(200).end()
  } else {
    res.status(404).end()
  }
})

// Post request for creating / editing new user accounts.
router.postAsync(['/', '/:id'], async (req, res) => {
  const body = req.body
  // Delete request
  if (req.query.delete !== undefined && req.query.delete !== 'false') {
    const user = await db.user.findOne({ where: { id: req.user.id } })

    // Only need to log batch request if there is more than one user to delete.
    if (req.body.ids.length > 1) {
      await log({
        category: 'USER_BATCH_DELETE',
        description: 'User batch deletion of ' + req.body.ids.length + ' users initiated by user.',
        user,
        userId: req.user.id
      })
    }

    let deletionCounter = 0
    let selfdeletion = false

    // Delete every user on its own, to get a better log
    for (let index in req.body.ids) {
      // We can't set the userId in the log if we delete ourselfs.
      if (req.body.ids[index] === req.user.id) selfdeletion = true
      const userDb = await db.user.findOne({ where: { id: req.body.ids[index] } })
      const count = await db.user.destroy({ where: { id: req.body.ids[index] } })
      if (count !== 1) {
        await log({
          category: 'ERROR_USER_DELETE',
          description: '[' + userDb.id + '] ' + userDb.username + ': User could not be deleted.\n' +
                       'ID: ' + userDb.id + '\n' +
                       'Username: ' + userDb.username + '\n' +
                       'Name: ' + userDb.name + '\n' +
                       'E-Mail: ' + userDb.email + '\n',
          user,
          userId: selfdeletion ? undefined : req.user.id
        })
      } else {
        await log({
          category: 'USER_DELETE',
          description: '[' + userDb.id + '] ' + userDb.username + ': User successfully deleted.\n' +
                       'ID: ' + userDb.id + '\n' +
                       'Username: ' + userDb.username + '\n' +
                       'Name: ' + userDb.name + '\n' +
                       'E-Mail: ' + userDb.email + '\n',
          user,
          userId: selfdeletion ? undefined : req.user.id
        })
        deletionCounter++
      }
    }
    if (req.body.ids.length > 1) {
      log({
        category: 'USER_BATCH_DELETE',
        description: deletionCounter + '/' + req.body.ids.length + ' users successfully deleted.',
        user,
        userId: selfdeletion ? undefined : req.user.id
      })
    }

    return res.send({ deletionCounter })
  }

  // Create new user
  if (req.params.id === undefined) {
    const result = await authentication.signup(body)
    const code = result.code
    const user = await db.user.findOne({ where: { id: result.id } })

    // Create a log entry for the user creation
    if (code === 200) {
      log({
        category: 'USER_CREATE',
        description: '[' + user.id + '] ' + user.username + ': User successfully created.\n' +
                     'ID: ' + user.id + '\n' +
                     'Username: ' + user.username + '\n' +
                     'Name: ' + user.name + '\n' +
                     'E-Mail: ' + user.email + '\n',
        userId: req.user.id
      })
    } else {
      log({
        category: 'ERROR_USER_CREATE',
        description: '[' + code + '][' + result.error + '] ' + result.message + '\n' +
                     'ID: ' + user.id + '\n' +
                     'Username: ' + user.username + '\n' +
                     'Name: ' + user.name + '\n' +
                     'E-Mail: ' + user.email + '\n',
        userId: req.user.id
      })
    }

    delete result.code
    return res.status(code).send(result)
  } else {
    // Edit user
    const id = req.params.id === 'current' ? req.user.id : req.params.id
    let user = await db.user.findOne({ where: { id: id } })

    let email = req.body.email
    if (!authentication.validateEmail(req.body.email)) {
      log({
        category: 'ERROR_USER_EDIT',
        description: '[' + user.id + '] ' + user.username + ': User could not be updated. The E-Mail is invalid.\n' +
                     'ID: ' + user.id + '\n' +
                     'Username: ' + user.username + '\n' +
                     'Name: ' + user.name + '\n' +
                     'E-Mail: ' + user.email + '\n',
        userId: req.user.id
      })
      return res.status(500).send({
        error: 'EMAIL_INVALID',
        message: 'The provided email is invalid.'
      })
    }

    if (user) {
      let userinfo = {
        name: body.name,
        email: email
      }

      // Check if the username is set and if it's valid.
      let username = body.username
      if (username && req.params.id !== 'current') {
        if (!authentication.validateUsername(username)) {
          log({
            category: 'ERROR_USER_EDIT',
            description: '[' + user.id + '] ' + user.username + ': User could not be updated. The username does not fullfull the requirements. (No whitespaces)\n' +
                         'ID: ' + user.id + '\n' +
                         'Username: ' + user.username + '\n' +
                         'Name: ' + user.name + '\n' +
                         'E-Mail: ' + user.email + '\n',
            userId: req.user.id
          })
          return res.status(400).send({ error: 'INVALID_USERNAME', message: 'Username does not fullfill the requirements. (No whitespaces)' })
        }

        // Check if the username already exists.
        let userDb = await db.user.findOne({ where: { username: username, id: { [db.Op.not]: id } } })
        if (userDb) {
          log({
            category: 'ERROR_USER_EDIT',
            description: '[' + user.id + '] ' + user.username + ': User could not be updated. The username already exists.\n' +
                         'ID: ' + user.id + '\n' +
                         'Username: ' + user.username + '\n' +
                         'Name: ' + user.name + '\n' +
                         'E-Mail: ' + user.email + '\n',
            userId: req.user.id
          })
          return res.status(400).send({ error: 'USER_ALREADY_EXISTS', message: 'The provided username already exists.' })
        }
        userinfo.username = username
      }

      // Update the user.
      await user.update(userinfo)
      log({
        category: 'USER_EDIT',
        description: '[' + user.id + '] ' + user.username + ': User successfully updated.\n' +
                     'ID: ' + user.id + '\n' +
                     'Username: ' + user.username + '\n' +
                     'Name: ' + user.name + '\n' +
                     'E-Mail: ' + user.email + '\n',
        userId: req.user.id
      })
      if (body.password) {
        const result = await authentication.changePassword(id, body.password, body.passwordCurrent)
        const code = result.code
        delete result.code

        if (code === 200) {
          log({
            category: 'USER_EDIT_PASSWORD',
            description: '[' + user.id + '] ' + user.username + ': Password successfully updated.\n' +
                         'ID: ' + user.id + '\n' +
                         'Username: ' + user.username + '\n' +
                         'Name: ' + user.name + '\n' +
                         'E-Mail: ' + user.email + '\n',
            userId: req.user.id
          })
        } else {
          log({
            category: 'ERROR_USER_EDIT_PASSWORD',
            description: '[' + user.id + '] ' + user.username + ': Password could not be updated. Code ' + code + '\n' +
                         'ID: ' + user.id + '\n' +
                         'Username: ' + user.username + '\n' +
                         'Name: ' + user.name + '\n' +
                         'E-Mail: ' + user.email + '\n',
            userId: req.user.id
          })
        }

        res.status(code).send(result)
      }
    }
    res.status(200).end()
  }
})

// Post request for changing the password.
router.postAsync('/:id/password', async (req, res) => {
  const id = req.params.id === 'current' ? req.user.id : req.params.id
  const body = req.body
  // Check if passwords are set.
  if (body.passwordCurrent && body.password) {
    if (body.passwordCurrent === body.password) return res.status(500).send({ error: 'PASSWORD_ERROR', message: 'The provided password must be different than the old password.' })
    const result = await authentication.changePassword(id, body.password, body.passwordCurrent)
    const code = result.code
    delete result.code

    if (code === 200) {
      log({
        category: 'USER_EDIT_PASSWORD',
        description: '[' + id + '] Password changed.',
        userId: req.user.id
      })
    } else {
      log({
        category: 'ERROR_USER_EDIT_PASSWORD',
        description: '[' + id + '] Password could not be changed. Code ' + code,
        userId: req.user.id
      })
    }
    res.status(code).send(result)
  } else res.status(400).send({ error: 'PASSWORD_MISSING', message: 'This service requires the current and the new password.' })
})

// Function for deleting a single user
router.deleteAsync('/:id/', async (req, res) => {
  const id = req.params.id === 'current' ? req.user.id : req.params.id
  const user = await db.user.findOne({ where: { id: id } })
  // Every user can delete his own account.
  const count = await db.user.destroy({ where: { id } })
  if (count !== 1) {
    log({
      category: 'ERROR_USER_DELETE',
      description: '[' + user.id + '] ' + user.username + ': User could not be deleted.\n' +
                   'ID: ' + user.id + '\n' +
                   'Username: ' + user.username + '\n' +
                   'Name: ' + user.name + '\n' +
                   'E-Mail: ' + user.email + '\n',
      user,
      userId: req.user.id
    })
  } else {
    log({
      category: 'USER_DELETE',
      description: '[' + user.id + '] ' + user.username + ': User successfully deleted.\n' +
                   'ID: ' + user.id + '\n' +
                   'Username: ' + user.username + '\n' +
                   'Name: ' + user.name + '\n' +
                   'E-Mail: ' + user.email + '\n',
      user,
      userId: req.params.id === 'current' ? undefined : req.user.id
    })
  }
  res.status(200).end()
})

// ############################################################################
// ############################################################################

module.exports.router = router