From 9892487c443b3b886525cf70daf513be8bf9371b Mon Sep 17 00:00:00 2001 From: Sindre Stephansen Date: Sun, 15 Mar 2020 19:57:27 +0100 Subject: [PATCH] Implement better password security The new scheme uses bcrypt and a random salt for each user. This is not compatible with old passwords. Fixes #13 --- mysql/sql/init.sql | 4 +-- src/app/models/user.py | 56 +++++++++++++++++++-------------------- src/app/requirements.txt | 1 + src/app/views/login.py | 8 +++--- src/app/views/register.py | 11 ++++---- 5 files changed, 39 insertions(+), 41 deletions(-) diff --git a/mysql/sql/init.sql b/mysql/sql/init.sql index 165cbe6..a675d58 100755 --- a/mysql/sql/init.sql +++ b/mysql/sql/init.sql @@ -2,7 +2,7 @@ CREATE TABLE users ( userid INT UNSIGNED AUTO_INCREMENT, username VARCHAR(45) UNIQUE NOT NULL, - password VARCHAR(45) NOT NULL, + password VARCHAR(60) NOT NULL, full_name VARCHAR(200) NOT NULL, company VARCHAR(50), email VARCHAR(50) NOT NULL, @@ -84,7 +84,7 @@ CREATE TABLE task_files ( * Initial data */ -insert into users values (NULL, "admin", "48bead1bb864138c2cafaf1bd41332ab", "Admin Modsen", "ntnu", 'mail@ntnu.no', "street", "trondheim", "trondheim", "1234", "norway"); +insert into users values (NULL, "admin", "$2b$12$iKbYZ0MFwWWxoYUXKRhFiOPo7itaQO2DIRnLgXbECsj8XKVzkNCSi", "Admin Modsen", "ntnu", 'mail@ntnu.no', "street", "trondheim", "trondheim", "1234", "norway"); insert into project_category values (NULL, "Gardening"); insert into project_category values (NULL, "Programming"); diff --git a/src/app/models/user.py b/src/app/models/user.py index 2b5e9b4..4519115 100755 --- a/src/app/models/user.py +++ b/src/app/models/user.py @@ -2,6 +2,33 @@ from models.database import db import mysql.connector +def get_user(username): + """ + Get the user with the given username + + :param username: The username + :type username: str + :return: user + """ + db.connect() + cursor = db.cursor() + query = ("SELECT userid, username, password from users where username = %s") + user = None + try: + cursor.execute(query, (username,)) + users = cursor.fetchall() + if len(users): + user = users[0] + except mysql.connector.Error as err: + print("Failed executing query: {}".format(err)) + cursor.fetchall() + exit(1) + finally: + cursor.close() + db.close() + return user + + def get_users(): """ Retreive all registrered users from the database @@ -73,32 +100,3 @@ def get_user_name_by_id(userid): cursor.close() db.close() return username - - -def match_user(username, password): - """ - Check if user credentials are correct, return if exists - - :param username: The user attempting to authenticate - :param password: The corresponding password - :type username: str - :type password: str - :return: user - """ - db.connect() - cursor = db.cursor() - query = ("SELECT userid, username from users where username = %s and password = %s") - user = None - try: - cursor.execute(query, (username, password)) - users = cursor.fetchall() - if len(users): - user = users[0] - except mysql.connector.Error as err: - print("Failed executing query: {}".format(err)) - cursor.fetchall() - exit(1) - finally: - cursor.close() - db.close() - return user diff --git a/src/app/requirements.txt b/src/app/requirements.txt index 19df7de..172cf6d 100755 --- a/src/app/requirements.txt +++ b/src/app/requirements.txt @@ -1,3 +1,4 @@ web.py==0.40 mysql-connector-python==8.0.* python-dotenv +bcrypt diff --git a/src/app/views/login.py b/src/app/views/login.py index 58b52c5..99f6d94 100755 --- a/src/app/views/login.py +++ b/src/app/views/login.py @@ -5,7 +5,7 @@ import models.session import models.user import random import string -import hashlib +import bcrypt import time # Get html templates @@ -45,11 +45,9 @@ class Login(): data = web.input(username="", password="", remember=False) # Validate login credential with database query - password_hash = hashlib.md5(b'TDT4237' + data.password.encode('utf-8')).hexdigest() - user = models.user.match_user(data.username, password_hash) + user = models.user.get_user(data.username) - # If there is a matching user/password in the database the user is logged in - if user: + if bcrypt.checkpw(data.password.encode('UTF-8'), user[2].encode('UTF-8')): self.login(user[1], user[0], data.remember) raise web.seeother("/") else: diff --git a/src/app/views/register.py b/src/app/views/register.py index cd79330..ecd414f 100755 --- a/src/app/views/register.py +++ b/src/app/views/register.py @@ -3,7 +3,7 @@ from views.forms import register_form from views.utils import get_nav_bar, csrf_protected import models.register import models.user -import hashlib +import bcrypt import re # Get html templates @@ -41,9 +41,10 @@ class Register: if models.user.get_user_id_by_name(data.username): return render.register(nav, register, "Invalid user, already exists.") - models.register.set_user(data.username, - hashlib.md5(b'TDT4237' + data.password.encode('utf-8')).hexdigest(), - data.full_name, data.company, data.email, data.street_address, - data.city, data.state, data.postal_code, data.country) + password_hash = bcrypt.hashpw(data.password.encode('UTF-8'), bcrypt.gensalt()) + + models.register.set_user(data.username, password_hash, data.full_name, data.company, + data.email, data.street_address, data.city, data.state, + data.postal_code, data.country) return render.register(nav, register_form, "User registered!")