/* global __appdir */ var jwt = require('jsonwebtoken') var path = require('path') var config = require(path.join(__appdir, 'config', 'authentication')) var db = require(path.join(__appdir, 'lib', 'sequelize')) var securePassword = require('secure-password') var pwd = securePassword() module.exports = { verifyUser, verifyToken, signup, changePassword, validateEmail, validateUsername } // Method for creating a new user. async function signup (user) { // TODO: Implement some security stuff. Not every user who call this request should be able to sign up. if (!user.username) return { code: 400, error: 'USER_MISSING', message: 'This service requires an username.' } if (!validateUsername(user.username)) return { code: 400, error: 'INVALID_USERNAME', message: 'Username does not fullfill the requirements. (No whitespaces)' } if (!user.password) return { code: 400, error: 'PASSWORD_MISSING', message: 'This services requires a password.' } // if (!params.email) return res.status(500).send({ auth: false, status: 'EMAIL_MISSING', error_message: 'This services requires an email.' }) // Database and user validation. let userDb = await db.user.findOne({ where: { username: user.username } }) // User exists validation. if (userDb) return { code: 500, error: 'USER_ALREADY_EXISTS', message: 'The provided username already exists.' } // Password requirements validation. if (!validatePassword(user.password)) return { code: 400, error: 'PASSWORD_REQUIREMENTS', message: 'The password requirements are not fullfilled.' } // Email validation. // if (!validateEmail(params.email)) return { code: 400, error: 'EMAIL_INVALID', message: 'The provided email is invalid.' } var userPassword = Buffer.from(user.password) // Register user try { var hash = await pwd.hash(userPassword) } catch (error) { return { code: 500, error: 'PASSWORD_HASH_ERROR', message: 'Hashing the password failed.' } } // Saving the non improved hash and creating the user in the db. const newUser = await db.user.create({ username: user.username, password: hash, email: user.email, name: user.name }) if (!newUser) return { code: 500, error: 'USER_CREATE_ERROR', message: 'User could not be created.' } // TODO: Username could also be used because those are unique as well. var userId = newUser.id // Verify & improving the hash. var result = await verifyHash(userPassword, hash, userId) if (result.error) return result result.id = userId return result } async function changePassword (id, password, passwordCurrent = '') { // 1. Get the user and verify it's existence. let user = await db.user.findOne({ where: { id: id } }) if (!user) return { code: 404, error: 'USER_NOTFOUND', message: 'There is no user with the provided id.' } const pwNew = Buffer.from(password) // 2. Only if the current password is set we have to check if it's valid. // This is because root can set passwords witout having the old ones. // But the authentication if you can call this function without the currentPasswords needs to be in the API. if (passwordCurrent) { // Verify the current hast with the provided current password. const pwCurrent = Buffer.from(passwordCurrent) var result = await verifyHash(pwCurrent, Buffer.from(user.password), user.id) if (result.error) return result } // 3. Check if the new provided password fullfills the requirements if (!validatePassword(password)) return { code: 400, error: 'PASSWORD_REQUIREMENTS', message: 'The provided password doesn\'t fullfill the requirements' } // 4. Calculate the new password hash. try { var hash = await pwd.hash(pwNew) } catch (error) { return { code: 500, error: 'PASSWORD_HASH_ERROR', message: 'Hashing the password failed.' } } // 5. Write the hash in the db. await user.update({ password: hash }) // 6. Verify & improving the hash. const res = await verifyHash(pwNew, hash, user.id) return res } // Middleware function. // Verifies the token given in the request either by the authorization header or jwt cookies. function verifyToken (req, res, next) { var token = '' // Check for the token in the authorization header or in the cookies. Else return with auth: false. if (req.headers['authorization']) { var authorization = req.headers['authorization'] // Authorization: Bearer // Split the bearer token. const bearer = authorization.split(' ') token = bearer[1] } else if (req.cookies.jwt_hp && req.cookies.jwt_s) { token = req.cookies.jwt_hp + '.' + req.cookies.jwt_s } else { if (res) return res.status(401).send({ error: 'TOKEN_MISSING', message: 'This service requires a token.' }) else return next(new Error('TOKEN_MISSING')) } // Verify the token with the secret. jwt.verify(token, config.secret, err => { if (err) { if (res) return res.status(401).send({ error: 'TOKEN_INVALID', message: 'The provided token is invalid.' }) else return next(new Error('TOKEN_INVALID')) } req.token = token const decodedToken = jwt.decode(token, { complete: true }) req.user = { id: decodedToken.payload.user.id } // Check weather the user exists. db.user.findOne({ where: { id: req.user.id } }).then(user => { if (user) next() else { if (res) return res.status(401).send({ error: 'TOKEN_INVALID', message: 'The token is from an invalid userid.' }) else return next(new Error('TOKEN_INVALID')) } }) }) } // The function for verifying a user. async function verifyUser (username, password) { if (!username) return { code: 400, error: 'USER_MISSING', message: 'This service requires an username.' } if (!password) return { code: 400, error: 'PASSWORD_MISSING', message: 'This services requires a password.' } const userDb = await db.user.findOne({ where: { username: username } }) if (!userDb) return { code: 404, error: 'USER_NOTFOUND', message: 'User does not exist.' } var user = {} user.id = userDb.id var userPassword = Buffer.from(password) var hash = Buffer.from(userDb.password) // Verify & improving the hash. var result = await verifyHash(userPassword, hash, user.id) if (result.error) return result try { var token = await jwt.sign({ user }, config.secret, { expiresIn: '12h' }) } catch (error) { return { code: 500, error: 'JWT_ERROR', message: 'Jwt sign failed.' } } result.token = token return result } // Function for validating the e-mail. function validateEmail (email) { // TODO: Remove if email is not optional if (email === '') return true // Removed escape before [ because eslint told me so. var re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ return re.test(String(email).toLowerCase()) } // ################################################ // ############## Helper function ################# // ################################################ // The verify hash function from the secure-passwords with error handling. async function verifyHash (password, hash, userId) { // Check if the hash in the database fullfills the requirements needed for pwd.verify. // Hash will be a Buffer of length SecurePassword.HASH_BYTES. if (hash.length !== securePassword.HASH_BYTES) return { code: 500, error: 'DATABASE_HASH_INVALID', message: 'The hash in the database is corrupted.' } // Password must be a Buffer of length SecurePassword.PASSWORD_BYTES_MIN - SecurePassword.PASSWORD_BYTES_MAX. if (password.length < securePassword.PASSWORD_BYTES_MIN || password.length > securePassword.PASSWORD_BYTES_MAX) return { code: 401, error: 'PASSWORD_INVALID', message: 'The provided password is invalid.' } // Verification of the password. Rehash if needed. try { var result = await pwd.verify(password, hash) } catch (error) { return { code: 500, error: 'PASSWORD_VERIFY_ERROR', message: 'Verifying the password failed.' } } // Check the state of the verification. if (result === securePassword.INVALID_UNRECOGNIZED_HASH) return { code: 500, error: 'INVALID_UNRECOGNIZED_HASH', message: 'This hash was not made with secure-password. Attempt legacy algorithm.' } if (result === securePassword.INVALID) return { code: 401, error: 'PASSWORD_INVALID', message: 'The provided password is invalid.' } if (result === securePassword.VALID) return { code: 200 } if (result === securePassword.VALID_NEEDS_REHASH) { try { var improvedHash = await pwd.hash(password) } catch (error) { return { code: 500, error: 'PASSWORD_REHASH_ERROR', message: 'Rehashing the password failed.' } } // Update the improved hash in the db. const user = await db.user.findOne({ where: { id: userId } }) await user.updateAttributes({ password: improvedHash }) return { code: 200 } } } // Function for validating the password. Password requirements are implemented here. function validatePassword (password) { // TODO: implement pw requirements like in the frontend. (SetupPage) if (password.length < 8) return false return true } // Function for validating the username. Username requirements are implemented here. function validateUsername (username) { // Disallow whitespaces return !/\s/.test(username) }