Skip to content

Commit 1a2d118

Browse files
committed
Final commit si Dios quiere
1 parent 75b5269 commit 1a2d118

6 files changed

+175
-35
lines changed

.gitignore

-2
This file was deleted.

project3.py

+73-33
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,15 @@ def decrypt(encrypted_info):
9595
no_padding_decrypted_info = decrypted_info.rstrip(b' ')
9696
return no_padding_decrypted_info
9797

98+
def log_auth_request(ip, user_id):
99+
db_cursor.execute(
100+
"INSERT INTO auth_logs (request_ip, user_id) VALUES (?, ?)",
101+
(ip, user_id)
102+
)
103+
db_connection.commit()
104+
98105
def int_to_base64(value):
99-
"""Convert an integer to a Base64URL-encoded string."""
106+
# converts int to a base64 encoded string.
100107
value_hex = format(value, 'x')
101108
if len(value_hex) % 2 == 1:
102109
value_hex = '0' + value_hex
@@ -105,7 +112,7 @@ def int_to_base64(value):
105112
return encoded.decode('utf-8')
106113

107114
def jwks_response():
108-
"""Generate JWKS JSON from unexpired keys."""
115+
# generate JWKS JSON from unexpired keys
109116
print("jwks_response called", flush=True)
110117
all_keys = get_valid_keys_for_jwks()
111118
keys = [
@@ -123,7 +130,7 @@ def jwks_response():
123130
return json.dumps({"keys": keys})
124131

125132
def save_key_to_db(key, expiry, fixed_kid=None):
126-
"""Saves key to db, encrypted"""
133+
# Saves key to db, encrypted
127134
pem_key = key.private_bytes(
128135
encoding=serialization.Encoding.PEM,
129136
format=serialization.PrivateFormat.PKCS8,
@@ -137,7 +144,7 @@ def save_key_to_db(key, expiry, fixed_kid=None):
137144
db_connection.commit()
138145

139146
def get_key_from_db(expired=False):
140-
"""Gets key from db then decrypts it"""
147+
# Gets key from db then decrypts it
141148
current_time = int(datetime.datetime.utcnow().timestamp())
142149
db_cursor.execute(
143150
"SELECT kid, key FROM keys WHERE exp {} ? ORDER BY exp {} LIMIT 1".format(
@@ -153,15 +160,15 @@ def get_key_from_db(expired=False):
153160
return None, None
154161

155162
def get_valid_keys_for_jwks():
156-
"""Retrieve all unexpired keys for JWKS."""
163+
# gets unexpired keys
157164
current_time = int(datetime.datetime.utcnow().timestamp())
158165
db_cursor.execute("SELECT kid, key FROM keys WHERE exp > ?", (current_time,))
159166
result = db_cursor.fetchall()
160167
print("All valid keys for JWKS:", result, flush=True)
161168
return result
162169

163170
def initialize_starter_keys():
164-
"""Initialize one expired and one valid key in the database."""
171+
# Initialize 1 valid and 1 expired key in db
165172
print("Initializing starter keys...", flush=True)
166173
current_time = int(datetime.datetime.utcnow().timestamp())
167174
expired_time = current_time - 3600 # Expired an hour ago
@@ -178,7 +185,7 @@ def initialize_starter_keys():
178185
print("Valid key inserted", flush=True)
179186

180187
def reset_database():
181-
"""Reset the database by dropping and recreating the keys table."""
188+
# Reset the database by dropping and recreating the keys table
182189
print("Resetting database...", flush=True)
183190
db_cursor.execute("DROP TABLE IF EXISTS keys")
184191
db_cursor.execute('''
@@ -201,33 +208,61 @@ def do_POST(self):
201208

202209
# AUTH
203210
if parsed_path.path == "/auth":
204-
expired = 'expired' in params
205-
kid, pem_key = get_key_from_db(expired)
211+
content_length = int(self.headers['Content-Length'])
212+
body = self.rfile.read(content_length).decode('utf-8')
206213

207-
if pem_key is None:
208-
self.send_response(404)
209-
self.end_headers()
210-
self.wfile.write(b"Key not found")
211-
return
212214

213-
private_key = serialization.load_pem_private_key(pem_key, password=None)
214-
expiry_time = datetime.datetime.utcnow() + (datetime.timedelta(hours=1) if not expired else datetime.timedelta(hours=-1))
215+
try:
216+
data = json.loads(body)
217+
username = data["username"]
218+
219+
user_id = self.get_user_id_by_username(username)
220+
if user_id is None:
221+
self.send_response(404)
222+
self.end_headers()
223+
self.wfile.write(b"User not found")
224+
return
225+
226+
227+
228+
expired = 'expired' in params
229+
kid, pem_key = get_key_from_db(expired)
230+
231+
if pem_key is None:
232+
self.send_response(404)
233+
self.end_headers()
234+
self.wfile.write(b"Key not found")
235+
return
236+
237+
private_key = serialization.load_pem_private_key(pem_key, password=None)
238+
expiry_time = datetime.datetime.utcnow() + (datetime.timedelta(hours=1) if not expired else datetime.timedelta(hours=-1))
215239

216-
headers = {"kid": str(kid)}
217-
token_payload = {
218-
"user": "username",
219-
"exp": expiry_time.timestamp()
220-
}
221-
encoded_jwt = jwt.encode(token_payload, private_key, algorithm="RS256", headers=headers)
240+
headers = {"kid": str(kid)}
241+
token_payload = {
242+
"user": username,
243+
"exp": expiry_time.timestamp()
244+
}
245+
encoded_jwt = jwt.encode(token_payload, private_key, algorithm="RS256", headers=headers)
222246

223-
self.send_response(200)
224-
self.send_header("Content-Type", "application/json")
225-
self.end_headers()
226-
self.wfile.write(json.dumps({"token": encoded_jwt}).encode("utf-8"))
247+
# Adding logging here
248+
client_ip = self.client_address[0]
249+
log_auth_request(client_ip, user_id)
250+
251+
self.send_response(200)
252+
self.send_header("Content-Type", "application/json")
253+
self.end_headers()
254+
self.wfile.write(json.dumps({"token": encoded_jwt}).encode("utf-8"))
255+
256+
except KeyError:
257+
self.send_response(400) # username
258+
self.end_headers()
259+
self.wfile.write(b"Missing 'username'")
260+
except json.JSONDecodeError:
261+
self.send_response(400) # json
262+
self.end_headers()
263+
self.wfile.write(b"Invalid JSON format")
227264
return
228-
229-
self.send_response(405)
230-
self.end_headers()
265+
231266

232267
# REGISTER
233268
if parsed_path.path == "/register":
@@ -239,12 +274,12 @@ def do_POST(self):
239274
email = data["email"]
240275

241276
password = str(uuid.uuid4())
242-
hashed_password = PassHasher.hash(password)
277+
password_hash = PassHasher.hash(password)
243278

244279
try:
245280
db_cursor.execute(
246-
"INSERT INTO users (username, hashed_password, email) VALUES (?, ?, ?)",
247-
(username, hashed_password, email)
281+
"INSERT INTO users (username, password_hash, email) VALUES (?, ?, ?)",
282+
(username, password_hash, email)
248283
)
249284
db_connection.commit()
250285

@@ -253,7 +288,7 @@ def do_POST(self):
253288
self.send_header("Content-Type", "application/json")
254289
self.end_headers()
255290
self.wfile.write(json.dumps({"password": password}).encode("utf-8"))
256-
except:
291+
except sqlite3.IntegrityError:
257292
self.send_response(400) # error, throw 400
258293
self.end_headers()
259294
self.wfile.write(b"Username and/or email address already exists")
@@ -276,6 +311,11 @@ def do_GET(self):
276311

277312
self.send_response(405)
278313
self.end_headers()
314+
315+
def get_user_id_by_username(self, username):
316+
db_cursor.execute("SELECT id FROM users WHERE username = ?", (username,))
317+
result = db_cursor.fetchone()
318+
return result[0] if result else None
279319

280320
# Initialize database and start server
281321
reset_database()

project3Gradebot.PNG

17.6 KB
Loading

project3Test.PNG

11 KB
Loading

test3.py

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import unittest
2+
import datetime
3+
import json
4+
import jwt
5+
import requests
6+
import sqlite3
7+
from threading import Thread
8+
from http.server import HTTPServer
9+
from cryptography.hazmat.primitives.asymmetric import rsa
10+
from project3 import MyServer, reset_database, save_key_to_db, get_key_from_db, jwks_response, log_auth_request
11+
12+
HOST = "http://localhost:8080"
13+
DB_PATH = "totally_not_my_privateKeys.db"
14+
15+
class TestProject3Server(unittest.TestCase):
16+
17+
def setUp(self):
18+
reset_database()
19+
self.server = HTTPServer(("localhost", 8080), MyServer)
20+
self.server_thread = Thread(target=self.server.serve_forever)
21+
self.server_thread.daemon = True
22+
self.server_thread.start()
23+
24+
def tearDown(self):
25+
self.server.shutdown()
26+
self.server.server_close()
27+
self.server_thread.join()
28+
29+
def test_register_endpoint(self):
30+
response = requests.post(f"{HOST}/register", json={
31+
"username": "testuser",
32+
"email": "[email protected]"
33+
})
34+
self.assertEqual(response.status_code, 201)
35+
data = response.json()
36+
self.assertIn("password", data)
37+
38+
# Validate user was added to the database
39+
conn = sqlite3.connect(DB_PATH)
40+
cursor = conn.cursor()
41+
cursor.execute("SELECT * FROM users WHERE username = ?", ("testuser",))
42+
user = cursor.fetchone()
43+
conn.close()
44+
self.assertIsNotNone(user, "User was not added to the database.")
45+
46+
def test_auth_endpoint_jwt_valid(self):
47+
# First register the user
48+
requests.post(f"{HOST}/register", json={
49+
"username": "testuser",
50+
"email": "[email protected]"
51+
})
52+
response = requests.post(f"{HOST}/auth", json={
53+
"username": "testuser"
54+
})
55+
self.assertEqual(response.status_code, 200)
56+
token = response.json()["token"]
57+
decoded = jwt.decode(token, options={"verify_signature": False})
58+
self.assertEqual(decoded["user"], "username")
59+
self.assertGreater(decoded["exp"], datetime.datetime.utcnow().timestamp())
60+
61+
def test_auth_logging(self):
62+
# First register the user
63+
requests.post(f"{HOST}/register", json={
64+
"username": "testuser",
65+
"email": "[email protected]"
66+
})
67+
requests.post(f"{HOST}/auth", json={
68+
"username": "testuser"
69+
})
70+
71+
# Check logs in the database
72+
conn = sqlite3.connect(DB_PATH)
73+
cursor = conn.cursor()
74+
cursor.execute("SELECT * FROM auth_logs")
75+
logs = cursor.fetchall()
76+
conn.close()
77+
self.assertGreater(len(logs), 0, "Auth requests were not logged.")
78+
79+
def test_jwks_json_endpoint(self):
80+
response = requests.get(f"{HOST}/.well-known/jwks.json")
81+
self.assertEqual(response.status_code, 200)
82+
keys = response.json()["keys"]
83+
self.assertGreater(len(keys), 0, "No keys returned in JWKS")
84+
for key in keys:
85+
self.assertIn("kid", key)
86+
self.assertIn("n", key)
87+
self.assertIn("e", key)
88+
self.assertEqual(key["alg"], "RS256")
89+
self.assertEqual(key["kty"], "RSA")
90+
self.assertEqual(key["use"], "sig")
91+
92+
def test_database_key_insertion_and_retrieval(self):
93+
expiry_time = int((datetime.datetime.utcnow() + datetime.timedelta(hours=1)).timestamp())
94+
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
95+
save_key_to_db(private_key, expiry_time, fixed_kid=99)
96+
97+
kid, pem_key = get_key_from_db(expired=False)
98+
self.assertEqual(kid, 99, "Retrieved key ID does not match saved key ID.")
99+
self.assertIsNotNone(pem_key, "No key was retrieved from the database.")
100+
101+
if __name__ == "__main__":
102+
unittest.main()

totally_not_my_privateKeys.db

36 KB
Binary file not shown.

0 commit comments

Comments
 (0)