From 54afcfad1d9dddf23201023f0a523fb6cf60e794 Mon Sep 17 00:00:00 2001 From: Adam Dodman Date: Wed, 4 Oct 2017 01:04:18 +0100 Subject: [PATCH] Add username/password authentication system --- adduser.py | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++ default.sql | 4 +-- main.py | 52 ++++++++++++++++++++++++++++++++------ 3 files changed, 119 insertions(+), 10 deletions(-) create mode 100644 adduser.py diff --git a/adduser.py b/adduser.py new file mode 100644 index 0000000..7de64db --- /dev/null +++ b/adduser.py @@ -0,0 +1,73 @@ +import pymysql +import getpass +import sys +from passlib.context import CryptContext + +user = realname = passwd = None + + +pass_ctx = CryptContext(["bcrypt_sha256"]) + +# https://stackoverflow.com/questions/3041986/apt-command-line-interface-like-yes-no-input +def query_yes_no(question, default="yes"): + """Ask a yes/no question via raw_input() and return their answer. + + "question" is a string that is presented to the user. + "default" is the presumed answer if the user just hits . + It must be "yes" (the default), "no" or None (meaning + an answer is required of the user). + + The "answer" return value is True for "yes" or False for "no". + """ + valid = {"yes": 1, "y": 1, "ye": 1, + "no": 0, "n": 0} + if default is None: + prompt = " [y/n] " + elif default == "yes": + prompt = " [Y/n] " + elif default == "no": + prompt = " [y/N] " + else: + raise ValueError("invalid default answer: '%s'" % default) + + while True: + sys.stdout.write(question + prompt) + choice = input().lower() + if default is not None and choice == '': + return valid[default] + elif choice in valid: + return valid[choice] + else: + sys.stdout.write("Please respond with 'yes' or 'no' " + "(or 'y' or 'n').\n") + +while not user: + user = input("Enter Username: ") + +while not realname: + realname = input("Enter the real name of the user: ") + + +while not passwd: + passwd = getpass.getpass() + +passwd_v = getpass.getpass(prompt="Reenter password: ") + +while passwd != passwd_v: + print ("Error: Passwords do not match!") + passwd = getpass.getpass() + passwd_v = getpass.getpass() + +passwd_hash = pass_ctx.hash(passwd) + +isadmin = query_yes_no("Is the user an admin?", "no") + +query = "INSERT INTO `Users` (`uid`, `user`, `realname`, `password`, `isadmin`) VALUES (NULL, '%s', '%s', '%s', %d);" % (user, realname, passwd_hash, isadmin) + +db = pymysql.connect(host='dockerdev', port=3306, user='root', passwd='development', db='QuoteDB') +cur = db.cursor() +cur.execute(query) +data = cur.fetchall() +cur.close() +db.commit() +db.close() diff --git a/default.sql b/default.sql index 096d2bf..7ef9530 100644 --- a/default.sql +++ b/default.sql @@ -1,9 +1,9 @@ CREATE TABLE Users ( uid INT NOT NULL AUTO_INCREMENT PRIMARY KEY, - user VARCHAR(255) NOT NULL, + user VARCHAR(255) NOT NULL UNIQUE, realname VARCHAR(255) NOT NULL, password VARCHAR(255), - isadmin BIT + isadmin BIT NOT NULL ); CREATE TABLE Quotes ( diff --git a/main.py b/main.py index 59bb836..709a14a 100644 --- a/main.py +++ b/main.py @@ -1,18 +1,23 @@ from os import urandom as rand -from flaskext.mysql import MySQL +#from flaskext.mysql import MySQL import pymysql from flask import Flask, render_template, session, redirect, url_for, request, flash +from passlib.context import CryptContext import pprint pp = pprint.PrettyPrinter(indent=4) +pass_ctx = CryptContext(["bcrypt_sha256"]) app = Flask(__name__) # Thank you based StackOverflow +# Remove Trailing and leading whitespace, strip unicode def cleanup_string(text): text = text.encode("ascii", "replace").decode() return text.strip() + + # Load User Table into variable def mysql_do(query): db = pymysql.connect(host='dockerdev', port=3306, user='root', passwd='development', db='QuoteDB') @@ -25,13 +30,37 @@ def mysql_do(query): return data def app_init(): - mysql_do("CREATE TABLE IF NOT EXISTS Users ( uid INT NOT NULL AUTO_INCREMENT PRIMARY KEY, user VARCHAR(255) NOT NULL, realname VARCHAR(255) NOT NULL, password VARCHAR(255), isadmin BIT );") + # Check to make sure tables are set up properly + mysql_do("CREATE TABLE IF NOT EXISTS Users ( uid INT NOT NULL AUTO_INCREMENT PRIMARY KEY, user VARCHAR(255) NOT NULL UNIQUE, realname VARCHAR(255) NOT NULL, password VARCHAR(255), isadmin BIT NOT NULL);") mysql_do("CREATE TABLE IF NOT EXISTS Quotes ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, quote VARCHAR(2048) NOT NULL, date VARCHAR(255) NOT NULL, user INT NOT NULL, context VARCHAR(8000), FOREIGN KEY (user) REFERENCES Users(uid) );") + + # Generate random key for session cookies app.secret_key = rand(24) + + # TODO: Replace this with something dynamic where needed global userdb userdb = mysql_do("SELECT * FROM Users") +def do_user_login(user, password): + try: + userdata = mysql_do("SELECT * FROM Users WHERE user='%s'" % (user))[0] + except IndexError: + # Returned when no rows found - no user with that name + flash( "Error: Incorrect Username or Password!", "danger") + return redirect(url_for('login')) + + if pass_ctx.verify(password, userdata[3]): + session['username'] = user + session['uid'] = userdata[0] + session['isAdmin'] = bool(ord(userdata[4])) + return redirect(url_for('index')) + + else: + flash( "Error: Incorrect Username or Password!", "danger") + return redirect(url_for('login')) + + @app.route("/") def index(): if 'username' in session: @@ -43,6 +72,12 @@ def quoutepage(): retdata = mysql_do("SELECT * FROM Quotes ORDER BY ID DESC") return render_template("quote_view.html", data=retdata) +@app.route("/login", methods=['GET', 'POST']) +def login(): + if request.method == 'POST': + return do_user_login(request.form['username'], request.form['pw']) + return render_template("login.html") + @app.route("/addquote", methods=['GET','POST']) def addquote(): if request.method == "POST": @@ -90,14 +125,15 @@ def addquote(): mysql_do(sql) flash("Success! The entry was added to the database.","success") return redirect(url_for('index')) + + # Check if the user is authenticated + try: + session['username'] + except KeyError: + flash("INFO: Please login first.","info") + return redirect(url_for("login")) return render_template("add_quote.html", users=userdb) -@app.route("/login", methods=['GET', 'POST']) -def login(): - if request.method == 'POST': - session['username'] = request.form['username'] - return redirect(url_for('index')) - return render_template("login.html") @app.route("/logout") def logout():