| @@ -13,6 +13,8 @@ CREATE TABLE users ( | |||||
| country VARCHAR(50), | country VARCHAR(50), | ||||
| login_attempts INT UNSIGNED, | login_attempts INT UNSIGNED, | ||||
| last_login_attempt INT UNSIGNED, | last_login_attempt INT UNSIGNED, | ||||
| verified BOOLEAN, | |||||
| token VARCHAR(50), | |||||
| PRIMARY KEY (userid) | PRIMARY KEY (userid) | ||||
| ); | ); | ||||
| @@ -6,7 +6,7 @@ logger = logging.getLogger(__name__) | |||||
| def set_user(username, password, full_name, company, email, | 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 | Register a new user in the database | ||||
| :param username: The users unique user name | :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 state: The state where the user lives | ||||
| :param postal_code: The corresponding postal code | :param postal_code: The corresponding postal code | ||||
| :param country: The users country | :param country: The users country | ||||
| :param token: The account verification token | |||||
| :type username: str | :type username: str | ||||
| :type password: str | :type password: str | ||||
| :type full_name: str | :type full_name: str | ||||
| @@ -29,13 +30,19 @@ def set_user(username, password, full_name, company, email, | |||||
| :type state: str | :type state: str | ||||
| :type postal_code: str | :type postal_code: str | ||||
| :type country: str | :type country: str | ||||
| :type token: str | |||||
| """ | """ | ||||
| db.connect() | db.connect() | ||||
| cursor = db.cursor() | 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: | 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() | db.commit() | ||||
| except mysql.connector.Error as err: | except mysql.connector.Error as err: | ||||
| logger.error("Failed executing query: %s", err) | logger.error("Failed executing query: %s", err) | ||||
| @@ -122,3 +122,83 @@ def get_user_name_by_id(userid): | |||||
| cursor.close() | cursor.close() | ||||
| db.close() | db.close() | ||||
| return username | 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 | |||||
| @@ -0,0 +1,19 @@ | |||||
| $def with (nav, message) | |||||
| <head> | |||||
| <title>Beelance2</title> | |||||
| <meta charset="utf-8"> | |||||
| <link rel="stylesheet" type="text/css" href="static/stylesheet.css"> | |||||
| <link rel="shortcut icon" type="image/png" href="static/honeybee.png"/> | |||||
| </head> | |||||
| <body> | |||||
| $:nav | |||||
| <h2>$message</h2> | |||||
| </body> | |||||
| <footer></footer> | |||||
| @@ -2,7 +2,7 @@ import os | |||||
| import web | import web | ||||
| from views.login import Login | from views.login import Login | ||||
| from views.logout import Logout | 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.new_project import New_project | ||||
| from views.open_projects import Open_projects | from views.open_projects import Open_projects | ||||
| from views.project import Project | from views.project import Project | ||||
| @@ -17,6 +17,7 @@ urls = ( | |||||
| '/', 'Index', | '/', 'Index', | ||||
| '/login', 'Login', | '/login', 'Login', | ||||
| '/logout', 'Logout', | '/logout', 'Logout', | ||||
| '/verify', 'Verify', | |||||
| '/register', 'Register', | '/register', 'Register', | ||||
| '/new_project', 'New_project', | '/new_project', 'New_project', | ||||
| '/open_projects', 'Open_projects', | '/open_projects', 'Open_projects', | ||||
| @@ -64,6 +64,9 @@ class Login(): | |||||
| if login_attempts > login_attempts_threshold: | if login_attempts > login_attempts_threshold: | ||||
| logger.info("User %s logged in succesfully after %s attempts", username, login_attempts) | 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()) | models.user.set_login_attempts(userid, 0, time.time()) | ||||
| self.login(username, userid, data.remember) | self.login(username, userid, data.remember) | ||||
| raise web.seeother("/") | raise web.seeother("/") | ||||
| @@ -1,6 +1,7 @@ | |||||
| import web | import web | ||||
| from views.forms import register_form | 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.register | ||||
| import models.user | import models.user | ||||
| import logging | import logging | ||||
| @@ -49,9 +50,56 @@ class Register: | |||||
| password_hash = bcrypt.hashpw(data.password.encode('UTF-8'), bcrypt.gensalt()) | 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) | 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.") | |||||