diff options
Diffstat (limited to 'api/auth')
-rw-r--r-- | api/auth/login.py | 74 | ||||
-rw-r--r-- | api/auth/login_token.py | 23 | ||||
-rw-r--r-- | api/auth/signup.py | 124 | ||||
-rw-r--r-- | api/auth/token.py | 60 |
4 files changed, 168 insertions, 113 deletions
diff --git a/api/auth/login.py b/api/auth/login.py index 78a9add..4ae1650 100644 --- a/api/auth/login.py +++ b/api/auth/login.py @@ -5,38 +5,50 @@ import passwords login = Blueprint('login', __name__) -@login.route('/login', methods = ['POST']) -def index(): - data = request.get_json() - - # get form data - email = data.get("email") or "" - password = data.get("password") or "" - - # return malformed request if email or password is missing - if not email or \ - not password: - return "", 400 - - # resolve user_id from username or email - user_id = None - user_id = user_id or cursor.execute("select user_id from users where email = ?", [email]).fetchone() - user_id = user_id or cursor.execute("select user_id from users where lower(username) = lower(?)", [email]).fetchone() - if user_id == None: return "", 401 - # check the password - passwd = cursor.execute("select password_hash from users where user_id = ?", [user_id[0]]).fetchone() - check = passwords.check_password(password, passwd[0]) - if not check: return "", 401 - - # generate a new authentication token and add it to the users valid token list - new_token = token.generate_token() - token.add_token(user_id[0], token.hash_token(new_token)) - - # make response with the set_cookie header - res = make_response("", 200) - res.set_cookie("token", new_token["token"], expires = int(new_token["expirationDate"] / 1000)) +@login.route('/login', methods=['POST']) +def index(): + data = request.get_json() + + # get form data + email = data.get("email") or "" + password = data.get("password") or "" + + # return malformed request if email or password is missing + if not email or \ + not password: + return "", 400 + + # resolve user_id from username or email + user_id = None + user_id = user_id or cursor.execute( + "select user_id from users where email = ?", [email] + ).fetchone() + user_id = user_id or cursor.execute( + "select user_id from users where lower(username) = lower(?)", [email] + ).fetchone() + if user_id == None: return "", 401 + + # check the password + passwd = cursor.execute( + "select password_hash from users where user_id = ?", [user_id[0]] + ).fetchone() + check = passwords.check_password(password, passwd[0]) + if not check: return "", 401 + + # generate a new authentication token and add it to the users valid token list + new_token = token.generate_token() + token.add_token(user_id[0], token.hash_token(new_token)) + + # make response with the set_cookie header + res = make_response("", 200) + res.set_cookie( + "token", + new_token["token"], + expires=int(new_token["expirationDate"] / 1000) + ) + + return res - return res dynamic_route = ["/auth", login] diff --git a/api/auth/login_token.py b/api/auth/login_token.py index d920eea..bb67c4f 100644 --- a/api/auth/login_token.py +++ b/api/auth/login_token.py @@ -2,22 +2,29 @@ from flask import Blueprint, request from db import cursor from auth.token import validate_token, hash_token + # get user_id from authentication token def token_login(token): - hashed = hash_token({ "token": token, "expirationDate": 0 }) - user_id = cursor.execute("select user_id from users where valid_tokens like ?", [f"%{hashed['token']}%"]).fetchone() - return None if not user_id else user_id[0] + hashed = hash_token({"token": token, "expirationDate": 0}) + user_id = cursor.execute( + "select user_id from users where valid_tokens like ?", + [f"%{hashed['token']}%"] + ).fetchone() + return None if not user_id else user_id[0] + token = Blueprint('token', __name__) + # this endpoint is currently unused, but verifies that a token is valid -@token.route('/token', methods = ['POST']) +@token.route('/token', methods=['POST']) def index(): - data = request.get_json() + data = request.get_json() + + auth_token = data.get("token") or "" + if not auth_token: return "", 400 - auth_token = data.get("token") or "" - if not auth_token: return "", 400 + return "", 200 if token_login(auth_token) else 401 - return "", 200 if token_login(auth_token) else 401 dynamic_route = ["/auth", token] diff --git a/api/auth/signup.py b/api/auth/signup.py index e758a4e..f9a1af5 100644 --- a/api/auth/signup.py +++ b/api/auth/signup.py @@ -6,71 +6,87 @@ import passwords import time import re + # checks if the usename is between 3 and 35 charachters def validate_username(username): - return len(username) in range(3, 35 + 1) + return len(username) in range(3, 35 + 1) + # garbage email validation (see todo) def validate_email(email): - #TODO: use node_modules/email-validator/index.js - return len(email) > 1 and \ - "@" in email + #TODO: use node_modules/email-validator/index.js + return len(email) > 1 and \ + "@" in email + # checks if the password is safe (regex explanation in pages/register.tsx) def validate_password(password): - passwordRegex = r"^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9]).{8,}$" # r"" = raw string - return re.match(passwordRegex, password) + passwordRegex = r"^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9]).{8,}$" # r"" = raw string + return re.match(passwordRegex, password) + signup = Blueprint('signup', __name__) -@signup.route('/signup', methods = ['POST']) + +@signup.route('/signup', methods=['POST']) def index(): - # parse request data if the content-type header is set to application/json - data = request.get_json() - - # force string if {}.get(...) returns None - username = data.get("username") or "" - email = data.get("email") or "" - password = data.get("password") or "" - - # return 400 (malformed request) if any of the required data is missing - if not username or \ - not email or \ - not password: - return "", 400 - - # return 403 (forbidden) if any of the required data is invalid - if not validate_username(username) or \ - not validate_email(email) or \ - not validate_password(password): - return {"error": "form_data_invalid"}, 403 - - # check if username is taken - if cursor.execute("select username from users where lower(username) = lower(?)", [username]).fetchone(): - return {"error": "username_taken"}, 403 - - # check if email is taken - if cursor.execute("select email from users where email = ?", [email]).fetchone(): - return {"error": "email_taken"}, 403 - - # create new user_id, hash password and note timestamp - user_id = new_uuid("users") - password_hash = passwords.password_hash(password) - registered = int( time.time() * 1000 ) - - # write new user to database and commit - cursor.execute("insert into users values (?, ?, ?, NULL, NULL, ?, ?, \"[]\", FALSE, \"user\", \"{}\", \"online\") ", - (user_id, username, email, password_hash, registered)) - connection.commit() - - # create a new token for the user to use for authentication - new_token = token.generate_token() - token.add_token(user_id, token.hash_token(new_token)) - - # create a flask response object to add the set-cookie header to - res = make_response("", 200) - res.set_cookie("token", new_token["token"], expires = int(new_token["expirationDate"] / 1000)) - - return res + # parse request data if the content-type header is set to application/json + data = request.get_json() + + # force string if {}.get(...) returns None + username = data.get("username") or "" + email = data.get("email") or "" + password = data.get("password") or "" + + # return 400 (malformed request) if any of the required data is missing + if not username or \ + not email or \ + not password: + return "", 400 + + # return 403 (forbidden) if any of the required data is invalid + if not validate_username(username) or \ + not validate_email(email) or \ + not validate_password(password): + return {"error": "form_data_invalid"}, 403 + + # check if username is taken + if cursor.execute( + "select username from users where lower(username) = lower(?)", + [username] + ).fetchone(): + return {"error": "username_taken"}, 403 + + # check if email is taken + if cursor.execute("select email from users where email = ?", + [email]).fetchone(): + return {"error": "email_taken"}, 403 + + # create new user_id, hash password and note timestamp + user_id = new_uuid("users") + password_hash = passwords.password_hash(password) + registered = int(time.time() * 1000) + + # write new user to database and commit + cursor.execute( + "insert into users values (?, ?, ?, NULL, NULL, ?, ?, \"[]\", FALSE, \"user\", \"{}\", \"online\") ", + (user_id, username, email, password_hash, registered) + ) + connection.commit() + + # create a new token for the user to use for authentication + new_token = token.generate_token() + token.add_token(user_id, token.hash_token(new_token)) + + # create a flask response object to add the set-cookie header to + res = make_response("", 200) + res.set_cookie( + "token", + new_token["token"], + expires=int(new_token["expirationDate"] / 1000) + ) + + return res + dynamic_route = ["/auth", signup] diff --git a/api/auth/token.py b/api/auth/token.py index 113c2c7..d75c91b 100644 --- a/api/auth/token.py +++ b/api/auth/token.py @@ -4,37 +4,57 @@ import secrets import json import time + # get valid token hashes for a given user_id def valid_tokens(user_id): - tokens = json.loads(cursor.execute("select valid_tokens from users where user_id = ?", [user_id]).fetchone()[0]) - # return only tokens that aren't expired - return [token for token in tokens if token["expirationDate"] > int( time.time() * 1000 )] + tokens = json.loads( + cursor.execute( + "select valid_tokens from users where user_id = ?", [user_id] + ).fetchone()[0] + ) + # return only tokens that aren't expired + return [ + token for token in tokens + if token["expirationDate"] > int(time.time() * 1000) + ] + def validate_token(user_id, token): - tokens = valid_tokens(user_id) - return hashlib.sha256(str(token).encode()).hexdigest() in [ t["token"] for t in tokens if t["expirationDate"] > int( time.time() * 1000 ) ] + tokens = valid_tokens(user_id) + return hashlib.sha256(str(token).encode()).hexdigest() in [ + t["token"] for t in tokens + if t["expirationDate"] > int(time.time() * 1000) + ] + def modify_tokens(user_id, formatted_token, remove): - temp_tokens = valid_tokens(user_id) - temp_tokens.remove(formatted_token) if remove else temp_tokens.append(formatted_token) - cursor.execute("update users set valid_tokens = ? where user_id = ?", [json.dumps(temp_tokens), user_id]) - connection.commit() + temp_tokens = valid_tokens(user_id) + temp_tokens.remove(formatted_token + ) if remove else temp_tokens.append(formatted_token) + cursor.execute( + "update users set valid_tokens = ? where user_id = ?", + [json.dumps(temp_tokens), user_id] + ) + connection.commit() + def add_token(user_id, formatted_token): - modify_tokens(user_id, formatted_token, False) + modify_tokens(user_id, formatted_token, False) + def revoke_token(user_id, formatted_token): - modify_tokens(user_id, formatted_token, True) + modify_tokens(user_id, formatted_token, True) + def hash_token(token): - return { - "token": hashlib.sha256(str(token["token"]).encode()).hexdigest(), - "expirationDate": token["expirationDate"] - } + return { + "token": hashlib.sha256(str(token["token"]).encode()).hexdigest(), + "expirationDate": token["expirationDate"] + } -def generate_token(): - return { - "token": secrets.token_hex(128), - "expirationDate": int( time.time() * 1000 ) + ( 24 * 60 * 60 * 1000 ) - } +def generate_token(): + return { + "token": secrets.token_hex(128), + "expirationDate": int(time.time() * 1000) + (24 * 60 * 60 * 1000) + } |