| @@ -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) | |||
| ); | |||
| @@ -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) | |||
| @@ -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 | |||
| @@ -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 | |||
| 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', | |||
| @@ -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("/") | |||
| @@ -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.") | |||