git.haldean.org apex / master users.py
master

Tree @master (Download .tar.gz)

users.py @masterraw · history · blame

import argon2
import base64
import flask
import hmac
import re

from globals import *

class AuthenticationFailed(Exception):
    pass

def valid_username(username):
    return re.match("^[a-zA-Z0-9_]{1,32}$", username)

def hash_password(username, password):
    salt = username[:32].ljust(32, "$") + "-salt:"
    return salt.encode("utf-8") + base64.b64encode(argon2.argon2_hash(password, salt))

def check_password(username, password):
    if not valid_username(username):
        raise AuthenticationFailed()
    phash_stored = r.get("apex:users:{}:password".format(username))
    if phash_stored is None:
        # the comparison to this will always fail, but we do the comparison
        # anyway to maintain constant-time and avoid user enumeration
        phash_compare = hash_password("", "")
    else:
        phash_compare = phash_stored
    phash_request = hash_password(username, password)
    return hmac.compare_digest(phash_compare, phash_request)

def add_user(username, password):
    if not valid_username(username):
        flask.abort(400)
        return
    hash = hash_password(username, password)
    r.set("apex:users:{}:password".format(username), hash)

def session_user():
    if "username" in flask.session:
        return flask.session["username"]
    return None

def set_session_user(username):
    r.sadd("apex:users", username)
    flask.session["username"] = username

def user_is_admin(username=None):
    if username is None:
        username = session_user()
        if username is None:
            return False
    return bool(r.get("apex:users:{}:admin".format(username)))

def user_set_admin(username):
    r.set("apex:users:{}:admin".format(username), True)