summaryrefslogtreecommitdiffstats
path: root/server/lib/authentication.js
diff options
context:
space:
mode:
authorJannik Schönartz2018-07-02 21:52:25 +0200
committerJannik Schönartz2018-07-02 21:52:25 +0200
commitaa4e552a03657a63922f5cd085431257c183f458 (patch)
treefc8cd63129bb54b06326c11847a9731fe70f1b33 /server/lib/authentication.js
downloadbas-aa4e552a03657a63922f5cd085431257c183f458.tar.gz
bas-aa4e552a03657a63922f5cd085431257c183f458.tar.xz
bas-aa4e552a03657a63922f5cd085431257c183f458.zip
[server] Initial commit to add the node server stuff.
Diffstat (limited to 'server/lib/authentication.js')
-rw-r--r--server/lib/authentication.js203
1 files changed, 203 insertions, 0 deletions
diff --git a/server/lib/authentication.js b/server/lib/authentication.js
new file mode 100644
index 0000000..3a4dccd
--- /dev/null
+++ b/server/lib/authentication.js
@@ -0,0 +1,203 @@
+/* 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', 'database')).connectionPool;
+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;
+
+ 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.
+ //SEQ//db.query('SELECT * FROM users WHERE username = ?', [params.username], function(err, rows) {
+ db.user.findOne({ where: { username: params.username } }).then(user_db => {
+ //SEQ//if (err) return res.status(500).send({ auth: false, status: 'DATABASE_ERROR', error_message: 'SQL query failed.' });
+
+ // User exists validation.
+ //SEQ//if (rows.length) return res.status(500).send({ auth: false, status: 'USER_ALREADY_EXISTS', error_message: 'The provided username already exists.' });
+ if (user_db) 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.
+ //SEQ//var att = [params.username, hash, params.email, params.name];
+ //SEQ//db.query('INSERT INTO users (username, password, email, name) VALUES (?)', [att], function(err, result) {
+ db.user.create({ username: params.username, password: hash, email: params.email, name: params.name }).then((user_db) => {
+ //SEQ//if (err) return res.status(500).send({ auth: false, status: 'DATABASE_INSERT_ERROR', error_message: 'Inserting the user in the database failed.' });
+ // TODO: Username could also be used because those are unique as well.
+ //SEQ//var userId = result.insertId;
+ var userId = user_db.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 <token>
+ // 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 {
+ return res.status(403).send({ auth: false, status: 'TOKEN_MISSING', error_message: 'This service requires a token.' });
+ }
+ // Verify the token with the secret.
+ jwt.verify(token, config.secret, function(err) {
+ if (err) return res.status(500).send({ auth: false, status: 'TOKEN_INVALID', error_message: 'The provided token is invalid.' });
+ req.token = token;
+ next();
+ });
+ }
+};
+
+// 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.' });
+ if (!password) return res.status(500).send({ auth: false, status: 'PASSWORD_MISSING', error_message: 'This services requires a password.' });
+
+ //SEQ//db.query('SELECT * FROM users WHERE username = ?', [username], function(err, rows) {
+ db.user.findOne({ where: { username: username } }).then(user_db => {
+ //SEQ//if (err) return res.status(500).send({ auth: false, status: 'DATABASE_ERROR', error_message: 'Database connection failed.' });
+ //SEQ//if (rows.length != 1) {
+ //SEQ// return res.status(404).send({ auth: false, status: 'USER_NOTFOUND', error_message: 'User does not exist.' });
+ //SEQ//}
+
+ if (!user_db) {
+ return res.status(404).send({ auth: false, status: 'USER_NOTFOUND', error_message: 'User does not exist.' });
+ }
+ var user = {};
+ //SEQ//user.id = rows[0].id;
+ user.id = user_db.id;
+ //user.username = rows[0].username;
+ //user.email = rows[0].email;
+ var userPassword = Buffer.from(password);
+ //SEQ//var hash = Buffer.from(rows[0].password);
+ var hash = Buffer.from(user_db.password);
+
+ // Verify & improving the hash.
+ verifyHash(res, userPassword, hash, user.id, function() {
+ jwt.sign({user}, config.secret, { expiresIn: '12h' }, (err, token) => {
+ if (err) return res.status(500).send({ auth: false, status: 'JWT_ERROR', error_message: 'Jwt sign failed.' });
+ return callback(token);
+ });
+ });
+ });
+}
+
+// The verify hash function from the secure-passwords with error handling.
+function verifyHash(res, password, hash, userId, callback) {
+ // 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(500).send({ 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(500).send({ 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(500).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(500).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(500).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.
+ //SEQ//db.query('UPDATE users SET password=? WHERE id=?', [improvedHash, userId], function(err, result) {
+ db.user.findOne({ where: { id: userId } }).then(user_db => {
+ //SEQ//if (err) throw err;
+ user_db.updateAttributes({
+ password: improvedHash
+ });
+ return callback();
+ });
+ });
+ }
+ });
+}
+
+// Function for validating the e-mail.
+function validateEmail(email) {
+ 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());
+}
+
+// Function for validating the password. Password requirements are implemented here.
+function validatePassword(password) {
+ // TODO: implement pw requirements like in the frontend.
+ return true;
+} \ No newline at end of file