summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJannik Schönartz2019-02-26 08:18:52 +0100
committerJannik Schönartz2019-02-26 08:18:52 +0100
commitdcd82e1c5847151678ae7ffc982b4595304c1eeb (patch)
treed3a7ea5a6390e0c1c2bb49ef24f31b1ddec50606
parent[webapp/configurator] disable touch swipe tabs switching (diff)
downloadbas-dcd82e1c5847151678ae7ffc982b4595304c1eeb.tar.gz
bas-dcd82e1c5847151678ae7ffc982b4595304c1eeb.tar.xz
bas-dcd82e1c5847151678ae7ffc982b4595304c1eeb.zip
[authentication] Rewrite code in async/await, fix edit account module
-rw-r--r--server/api/authentication.js19
-rw-r--r--server/api/users.js74
-rw-r--r--server/lib/authentication.js143
-rw-r--r--webapp/src/components/StartPageSetup.vue9
4 files changed, 132 insertions, 113 deletions
diff --git a/server/api/authentication.js b/server/api/authentication.js
index 60b04f9..60b08a1 100644
--- a/server/api/authentication.js
+++ b/server/api/authentication.js
@@ -2,7 +2,8 @@
const path = require('path')
var db = require(path.join(__appdir, 'lib', 'sequelize'))
var express = require('express')
-var noAuthRouter = express.Router()
+const { decorateApp } = require('@awaitjs/express')
+var noAuthRouter = decorateApp(express.Router())
var authentication = require(path.join(__appdir, 'lib', 'authentication'))
// Setup method for checking if setup is possible.
@@ -26,11 +27,17 @@ noAuthRouter.post('/logout', (req, res) => {
})
// Setup method for creating the initial root account.
-noAuthRouter.post('/setup', (req, res) => {
- db.user.findAll().then(users => {
- if (users.length > 0) res.status(403).send({ status: 'USERTABLE_NOT_EMPTY', error_message: 'The user table is not empty, unauthorized creation is forbidden.' })
- else authentication.signup(req, res)
- })
+noAuthRouter.postAsync('/setup', async (req, res) => {
+ const users = await db.user.findAll()
+ if (users.length > 0) res.status(403).send({ status: 'USERTABLE_NOT_EMPTY', error_message: 'The user table is not empty, unauthorized creation is forbidden.' })
+ else {
+ const user = await authentication.signup(req, res)
+ const roleDb = await db.role.create({ name: user.username, descr: 'Superadmin' })
+ const permission = await db.permission.findOne({ where: { name: 'superadmin' } })
+ await roleDb.addPermissions(permission.id)
+ await user.addRoles(roleDb.id)
+ res.status(200).send({ auth: true, status: 'VALID' })
+ }
})
module.exports.noAuthRouter = noAuthRouter
diff --git a/server/api/users.js b/server/api/users.js
index c5eb822..a754155 100644
--- a/server/api/users.js
+++ b/server/api/users.js
@@ -63,32 +63,52 @@ router.postAsync('/:id/roles', async (req, res) => {
// Post request for creating new user accounts.
router.postAsync(['/', '/:id'], async (req, res) => {
+ if (req.params.id !== 'current') {
+ // TODO: Check for permission to delete / create / update user
+ }
+
if (req.query.delete !== undefined && req.query.delete !== 'false') {
const count = await db.user.destroy({ where: { id: req.body.ids } })
- res.status(200).send({ count })
+ return res.status(200).send({ count })
+ }
+
+ if (req.params.id === undefined) {
+ await authentication.signup(req, res)
+ return res.status(200).send({ auth: true, status: 'VALID' })
} else {
- if (req.params.id === undefined) return authentication.signup(req, res)
- else {
- let user
- user = await db.user.findOne({ where: { id: req.params.id } })
- if (user) {
- await user.update({
- username: req.body.username,
- name: req.body.name,
- email: req.body.email
- })
-
- if (req.body.password) {
- return authentication.changePassword(req, res)
- }
+ const id = req.params.id === 'current' ? req.user.id : req.params.id
+
+ let email = req.body.email
+ if (!authentication.validateEmail(req.body.email)) return res.status(500).send({ status: 'EMAIL_INVALID', error_message: 'The provided email is invalid.' })
+
+ let user
+ user = await db.user.findOne({ where: { id: id } })
+
+ if (user) {
+ let userinfo = {
+ name: req.body.name,
+ email: email
+ }
+
+ // Check if the username is set and if it's valid.
+ let username = req.body.username
+ if (username && req.params.id !== 'current') {
+ if (!authentication.validateUsername(username)) return res.status(400).send({ auth: false, status: 'INVALID_USERNAME', error_message: 'Username does not fullfill the requirements. (No whitespaces)' })
+ userinfo.username = username
+ }
+
+ // Update the user.
+ await user.update(userinfo)
+ if (req.body.password) {
+ return authentication.changePassword(req, res)
}
- res.status(200).end()
}
+ res.status(200).end()
}
})
// Post request for changing the password.
-router.post('/:id/password', (req, res) => {
+router.postAsync('/:id/password', async (req, res) => {
// Check if passwords are set.
if (req.body.passwordCurrent && req.body.password) {
if (req.body.passwordCurrent === req.body.password) return res.status(500).send({ auth: false, status: 'PASSWORD_ERROR', error_message: 'The provided password must be different than the old password.' })
@@ -96,26 +116,6 @@ router.post('/:id/password', (req, res) => {
} else res.status(400).send({ auth: false, status: 'PASSWORD_MISSING', error_message: 'This service requires the current and the new password.' })
})
-// Post request for chaning the user info. (name, email)
-router.post('/:id', (req, res) => {
- if (req.params.id !== 'current') {
- // Check if the user has the permission for chaning those userdata. Else return.
- return res.status(500).end()
- }
- const id = req.params.id === 'current' ? req.user.id : req.params.id
-
- let email = req.body.email
- if (!authentication.validateEmail(req.body.email)) return res.status(500).send({ status: 'EMAIL_INVALID', error_message: 'The provided email is invalid.' })
- db.user.findOne({ where: { id } }).then(user => {
- user.update({
- name: req.body.name,
- email
- }).then(() => {
- res.send(200)
- })
- })
-})
-
// Function for deleting a single user
router.delete('/:id/', (req, res) => {
// Check if the user has the permission for chaning those userdata. Else return.
diff --git a/server/lib/authentication.js b/server/lib/authentication.js
index 9a91850..dcbe880 100644
--- a/server/lib/authentication.js
+++ b/server/lib/authentication.js
@@ -6,31 +6,34 @@ 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 }
+module.exports = { loginCookie, loginToken, logout, verifyToken, signup, changePassword, validateEmail, validateUsername }
// Authentifivation method for the frontend using secure httpOnly cookies. (POST)
-function loginCookie (req, res) {
+async function loginCookie (req, res) {
var params = req.body
- verifyUser(res, params.username, params.password, 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' })
- })
+ 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)
-function loginToken (req, res) {
+async function loginToken (req, res) {
var body = req.body
- verifyUser(res, body.username, body.password, function (token) {
- return res.status(200).send({ auth: true, token })
- })
+
+ 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 })
}
// Method for creating a new user.
@@ -67,8 +70,10 @@ async function signup (req, res) {
var userId = newUser.id
// Verify & improving the hash.
- await verifyHash(res, userPassword, hash, userId, () => {})
- return res.status(200).send({ auth: true, status: 'VALID' })
+ var result = await verifyHash(res, userPassword, hash, userId)
+ if (result.status !== 'SUCCESS') return res.status(401).send(result)
+
+ return newUser
}
// Logout method for the frontend. Deleting the cookies by overwriting them.
@@ -94,11 +99,12 @@ async function changePassword (req, res) {
if (req.body.passwordCurrent) {
// Verify the current hast with the provided current password.
const pwCurrent = Buffer.from(req.body.passwordCurrent)
- await verifyHash(res, pwCurrent, Buffer.from(user.password), user.id)
+ var result = await verifyHash(res, pwCurrent, Buffer.from(user.password), user.id)
+ if (result.status !== 'SUCCESS') return res.status(400).send(result)
}
// 3. Check if the new provided password fullfills the requirements
- if (!validatePassword(req.body.password)) return res.send({ status: 'PASSWORD_REQUIREMENTS', error_message: 'The provided password doesn\'t fullfill 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' })
// 4. Calculate the new password hash.
try {
@@ -168,60 +174,65 @@ function validateEmail (email) {
// ############## 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(401).send({ auth: false, status: 'USER_MISSING', error_message: 'This service requires an username.' })
- if (!password) return res.status(401).send({ auth: false, status: 'PASSWORD_MISSING', error_message: 'This services requires a password.' })
+// 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.' }
- db.user.findOne({ where: { username: username } }).then(userDb => {
- if (!userDb) {
- return res.status(401).send({ auth: false, status: 'USER_NOTFOUND', error_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.
- verifyHash(res, userPassword, hash, user.id, () => {
- jwt.sign({ user }, config.secret, { expiresIn: '12h' }, (err, token) => {
- if (err) return res.status(401).send({ auth: false, status: 'JWT_ERROR', error_message: 'Jwt sign failed.' })
- return callback(token)
- })
- })
- })
+ const userDb = await db.user.findOne({ where: { username: username } })
+ if (!userDb) {
+ return { auth: false, status: 'USER_NOTFOUND', error_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
+
+ 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 { auth: true, status: 'SUCCESS', data: { token: token } }
}
// The verify hash function from the secure-passwords with error handling.
-function verifyHash (res, password, hash, userId, callback = () => {}) {
+async function verifyHash (res, 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 res.status(401).send({ auth: false, status: 'DATABASE_HASH_INVALID', error_message: 'The hash in the database is corrupted.' })
+ if (hash.length !== securePassword.HASH_BYTES) return { auth: false, status: 'DATABASE_HASH_INVALID', error_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 res.status(401).send({ 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 { auth: false, status: 'PASSWORD_INVALID', error_message: 'The provided password has an invalid length.' }
// Verification of the password. Rehash if needed.
- pwd.verify(password, hash, function (err, result) {
- if (err) return res.status(401).send({ auth: false, status: 'PASSWORD_VERIFY_ERROR', error_message: 'Verifying the password failed.' })
-
- // Check the state of the verification.
- if (result === securePassword.INVALID_UNRECOGNIZED_HASH) return res.status(401).send({ auth: false, status: 'INVALID_UNRECOGNIZED_HASH', error_message: 'This hash was not made with secure-password. Attempt legacy algorithm.' })
- if (result === securePassword.INVALID) return res.status(401).send({ auth: false, status: 'PASSWORD_INVALID', error_message: 'The provided password is invalid.' })
- if (result === securePassword.VALID) callback()
- if (result === securePassword.VALID_NEEDS_REHASH) {
- pwd.hash(password, function (err, improvedHash) {
- if (err) throw err
-
- // Update the improved hash in the db.
- db.user.findOne({ where: { id: userId } }).then(user => {
- user.updateAttributes({
- password: improvedHash
- })
- return callback()
- })
- })
+ try {
+ var result = await pwd.verify(password, hash)
+ } catch (error) {
+ return { auth: false, status: 'PASSWORD_VERIFY_ERROR', 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.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.' }
}
- })
+ // Update the improved hash in the db.
+ const user = await db.user.findOne({ where: { id: userId } })
+ await user.updateAttributes({
+ password: improvedHash
+ })
+ return { status: 'SUCCESS' }
+ }
}
// Function for validating the password. Password requirements are implemented here.
diff --git a/webapp/src/components/StartPageSetup.vue b/webapp/src/components/StartPageSetup.vue
index 36ad648..1bde01c 100644
--- a/webapp/src/components/StartPageSetup.vue
+++ b/webapp/src/components/StartPageSetup.vue
@@ -33,9 +33,11 @@
<template>
<div class="setup-page">
- <label style="color: red; font-size: large">{{ $t('createRoot') }}</label>
- <signup v-model="user" ref="setupRoot"></signup>
- <v-btn @click="setup" class="setup-button primary" raised>{{ $t('signup') }}</v-btn>
+ <label class="error--text" style="font-size: large; margin-bottom: 20px;margin-top: -20px;">{{ $t('createRoot') }}</label>
+ <div class="text-xs-right">
+ <signup v-model="user" ref="setupRoot"></signup>
+ <v-btn @click="setup" class="setup-button primary" raised>{{ $t('signup') }}</v-btn>
+ </div>
</div>
</template>
@@ -94,6 +96,5 @@ export default {
.setup-button {
margin-top: 20px;
- float: right;
}
</style>