Skip to content

Commit

Permalink
Add lockout functionality (#62)
Browse files Browse the repository at this point in the history
* feat: moved advent folder -> puzzles, added some comments

* feat(docker): start separation of dev and prod builds, add pytest functionality to backend

* feat(docker): added dev/prod to frontend, transition frontend to yarn

* fix: remove .vscode folder

* fix(makefile): restructured makefile a bit

* feat: removed .vscode folder from git

* feat(auth): get rudimentary autotesting in place, created clear_database function

* feat(test): added all tests for auth/register

* fix(puzzle): changed blueprint in routes/puzzle.py

* feat(auth): refactored registration system, database connections

* fix(auth): minor changes to constructor

* feat(auth): implement email verification endpoints

* feat(test): using fixtures

* feat(auth): finish autotests, still needs commenting

* feat(auth): finished writing tests for the most part

* feat(auth): complete tests for basic auth system

* fix(auth): removed duplicate clear_database function

* fix(auth): add basic lockout functionality

* fix(auth): fix clear_database utility function

* fix(auth): change requests to conform with DB

* fix(auth): add basic lockout to /login route

* feat(auth): add function to carry over CSRF in headers
  • Loading branch information
hanyuone authored Aug 9, 2022
1 parent 5ee1ad1 commit 47db08e
Show file tree
Hide file tree
Showing 10 changed files with 584 additions and 426 deletions.
50 changes: 16 additions & 34 deletions backend/common/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,18 +147,6 @@ def checkInput(compName, dayNum, uid, solution):
# note: for more advanced processing, we might consider having a timeout if a user tries too many things too quickly
# but idk how to implement this too well

# Get all the information about a user given their uid
# Returns all information in the form of a dictionary
def getUserInfo(uid):
query = f"""
select * from Users where uid = {uid};
"""
cur.execute(query)

# only one entry should be returned since day number is unique
t = cur.fetchone()
return t

# Get all the information about a user's stats in a certain competition
# Returns all information in the form of a list of 'solved objects'
def getUserStatsPerComp(compName, uid):
Expand All @@ -172,23 +160,7 @@ def getUserStatsPerComp(compName, uid):
right outer join Parts p on s.pid = p.pid
join Questions q on p.qid = q.qid
join Competitions c on q.cid = c.cid
where s.uid = {uid} and c.name = '{compName}';
"""
cur.execute(query)

return cur.fetchall()

# Get only the number of stars and points for a user.
# Returns extremely simple info
def getBasicUserStatsPerComp(compName, uid):

# A right outer join returns all the results from the parts table, even if there is no solves
# Best to look up examples :D
# Use this information to deduce whether a user has solved a part or not
query = f"""
select u.username, u.github, s.numStars, s.score from Stats s
right outer join Users u
where s.uid = {uid} and c.name = '{compName}';
where i.uid = {uid} and c.name = {compName};
"""
cur.execute(query)

Expand Down Expand Up @@ -217,12 +189,22 @@ def getAllCompetitions():
def updateUsername(username, uid):
query = f"""
update Users
set username = '{username}'
set username = {username}
where uid = {uid};
"""
cur.execute(query)
conn.commit()
'''
cursor.close()
conn.close()
'''

# DO NOT EVER EXECUTE THIS FUNCTION BRUH
def dropDatabase():
query = f"""
SELECT 'DROP TABLE IF EXISTS "' || tablename || '" CASCADE;'
from
pg_tables WHERE schemaname = 'advent';
"""
cur.execute(query)
conn.commit()

def clear_database():
conn = get_connection()
cursor = conn.cursor()
56 changes: 50 additions & 6 deletions backend/common/redis.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,50 @@
import redis

# We're using Redis as a way to store codes with expiry dates - it might a bit
# overkill, but it works

cache = redis.Redis(host="redis", port=6379, db=0)
from datetime import timedelta
import redis

# We're using Redis as a way to store codes with expiry dates - it might a bit
# overkill, but it works

MINUTES_IN_DAY = 1440

cache = redis.Redis(host="redis", port=6379, db=0)

## EMAIL VERIFICATION

## LOCKOUT

def register_incorrect(id):
times = cache.get(f"attempts_{id}")

if times is None:
times = 0

cache.set(f"attempts_{id}", int(times) + 1)

def incorrect_attempts(id):
attempts = cache.get(f"attempts_{id}")

if attempts is None:
return 0
else:
return int(attempts)

def calculate_time(attempts):
if attempts < 3:
return 0

minutes = 2 ** (attempts - 3)

if minutes > MINUTES_IN_DAY:
return MINUTES_IN_DAY
else:
return minutes

def block(id, time):
cache.set(f"block_{id}", "", ex=timedelta(minutes=time))

def is_blocked(id):
token = cache.get(f"block_{id}")
return token is not None

def clear_redis():
cache.flushdb()
56 changes: 28 additions & 28 deletions backend/database/database.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
import os
from psycopg2.pool import ThreadedConnectionPool

user = os.environ["POSTGRES_USER"]
password = os.environ["POSTGRES_PASSWORD"]
host = os.environ["POSTGRES_HOST"]
port = os.environ["POSTGRES_PORT"]
database = os.environ["POSTGRES_DB"]

# TABLES = ["Users", "Questions", "Parts", "Competitions", "Inputs", "Solves"]

db = ThreadedConnectionPool(
1, 20,
user=user,
password=password,
host=host,
port=port,
database=database
)

def clear_database():
conn = db.getconn()

with conn.cursor() as cursor:
cursor.execute(f"""SELECT truncate_tables();""")
conn.commit()

db.putconn(conn)
import os
from psycopg2.pool import ThreadedConnectionPool

user = os.environ["POSTGRES_USER"]
password = os.environ["POSTGRES_PASSWORD"]
host = os.environ["POSTGRES_HOST"]
port = os.environ["POSTGRES_PORT"]
database = os.environ["POSTGRES_DB"]

# TABLES = ["Users", "Questions", "Parts", "Competitions", "Inputs", "Solves"]

db = ThreadedConnectionPool(
1, 20,
user=user,
password=password,
host=host,
port=port,
database=database
)

def clear_database():
conn = db.getconn()

with conn.cursor() as cursor:
cursor.execute(f"""SELECT truncate_tables();""")
conn.commit()

db.putconn(conn)
149 changes: 93 additions & 56 deletions backend/database/user.py
Original file line number Diff line number Diff line change
@@ -1,56 +1,93 @@
from database.database import db


def add_user(email, username, password) -> int:
"""Adds a user to the database, returning their ID."""

conn = db.getconn()

with conn.cursor() as cursor:
cursor.execute(f"INSERT INTO Users (email, username, password) VALUES ('{email}', '{username}', '{password}')")
conn.commit()

cursor.execute(f"SELECT uid FROM Users WHERE email = '{email}'")
id = cursor.fetchone()[0]

db.putconn(conn)
return id


def fetch_user(email: str):
"""Given a user's email, fetches their content from the database."""

conn = db.getconn()

with conn.cursor() as cursor:
cursor.execute(f"SELECT * FROM Users WHERE email = '{email}'")
result = cursor.fetchone()

db.putconn(conn)
return result


def email_exists(email: str) -> bool:
"""Checks if an email exists in the users table."""

conn = db.getconn()

with conn.cursor() as cursor:
cursor.execute(f"SELECT * FROM Users WHERE email = '{email}'")
results = cursor.fetchall()

db.putconn(conn)
return results != []


def username_exists(username: str) -> bool:
"""Checks if a username is already used."""

conn = db.getconn()

with conn.cursor() as cursor:
cursor.execute(f"SELECT * FROM Users WHERE username = '{username}'")
results = cursor.fetchall()

db.putconn(conn)
return results != []
from database.database import db

# Get all the information about a user given their uid
# Returns all information in the form of a dictionary
def get_user_info(uid):
conn = db.getconn()

with conn.cursor() as cursor:
query = f"""
select * from Users where uid = {uid};
"""
cursor.execute(query)

# only one entry should be returned since day number is unique
t = cursor.fetchone()

db.putconn(conn)
return t


def add_user(email, username, password) -> int:
"""Adds a user to the database, returning their ID."""

conn = db.getconn()

with conn.cursor() as cursor:
cursor.execute("INSERT INTO Users (email, username, password) VALUES (%s, %s, %s)",
(email, username, password))
conn.commit()

cursor.execute("SELECT uid FROM Users WHERE email = %s", (email,))
id = cursor.fetchone()[0]

db.putconn(conn)
return id


def fetch_id(email: str):
"""Given a user's email, fetches their ID."""

conn = db.getconn()

with conn.cursor() as cursor:
cursor.execute("SELECT uid FROM Users WHERE email = %s", (email,))
result = cursor.fetchone()

if result is None:
db.putconn(conn)
return None

id = result[0]

db.putconn(conn)
return id


def fetch_user(email: str):
"""Given a user's email, fetches their content from the database."""

conn = db.getconn()

with conn.cursor() as cursor:
cursor.execute("SELECT * FROM Users WHERE email = %s", (email,))
result = cursor.fetchone()

db.putconn(conn)
return result


def email_exists(email: str) -> bool:
"""Checks if an email exists in the users table."""

conn = db.getconn()

with conn.cursor() as cursor:
cursor.execute("SELECT * FROM Users WHERE email = %s", (email,))
results = cursor.fetchall()

db.putconn(conn)
return results != []


def username_exists(username: str) -> bool:
"""Checks if a username is already used."""

conn = db.getconn()

with conn.cursor() as cursor:
cursor.execute("SELECT * FROM Users WHERE username = %s", (username,))
results = cursor.fetchall()

db.putconn(conn)
return results != []
Loading

0 comments on commit 47db08e

Please sign in to comment.