diff --git a/anonkun.py b/anonkun.py index 332c26b..0a2c205 100644 --- a/anonkun.py +++ b/anonkun.py @@ -4,7 +4,9 @@ Simple file host using Flask. """ import os import time +import string import urllib +import functools from flask import Flask, session, request, abort, redirect, url_for, g, \ render_template @@ -12,6 +14,7 @@ from flask_socketio import SocketIO, emit, join_room from flask_paranoid import Paranoid import MySQLdb import bleach +from passlib.hash import argon2 class ReverseProxied(object): """ @@ -97,6 +100,77 @@ def init(): db_execute(cmd) +def login_required(url=None): + """ + A decorator function to protect certain endpoints by requiring the user + to either pass a valid session cookie, or pass thier username and + password along with the request to login. + """ + def actual_decorator(func): + @functools.wraps(func) + def _nop(*args, **kwargs): + username = session.get("username") + if verify_username(username): + return func(*args, **kwargs) + + username = request.form.get("user") + password = request.form.get("pass") + if verify_password(username, password): + return func(*args, **kwargs) + + if url: + return redirect(url_for(url)) + else: + abort(401) + return _nop + return actual_decorator + + +def add_user(username, password): + """ + Adds a user to the database. + """ + if verify_username(username): # username taken + return "username_taken" + elif len(username) > 20: + return "username_too_long" + + pw_hash = argon2.hash(password) + db_execute( + "INSERT INTO `users` (`username`, `password_hash`) VALUES (%s, %s)", + (username, pw_hash)) + return "success" + + +def verify_password(username, password): + """ + Verifies a user's password. + """ + user = verify_username(username) + if not user: + return False + + user_id, _, pw_hash = user + + if argon2.verify(password, pw_hash): + session["user_id"] = user_id + return True + else: + return False + + +def verify_username(username): + """ + Checks to see if the given username is in the database. + """ + user = db_execute("SELECT * FROM `users` WHERE `username` = %s", + (username,)).fetchone() + if user: + return user + else: + return False + + @socketio.on('joined', namespace="/chat") def joined(data): """ @@ -156,6 +230,7 @@ def quest(quest_title): @app.route("/create_quest", methods=["GET", "POST"]) +@login_required("login") def create_quest(): """ Starts a new quest. @@ -175,6 +250,58 @@ def create_quest(): return redirect(url_for('quest', quest_title=ident_title)) +@app.route("/login", methods=["GET", "POST"]) +def login(): + """ + Logs the user in. + """ + if request.method == "GET": + return render_template("login.html") + + username = request.form.get("user") + password = request.form.get("pass") + + if verify_password(username, password): + session["username"] = username + return redirect(url_for("index")) + else: + abort(401) + + +@app.route("/signup", methods=["GET", "POST"]) +def signup(): + """ + Create a new account. + """ + if request.method == "GET": + return render_template("signup.html") + + username = request.form.get("user") + password = request.form.get("pass") + verify_password = request.form.get("verify_pass") + + if len(username) > 20: + "username_too_long" + elif len(username) < 3: + "username_too_short" + chrs = [c not in string.ascii_letters + string.digits for c in username] + if any(chrs): + "username_bad_chars" + if verify_username(username): + "username_taken" + + if len(password) > 1024: + "password_too_long" + elif len(password) < 8: + "password_too_short" + + if password != verify_password: + "passwords_dont_match" + + res = add_user(username, password) + return redirect(url_for("index")) + + @app.route("/") def index(): """ diff --git a/templates/index.html b/templates/index.html index 9f8159b..7db8827 100644 --- a/templates/index.html +++ b/templates/index.html @@ -6,6 +6,8 @@

Quests 'n Shiet

Unga Bunga Quest
- Create New Quest + Create New Quest
+ Sign up
+ Login