Skip to content

Commit 47db08e

Browse files
authored
Add lockout functionality (#62)
* 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
1 parent 5ee1ad1 commit 47db08e

File tree

10 files changed

+584
-426
lines changed

10 files changed

+584
-426
lines changed

backend/common/database.py

+16-34
Original file line numberDiff line numberDiff line change
@@ -147,18 +147,6 @@ def checkInput(compName, dayNum, uid, solution):
147147
# note: for more advanced processing, we might consider having a timeout if a user tries too many things too quickly
148148
# but idk how to implement this too well
149149

150-
# Get all the information about a user given their uid
151-
# Returns all information in the form of a dictionary
152-
def getUserInfo(uid):
153-
query = f"""
154-
select * from Users where uid = {uid};
155-
"""
156-
cur.execute(query)
157-
158-
# only one entry should be returned since day number is unique
159-
t = cur.fetchone()
160-
return t
161-
162150
# Get all the information about a user's stats in a certain competition
163151
# Returns all information in the form of a list of 'solved objects'
164152
def getUserStatsPerComp(compName, uid):
@@ -172,23 +160,7 @@ def getUserStatsPerComp(compName, uid):
172160
right outer join Parts p on s.pid = p.pid
173161
join Questions q on p.qid = q.qid
174162
join Competitions c on q.cid = c.cid
175-
where s.uid = {uid} and c.name = '{compName}';
176-
"""
177-
cur.execute(query)
178-
179-
return cur.fetchall()
180-
181-
# Get only the number of stars and points for a user.
182-
# Returns extremely simple info
183-
def getBasicUserStatsPerComp(compName, uid):
184-
185-
# A right outer join returns all the results from the parts table, even if there is no solves
186-
# Best to look up examples :D
187-
# Use this information to deduce whether a user has solved a part or not
188-
query = f"""
189-
select u.username, u.github, s.numStars, s.score from Stats s
190-
right outer join Users u
191-
where s.uid = {uid} and c.name = '{compName}';
163+
where i.uid = {uid} and c.name = {compName};
192164
"""
193165
cur.execute(query)
194166

@@ -217,12 +189,22 @@ def getAllCompetitions():
217189
def updateUsername(username, uid):
218190
query = f"""
219191
update Users
220-
set username = '{username}'
192+
set username = {username}
221193
where uid = {uid};
222194
"""
223195
cur.execute(query)
224196
conn.commit()
225-
'''
226-
cursor.close()
227-
conn.close()
228-
'''
197+
198+
# DO NOT EVER EXECUTE THIS FUNCTION BRUH
199+
def dropDatabase():
200+
query = f"""
201+
SELECT 'DROP TABLE IF EXISTS "' || tablename || '" CASCADE;'
202+
from
203+
pg_tables WHERE schemaname = 'advent';
204+
"""
205+
cur.execute(query)
206+
conn.commit()
207+
208+
def clear_database():
209+
conn = get_connection()
210+
cursor = conn.cursor()

backend/common/redis.py

+50-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,50 @@
1-
import redis
2-
3-
# We're using Redis as a way to store codes with expiry dates - it might a bit
4-
# overkill, but it works
5-
6-
cache = redis.Redis(host="redis", port=6379, db=0)
1+
from datetime import timedelta
2+
import redis
3+
4+
# We're using Redis as a way to store codes with expiry dates - it might a bit
5+
# overkill, but it works
6+
7+
MINUTES_IN_DAY = 1440
8+
9+
cache = redis.Redis(host="redis", port=6379, db=0)
10+
11+
## EMAIL VERIFICATION
12+
13+
## LOCKOUT
14+
15+
def register_incorrect(id):
16+
times = cache.get(f"attempts_{id}")
17+
18+
if times is None:
19+
times = 0
20+
21+
cache.set(f"attempts_{id}", int(times) + 1)
22+
23+
def incorrect_attempts(id):
24+
attempts = cache.get(f"attempts_{id}")
25+
26+
if attempts is None:
27+
return 0
28+
else:
29+
return int(attempts)
30+
31+
def calculate_time(attempts):
32+
if attempts < 3:
33+
return 0
34+
35+
minutes = 2 ** (attempts - 3)
36+
37+
if minutes > MINUTES_IN_DAY:
38+
return MINUTES_IN_DAY
39+
else:
40+
return minutes
41+
42+
def block(id, time):
43+
cache.set(f"block_{id}", "", ex=timedelta(minutes=time))
44+
45+
def is_blocked(id):
46+
token = cache.get(f"block_{id}")
47+
return token is not None
48+
49+
def clear_redis():
50+
cache.flushdb()

backend/database/database.py

+28-28
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
1-
import os
2-
from psycopg2.pool import ThreadedConnectionPool
3-
4-
user = os.environ["POSTGRES_USER"]
5-
password = os.environ["POSTGRES_PASSWORD"]
6-
host = os.environ["POSTGRES_HOST"]
7-
port = os.environ["POSTGRES_PORT"]
8-
database = os.environ["POSTGRES_DB"]
9-
10-
# TABLES = ["Users", "Questions", "Parts", "Competitions", "Inputs", "Solves"]
11-
12-
db = ThreadedConnectionPool(
13-
1, 20,
14-
user=user,
15-
password=password,
16-
host=host,
17-
port=port,
18-
database=database
19-
)
20-
21-
def clear_database():
22-
conn = db.getconn()
23-
24-
with conn.cursor() as cursor:
25-
cursor.execute(f"""SELECT truncate_tables();""")
26-
conn.commit()
27-
28-
db.putconn(conn)
1+
import os
2+
from psycopg2.pool import ThreadedConnectionPool
3+
4+
user = os.environ["POSTGRES_USER"]
5+
password = os.environ["POSTGRES_PASSWORD"]
6+
host = os.environ["POSTGRES_HOST"]
7+
port = os.environ["POSTGRES_PORT"]
8+
database = os.environ["POSTGRES_DB"]
9+
10+
# TABLES = ["Users", "Questions", "Parts", "Competitions", "Inputs", "Solves"]
11+
12+
db = ThreadedConnectionPool(
13+
1, 20,
14+
user=user,
15+
password=password,
16+
host=host,
17+
port=port,
18+
database=database
19+
)
20+
21+
def clear_database():
22+
conn = db.getconn()
23+
24+
with conn.cursor() as cursor:
25+
cursor.execute(f"""SELECT truncate_tables();""")
26+
conn.commit()
27+
28+
db.putconn(conn)

backend/database/user.py

+93-56
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,93 @@
1-
from database.database import db
2-
3-
4-
def add_user(email, username, password) -> int:
5-
"""Adds a user to the database, returning their ID."""
6-
7-
conn = db.getconn()
8-
9-
with conn.cursor() as cursor:
10-
cursor.execute(f"INSERT INTO Users (email, username, password) VALUES ('{email}', '{username}', '{password}')")
11-
conn.commit()
12-
13-
cursor.execute(f"SELECT uid FROM Users WHERE email = '{email}'")
14-
id = cursor.fetchone()[0]
15-
16-
db.putconn(conn)
17-
return id
18-
19-
20-
def fetch_user(email: str):
21-
"""Given a user's email, fetches their content from the database."""
22-
23-
conn = db.getconn()
24-
25-
with conn.cursor() as cursor:
26-
cursor.execute(f"SELECT * FROM Users WHERE email = '{email}'")
27-
result = cursor.fetchone()
28-
29-
db.putconn(conn)
30-
return result
31-
32-
33-
def email_exists(email: str) -> bool:
34-
"""Checks if an email exists in the users table."""
35-
36-
conn = db.getconn()
37-
38-
with conn.cursor() as cursor:
39-
cursor.execute(f"SELECT * FROM Users WHERE email = '{email}'")
40-
results = cursor.fetchall()
41-
42-
db.putconn(conn)
43-
return results != []
44-
45-
46-
def username_exists(username: str) -> bool:
47-
"""Checks if a username is already used."""
48-
49-
conn = db.getconn()
50-
51-
with conn.cursor() as cursor:
52-
cursor.execute(f"SELECT * FROM Users WHERE username = '{username}'")
53-
results = cursor.fetchall()
54-
55-
db.putconn(conn)
56-
return results != []
1+
from database.database import db
2+
3+
# Get all the information about a user given their uid
4+
# Returns all information in the form of a dictionary
5+
def get_user_info(uid):
6+
conn = db.getconn()
7+
8+
with conn.cursor() as cursor:
9+
query = f"""
10+
select * from Users where uid = {uid};
11+
"""
12+
cursor.execute(query)
13+
14+
# only one entry should be returned since day number is unique
15+
t = cursor.fetchone()
16+
17+
db.putconn(conn)
18+
return t
19+
20+
21+
def add_user(email, username, password) -> int:
22+
"""Adds a user to the database, returning their ID."""
23+
24+
conn = db.getconn()
25+
26+
with conn.cursor() as cursor:
27+
cursor.execute("INSERT INTO Users (email, username, password) VALUES (%s, %s, %s)",
28+
(email, username, password))
29+
conn.commit()
30+
31+
cursor.execute("SELECT uid FROM Users WHERE email = %s", (email,))
32+
id = cursor.fetchone()[0]
33+
34+
db.putconn(conn)
35+
return id
36+
37+
38+
def fetch_id(email: str):
39+
"""Given a user's email, fetches their ID."""
40+
41+
conn = db.getconn()
42+
43+
with conn.cursor() as cursor:
44+
cursor.execute("SELECT uid FROM Users WHERE email = %s", (email,))
45+
result = cursor.fetchone()
46+
47+
if result is None:
48+
db.putconn(conn)
49+
return None
50+
51+
id = result[0]
52+
53+
db.putconn(conn)
54+
return id
55+
56+
57+
def fetch_user(email: str):
58+
"""Given a user's email, fetches their content from the database."""
59+
60+
conn = db.getconn()
61+
62+
with conn.cursor() as cursor:
63+
cursor.execute("SELECT * FROM Users WHERE email = %s", (email,))
64+
result = cursor.fetchone()
65+
66+
db.putconn(conn)
67+
return result
68+
69+
70+
def email_exists(email: str) -> bool:
71+
"""Checks if an email exists in the users table."""
72+
73+
conn = db.getconn()
74+
75+
with conn.cursor() as cursor:
76+
cursor.execute("SELECT * FROM Users WHERE email = %s", (email,))
77+
results = cursor.fetchall()
78+
79+
db.putconn(conn)
80+
return results != []
81+
82+
83+
def username_exists(username: str) -> bool:
84+
"""Checks if a username is already used."""
85+
86+
conn = db.getconn()
87+
88+
with conn.cursor() as cursor:
89+
cursor.execute("SELECT * FROM Users WHERE username = %s", (username,))
90+
results = cursor.fetchall()
91+
92+
db.putconn(conn)
93+
return results != []

0 commit comments

Comments
 (0)