From 892a048d072d05886951bcb92e6b61c2094a6463 Mon Sep 17 00:00:00 2001 From: Jannik Schönartz Date: Fri, 22 Feb 2019 01:59:26 +0000 Subject: [authentication] Implement initial root account setup [backend] Reworked authentication library to the api structure Add authentication api to remove the login routes from the router.js [webapp] Split login Page in StartPage + Login/Setup Add Setup Page for the initial root creation --- server/lib/authentication.js | 206 +++++++++++++++++++++++-------------------- 1 file changed, 109 insertions(+), 97 deletions(-) (limited to 'server/lib/authentication.js') diff --git a/server/lib/authentication.js b/server/lib/authentication.js index 58d5e10..76e8b60 100644 --- a/server/lib/authentication.js +++ b/server/lib/authentication.js @@ -6,110 +6,121 @@ var db = require(path.join(__appdir, 'lib', 'sequelize')) var securePassword = require('secure-password') var pwd = securePassword() -module.exports = { - // Authentifivation method for the frontend using secure httpOnly cookies. (POST) - login: function (req, res) { - var params = req.body - - verifyUser(res, params.username, params.password, function (token) { - // The token has the form header.payload.signature - // We split the cookie in header.payload and signature in two seperate cookies. - // The signature cookie is httpOnly so JavaScript never has access to the full cookie. - // Read more at: https://medium.com/lightrail/getting-token-authentication-right-in-a-stateless-single-page-application-57d0c6474e3 - const split = token.split('.') - const headerPayload = split[0] + '.' + split[1] - const signature = split[2] - res.cookie('jwt_hp', headerPayload, { secure: true, httpOnly: false, sameSite: 'strict' }) - res.cookie('jwt_s', signature, { secure: true, httpOnly: true, sameSite: 'strict' }) - return res.status(200).send({ auth: true, status: 'VALID' }) - }) - }, - // Authentification method for the API using the authorization header. (GET) - auth: function (req, res) { - var query = req.query +module.exports = { loginCookie, loginToken, logout, verifyToken, signup, changePassword } + +// Authentifivation method for the frontend using secure httpOnly cookies. (POST) +function loginCookie (req, res) { + var params = req.body + verifyUser(res, params.username, params.password, function (token) { + // The token has the form header.payload.signature + // We split the cookie in header.payload and signature in two seperate cookies. + // The signature cookie is httpOnly so JavaScript never has access to the full cookie. + // Read more at: https://medium.com/lightrail/getting-token-authentication-right-in-a-stateless-single-page-application-57d0c6474e3 + const split = token.split('.') + const headerPayload = split[0] + '.' + split[1] + const signature = split[2] + res.cookie('jwt_hp', headerPayload, { secure: true, httpOnly: false, sameSite: 'strict' }) + res.cookie('jwt_s', signature, { secure: true, httpOnly: true, sameSite: 'strict' }) + return res.status(200).send({ auth: true, status: 'VALID' }) + }) +} - verifyUser(res, query.username, query.password, function (token) { - return res.status(200).send({ auth: true, token }) - }) - }, - - signup: function (req, res) { - // TODO: Implement some security stuff. Not every user who call this request should be able to sign up. - - var params = req.body - if (!params.username) return res.status(500).send({ auth: false, status: 'USER_MISSING', error_message: 'This service requires an username.' }) - if (!params.password) return res.status(500).send({ auth: false, status: 'PASSWORD_MISSING', error_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. - db.user.findOne({ where: { username: params.username } }).then(userDb => { - // User exists validation. - if (userDb) return res.status(500).send({ auth: false, status: 'USER_ALREADY_EXISTS', error_message: 'The provided username already exists.' }) - - // Password requirements validation. - if (!validatePassword(params.password)) return res.status(500).send({ auth: false, status: 'PASSWORD_REQUIREMENTS', error_message: 'The password requirements are not fullfilled.' }) - // Email validation. - if (!validateEmail(params.email)) return res.status(500).send({ auth: false, status: 'EMAIL_INVALID', error_message: 'The provided email is invalid.' }) - var userPassword = Buffer.from(params.password) - // Register user - pwd.hash(userPassword, function (err, hash) { - if (err) return res.status(500).send({ auth: false, status: 'PASSWORD_HASH_ERROR', error_message: 'Hashing the password failed.' }) - // Saving the non improved hash and creating the user in the db. - db.user.create({ username: params.username, password: hash, email: params.email, name: params.name }).then((userDb) => { - // TODO: Username could also be used because those are unique as well. - var userId = userDb.id - - // Verify & improving the hash. - verifyHash(res, userPassword, hash, userId, function () { - return res.status(200).send({ auth: true, status: 'VALID' }) - }) +// Authentification method for the API using the authorization header. (GET) +function loginToken (req, res) { + var body = req.body + verifyUser(res, body.username, body.password, function (token) { + return res.status(200).send({ auth: true, token }) + }) +} + +// Method for creating a new user. +function signup (req, res) { + // TODO: Implement some security stuff. Not every user who call this request should be able to sign up. + var params = req.body + if (!params.username) return res.status(500).send({ auth: false, status: 'USER_MISSING', error_message: 'This service requires an username.' }) + if (!params.password) return res.status(500).send({ auth: false, status: 'PASSWORD_MISSING', error_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. + db.user.findOne({ where: { username: params.username } }).then(userDb => { + // User exists validation. + if (userDb) return res.status(500).send({ auth: false, status: 'USER_ALREADY_EXISTS', error_message: 'The provided username already exists.' }) + // Password requirements validation. + if (!validatePassword(params.password)) return res.status(500).send({ auth: false, status: 'PASSWORD_REQUIREMENTS', error_message: 'The password requirements are not fullfilled.' }) + // Email validation. + if (!validateEmail(params.email)) return res.status(500).send({ auth: false, status: 'EMAIL_INVALID', error_message: 'The provided email is invalid.' }) + var userPassword = Buffer.from(params.password) + + // Register user + pwd.hash(userPassword, function (err, hash) { + if (err) return res.status(500).send({ auth: false, status: 'PASSWORD_HASH_ERROR', error_message: 'Hashing the password failed.' }) + // Saving the non improved hash and creating the user in the db. + db.user.create({ username: params.username, password: hash, email: params.email, name: params.name }).then((userDb) => { + // TODO: Username could also be used because those are unique as well. + var userId = userDb.id + // Verify & improving the hash. + verifyHash(res, userPassword, hash, userId, function () { + return res.status(200).send({ auth: true, status: 'VALID' }) }) }) }) - }, - - // Logout method for the frontend. Deleting the cookies by overwriting them. - logout: function (req, res) { - // End session properly. - res.clearCookie('jwt_hp') - res.clearCookie('jwt_s') - return res.status(200).send() - // TODO: Implement.. blacklisting for jwt's and destroy the cookies.. - // Maybe use express-jwt and use the rewoke function. - }, - - changePassword: function (req, res) { - // TODO: IMPLEMENT - }, - verifyToken: function (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(403).send({ auth: false, status: 'TOKEN_MISSING', error_message: 'This service requires a token.' }) - else return next(new Error('TOKEN_MISSING')) + }) +} + +// Logout method for the frontend. Deleting the cookies by overwriting them. +function logout (req, res) { + // End session properly. + res.clearCookie('jwt_hp') + res.clearCookie('jwt_s') + return res.status(200).send() + // TODO: Implement.. blacklisting for jwt's and destroy the cookies.. + // Maybe use express-jwt and use the rewoke function. +} + +function changePassword (req, res) { + // TODO: IMPLEMENT +} + +// 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(403).send({ auth: false, status: 'TOKEN_MISSING', error_message: 'This service requires a token.' }) + else return next(new Error('TOKEN_MISSING')) + } + + // Verify the token with the secret. + jwt.verify(token, config.secret, function (err) { + if (err) { + if (res) return res.status(500).send({ auth: false, status: 'TOKEN_INVALID', error_message: 'The provided token is invalid.' }) + else return next(new Error('TOKEN_INVALID')) } - // Verify the token with the secret. - jwt.verify(token, config.secret, function (err) { - if (err) { - if (res) return res.status(500).send({ auth: false, status: 'TOKEN_INVALID', error_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 } - - next() + 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 return res.status(500).send({ auth: false, status: 'TOKEN_INVALID', error_message: 'The token is from an invalid userid.' }) }) - } + }) } +// ################################################ +// ############## Helper function ################# +// ################################################ + // The function for verifying a user. Callback only gets called if the user gets verified. function verifyUser (res, username, password, callback) { if (!username) return res.status(500).send({ auth: false, status: 'USER_MISSING', error_message: 'This service requires an username.' }) @@ -175,6 +186,7 @@ function validateEmail (email) { // Function for validating the password. Password requirements are implemented here. function validatePassword (password) { - // TODO: implement pw requirements like in the frontend. + // TODO: implement pw requirements like in the frontend. (SetupPage) + if (password.length < 8) return false return true } -- cgit v1.2.3-55-g7522