diff --git a/mysql/sql/init.sql b/mysql/sql/init.sql index a25a17b..b5e7fbc 100755 --- a/mysql/sql/init.sql +++ b/mysql/sql/init.sql @@ -13,6 +13,8 @@ CREATE TABLE users ( country VARCHAR(50), login_attempts INT UNSIGNED, last_login_attempt INT UNSIGNED, + verified BOOLEAN, + token VARCHAR(50), PRIMARY KEY (userid) ); diff --git a/src/app/models/register.py b/src/app/models/register.py index 2be8e1f..a01adfe 100755 --- a/src/app/models/register.py +++ b/src/app/models/register.py @@ -6,7 +6,7 @@ logger = logging.getLogger(__name__) def set_user(username, password, full_name, company, email, - street_address, city, state, postal_code, country): + street_address, city, state, postal_code, country, token): """ Register a new user in the database :param username: The users unique user name @@ -19,6 +19,7 @@ def set_user(username, password, full_name, company, email, :param state: The state where the user lives :param postal_code: The corresponding postal code :param country: The users country + :param token: The account verification token :type username: str :type password: str :type full_name: str @@ -29,13 +30,19 @@ def set_user(username, password, full_name, company, email, :type state: str :type postal_code: str :type country: str + :type token: str """ db.connect() cursor = db.cursor() - query = ("INSERT INTO users VALUES (NULL, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, 0, 0)") + query = (""" + INSERT INTO users (userid, username, password, full_name, company, + email, street_address, city, state, postal_code, + country, login_attempts, last_login_attempt, verified, token) + VALUES (NULL, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, 0, 0, 0, %s) + """) try: - cursor.execute(query, (username, password, full_name, company, email, street_address, - city, state, postal_code, country)) + cursor.execute(query, (username, password, full_name, company, email, + street_address, city, state, postal_code, country, token)) db.commit() except mysql.connector.Error as err: logger.error("Failed executing query: %s", err) diff --git a/src/app/models/user.py b/src/app/models/user.py index 24dd567..9b379c5 100755 --- a/src/app/models/user.py +++ b/src/app/models/user.py @@ -122,3 +122,83 @@ def get_user_name_by_id(userid): cursor.close() db.close() return username + + +def set_token(userid, token): + """Set the given token for the given user.""" + db.connect() + cursor = db.cursor() + query = ("UPDATE users SET token=%s WHERE userid=%s") + try: + cursor.execute(query, (token, userid)) + db.commit() + except mysql.connector.Error as err: + print("Failed executing query: {}".format(err)) + cursor.fetchall() + exit(1) + finally: + cursor.close() + db.close() + + +def get_userid_from_token(token): + """Get the user with the given verify token.""" + db.connect() + cursor = db.cursor() + query = ("SELECT userid FROM users WHERE token=%s") + try: + cursor.execute(query, (token,)) + tokens = cursor.fetchall() + if tokens: + return tokens[0][0] + except mysql.connector.Error as err: + print("Failed executing query: {}".format(err)) + cursor.fetchall() + exit(1) + finally: + cursor.close() + db.close() + + return None + + +def verify_user(userid): + """ + Mark the user as verified. + """ + db.connect() + cursor = db.cursor() + query = ("UPDATE users SET verified=1 WHERE userid=%s AND verified=0") + try: + cursor.execute(query, (userid,)) + db.commit() + except mysql.connector.Error as err: + print("Failed executing query: {}".format(err)) + cursor.fetchall() + exit(1) + finally: + cursor.close() + db.close() + + +def is_verified(userid): + """ + Check whether the user has verified + """ + db.connect() + cursor = db.cursor() + query = ("SELECT userid FROM users WHERE verified=1 AND userid=%s") + try: + cursor.execute(query, (userid,)) + users = cursor.fetchall() + if users: + return True + except mysql.connector.Error as err: + print("Failed executing query: {}".format(err)) + cursor.fetchall() + exit(1) + finally: + cursor.close() + db.close() + + return False diff --git a/src/app/templates/verify.html b/src/app/templates/verify.html new file mode 100644 index 0000000..6234b27 --- /dev/null +++ b/src/app/templates/verify.html @@ -0,0 +1,19 @@ +$def with (nav, message) + + + Beelance2 + + + + + + + + + $:nav + +

$message

+ + + + diff --git a/src/app/views/app.py b/src/app/views/app.py index 15e6df1..2eee3fc 100755 --- a/src/app/views/app.py +++ b/src/app/views/app.py @@ -2,7 +2,7 @@ import os import web from views.login import Login from views.logout import Logout -from views.register import Register +from views.register import Register, Verify from views.new_project import New_project from views.open_projects import Open_projects from views.project import Project @@ -17,6 +17,7 @@ urls = ( '/', 'Index', '/login', 'Login', '/logout', 'Logout', + '/verify', 'Verify', '/register', 'Register', '/new_project', 'New_project', '/open_projects', 'Open_projects', diff --git a/src/app/views/login.py b/src/app/views/login.py index 605ea08..2e7426d 100755 --- a/src/app/views/login.py +++ b/src/app/views/login.py @@ -64,6 +64,9 @@ class Login(): if login_attempts > login_attempts_threshold: logger.info("User %s logged in succesfully after %s attempts", username, login_attempts) + if not models.user.is_verified(userid): + return render.login(nav, login_form, "- User not authenticated yet. Please check you email.") + models.user.set_login_attempts(userid, 0, time.time()) self.login(username, userid, data.remember) raise web.seeother("/") diff --git a/src/app/views/register.py b/src/app/views/register.py index a7bef39..697de9b 100755 --- a/src/app/views/register.py +++ b/src/app/views/register.py @@ -1,6 +1,7 @@ import web from views.forms import register_form -from views.utils import get_nav_bar, csrf_protected, password_weakness, get_render +from views.utils import get_nav_bar, csrf_protected, password_weakness, get_render, sendmail +from uuid import uuid4 import models.register import models.user import logging @@ -49,9 +50,56 @@ class Register: 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) + # Create a verify token + while True: + token = uuid4().hex + if models.user.get_userid_from_token(token) is None: + break + + 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, + token, + ) + + verify_url = "https://{}/verify?token={}".format(web.ctx.host, token) + + sendmail( + 'Verify your Beelance account', + """ + Welcome to Beelance! + + To verify your account, please go to this link: {url} + """.format(url=verify_url), + data.full_name, + data.email, + ) logger.info("User %s registered", data.username) - return render.register(nav, register_form, "User registered!") + return render.register(nav, register_form, "User registered! We have sent you an email to verify your account.") + + +class Verify: + def GET(self): + """ + Verify the user email + """ + session = web.ctx.session + nav = get_nav_bar(session) + render = get_render() + + token = web.input(token='').token + userid = models.user.get_userid_from_token(token) + if token and userid is not None: + models.user.verify_user(userid) + return render.verify(nav, "Your email has been verified. You can log in now.") + else: + return render.verify(nav, "Invalid token. Please try again.")