summaryrefslogtreecommitdiffstats
path: root/server/lib/authentication.js
diff options
context:
space:
mode:
authorJannik Schönartz2019-03-04 01:14:38 +0100
committerJannik Schönartz2019-03-04 01:14:38 +0100
commit6471511909de79c1f3739ba9d6a5b45b7eb1fadb (patch)
treeb0702eae88cea3ce8fff89f1fa2f91849e79e1ee /server/lib/authentication.js
parent[webapp] add option to disable all animations (diff)
downloadbas-6471511909de79c1f3739ba9d6a5b45b7eb1fadb.tar.gz
bas-6471511909de79c1f3739ba9d6a5b45b7eb1fadb.tar.xz
bas-6471511909de79c1f3739ba9d6a5b45b7eb1fadb.zip
[authentication] Restructure api to match our new error code standard
Moved most of the res.send from the lib to the api Fixed frontend to match the new api
Diffstat (limited to 'server/lib/authentication.js')
-rw-r--r--server/lib/authentication.js170
1 files changed, 66 insertions, 104 deletions
diff --git a/server/lib/authentication.js b/server/lib/authentication.js
index dcbe880..58ae73c 100644
--- a/server/lib/authentication.js
+++ b/server/lib/authentication.js
@@ -6,119 +6,81 @@ var db = require(path.join(__appdir, 'lib', 'sequelize'))
var securePassword = require('secure-password')
var pwd = securePassword()
-module.exports = { loginCookie, loginToken, logout, verifyToken, signup, changePassword, validateEmail, validateUsername }
-
-// Authentifivation method for the frontend using secure httpOnly cookies. (POST)
-async function loginCookie (req, res) {
- var params = req.body
- var result = await verifyUser(res, params.username, params.password)
- if (result.status !== 'SUCCESS') return res.status(401).send(result)
-
- // 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 = result.data.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)
-async function loginToken (req, res) {
- var body = req.body
-
- var result = await verifyUser(res, body.username, body.password)
- if (result.status !== 'SUCCESS') return res.status(401).send(result)
- const token = result.data.token
- return res.status(200).send({ auth: true, token })
-}
+module.exports = { verifyUser, verifyToken, signup, changePassword, validateEmail, validateUsername }
// Method for creating a new user.
-async function signup (req, res) {
+async function signup (user) {
// 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(400).send({ auth: false, status: 'USER_MISSING', error_message: 'This service requires an username.' })
- if (!validateUsername(params.username)) return res.status(400).send({ auth: false, status: 'INVALID_USERNAME', error_message: 'Username does not fullfill the requirements. (No whitespaces)' })
- if (!params.password) return res.status(400).send({ auth: false, status: 'PASSWORD_MISSING', error_message: 'This services requires a password.' })
+ 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: params.username } })
+ let userDb = await db.user.findOne({ where: { username: user.username } })
// User exists validation.
- if (userDb) return res.status(500).send({ auth: false, status: 'USER_ALREADY_EXISTS', error_message: 'The provided username already exists.' })
+ if (userDb) return { code: 500, error: 'USER_ALREADY_EXISTS', message: 'The provided username already exists.' }
// Password requirements validation.
- if (!validatePassword(params.password)) return res.status(400).send({ auth: false, status: 'PASSWORD_REQUIREMENTS', error_message: 'The password requirements are not fullfilled.' })
+ if (!validatePassword(user.password)) return { code: 400, error: 'PASSWORD_REQUIREMENTS', 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)
+ // 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 res.status(500).send({ auth: false, status: 'PASSWORD_HASH_ERROR', error_message: 'Hashing the password failed.' })
+ 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: params.username, password: hash, email: params.email, name: params.name })
+ 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(res, userPassword, hash, userId)
- if (result.status !== 'SUCCESS') return res.status(401).send(result)
-
- return newUser
+ var result = await verifyHash(userPassword, hash, userId)
+ if (result.error) return result
+ result.id = userId
+ return result
}
-// 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.
-}
-
-async function changePassword (req, res) {
+async function changePassword (id, password, passwordCurrent = '') {
// 1. Get the user and verify it's existence.
- let user = await db.user.findOne({ where: { id: req.params.id } })
- if (!user) return res.send({ status: 'INVALID_USER', error_message: 'There is no user with the provided id.' })
+ 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(req.body.password)
+ 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 (req.body.passwordCurrent) {
+ if (passwordCurrent) {
// Verify the current hast with the provided current password.
- const pwCurrent = Buffer.from(req.body.passwordCurrent)
- var result = await verifyHash(res, pwCurrent, Buffer.from(user.password), user.id)
- if (result.status !== 'SUCCESS') return res.status(400).send(result)
+ 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(req.body.password)) return res.status(400).send({ status: 'PASSWORD_REQUIREMENTS', error_message: 'The provided password doesn\'t fullfill 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 res.status(500).send({ auth: false, status: 'PASSWORD_HASH_ERROR', error_message: 'Hashing the password failed.' })
+ return { code: 500, error: 'PASSWORD_HASH_ERROR', message: 'Hashing the password failed.' }
}
- // 5. Write the hash in the dbW
+ // 5. Write the hash in the db.
await user.update({ password: hash })
// 6. Verify & improving the hash.
- await verifyHash(res, pwNew, hash, user.id)
- res.status(200).send({ auth: true, status: 'VALID' })
+ const res = await verifyHash(pwNew, hash, user.id)
+ return res
}
// Middleware function.
@@ -135,14 +97,14 @@ function verifyToken (req, res, next) {
} 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({ auth: false, status: 'TOKEN_MISSING', error_message: 'This service requires a token.' })
+ 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({ auth: false, status: 'TOKEN_INVALID', error_message: 'The provided token is invalid.' })
+ 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
@@ -153,85 +115,85 @@ function verifyToken (req, res, next) {
db.user.findOne({ where: { id: req.user.id } }).then(user => {
if (user) next()
else {
- if (res) return res.status(401).send({ auth: false, status: 'TOKEN_INVALID', error_message: 'The token is from an invalid userid.' })
+ 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'))
}
})
})
}
-// 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 function for verifying a user.
-async function verifyUser (res, username, password) {
- if (!username) return { auth: false, status: 'USER_MISSING', error_message: 'This service requires an username.' }
- if (!password) return { auth: false, status: 'PASSWORD_MISSING', error_message: 'This services requires a password.' }
+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 { auth: false, status: 'USER_NOTFOUND', error_message: 'User does not exist.' }
- }
+ 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(res, userPassword, hash, user.id)
- if (result.status !== 'SUCCESS') return result
+ 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 { auth: false, status: 'JWT_ERROR', error_message: 'Jwt sign failed.' }
+ return { code: 500, error: 'JWT_ERROR', message: 'Jwt sign failed.' }
}
- return { auth: true, status: 'SUCCESS', data: { token: token } }
+ 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 (res, password, hash, userId) {
+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 { auth: false, status: 'DATABASE_HASH_INVALID', error_message: 'The hash in the database is corrupted.' }
+ 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 { auth: false, status: 'PASSWORD_INVALID', error_message: 'The provided password has an invalid length.' }
+ if (password.length < securePassword.PASSWORD_BYTES_MIN || password.length > securePassword.PASSWORD_BYTES_MAX) return { code: 401, error: 'PASSWORD_INVALID', message: 'The provided password has an invalid length.' }
// Verification of the password. Rehash if needed.
try {
var result = await pwd.verify(password, hash)
} catch (error) {
- return { auth: false, status: 'PASSWORD_VERIFY_ERROR', error_message: 'Verifying the password failed.' }
+ 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 { auth: false, status: 'INVALID_UNRECOGNIZED_HASH', error_message: 'This hash was not made with secure-password. Attempt legacy algorithm.' }
- if (result === securePassword.INVALID) return { auth: false, status: 'PASSWORD_INVALID', error_message: 'The provided password is invalid.' }
- if (result === securePassword.VALID) return { status: 'SUCCESS' }
+ 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 { auth: false, status: 'PASSWORD_REHASH_ERROR', error_message: 'Rehashing the password failed.' }
+ 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 { status: 'SUCCESS' }
+ return { code: 200 }
}
}