Skip to content

Commit 22942b6

Browse files
committed
Update error message if API key is wrong
+ fix tests some more
1 parent b335174 commit 22942b6

File tree

5 files changed

+44
-86
lines changed

5 files changed

+44
-86
lines changed

app/core/security.py

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -128,34 +128,18 @@ async def verify_token(request: Request, credentials: Optional[HTTPAuthorization
128128

129129
# Now we can do the rest of the logic. run test routines first, then check guest or proper user.
130130
try:
131-
# Skip email verification in test environment
132-
if settings.ENVIRONMENT == "TEST" and settings.SKIP_EMAIL_VERIFICATION:
133-
print("Test environment detected - skipping email verification and checking database for user details")
134-
is_guest = not credentials
135-
user_id = f"test_user{'_guest' if is_guest else ''}"
136-
if credentials:
137-
try:
138-
decoded = jwt.decode(credentials.credentials, settings.SECRET_KEY, algorithms=["HS256"])
139-
user_id = decoded.get("user_id", "test_user")
140-
except:
141-
pass
142-
143-
return {
144-
"user_id": user_id,
145-
"is_guest": is_guest,
146-
"email_verified": True, # Always verified in tests
147-
"balance": settings.GUEST_MAX_CREDITS if is_guest else settings.USER_MAX_CREDITS
148-
}
149-
150-
# Regular verification logic...
151131
is_guest = not credentials
152132
if is_guest:
153133
print("No credentials supplied, continuing as guest...")
154134
user = generate_guest_id(request)
155135
user["email_verified"] = True
156136
else:
157137
print("Credentials supplied, decoding token...")
158-
decoded = jwt.decode(credentials.credentials, settings.SECRET_KEY, algorithms=["HS256"])
138+
try:
139+
decoded = jwt.decode(credentials.credentials, settings.SECRET_KEY, algorithms=["HS256"])
140+
except jwt.JWTError as e:
141+
print("JWT decoding error:", str(e))
142+
raise HTTPException(status_code=401, detail="Your API key is invalid. Please make sure you have set the API key correctly.")
159143
# Check if token is API key
160144
if decoded.get("token_type") == "api_key":
161145
print("Got API key, verifying...")

tests/api/v1/routes/test_auth.py

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import warnings
2323
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
2424
import app.core.security as backend
25+
from typing import Optional
2526

2627
# Add warning filter for the jose library
2728
warnings.filterwarnings(
@@ -707,29 +708,21 @@ def test_get_credits_success(mock_auth_flow, mock_db_query, auth_header):
707708

708709
def test_get_credits_unauthorized():
709710
"""Test credits retrieval without auth"""
710-
with patch('app.core.security.verify_token') as mock_verify, \
711-
patch('app.core.security.generate_guest_id') as mock_guest, \
712-
patch('app.core.security.db') as mock_db:
713-
714-
# Make verify_token raise unauthorized
715-
mock_verify.side_effect = HTTPException(
716-
status_code=401,
717-
detail="Unauthorized"
718-
)
719-
720-
# Prevent guest user creation
721-
mock_guest.side_effect = HTTPException(
722-
status_code=401,
723-
detail="Unauthorized"
724-
)
725-
726-
# Mock db to prevent connection errors
727-
mock_db.table.return_value = MagicMock()
728-
711+
# Create a function with the same signature asverify_token
712+
async def mock_verify_token(request: Request, credentials: Optional[HTTPAuthorizationCredentials] = None):
713+
raise HTTPException(status_code=401, detail="Unauthorized")
714+
715+
# Use dependency_overrides to replace the verify_token dependency
716+
app.dependency_overrides[backend.verify_token] = mock_verify_token
717+
718+
try:
729719
response = client.get("/auth/credits")
730720

731721
assert response.status_code == 401
732722
assert "unauthorized" in response.json()["detail"].lower()
723+
finally:
724+
# Clean up the override after the test
725+
app.dependency_overrides.pop(backend.verify_token, None)
733726

734727
def test_get_credits_guest_user(mock_auth_flow, mock_db_query):
735728
"""Test credits retrieval for guest user"""

tests/conftest.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,12 +181,14 @@ def _create_user(identifier=None):
181181

182182

183183
@pytest.fixture
184-
def client():
184+
def locally_running_client():
185185
"""Create a test client that connects to the running server"""
186186
import httpx
187187
with httpx.Client(base_url="http://localhost:8000", timeout=50.0) as client:
188188
yield client
189189

190+
191+
190192
@pytest.fixture
191193
def auth_headers(test_user):
192194
"""Fixture for authentication headers"""
Lines changed: 23 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1+
from fastapi import FastAPI
2+
from fastapi.testclient import TestClient
13
import pytest
2-
import httpx
34
from datetime import datetime, UTC
45
import time
5-
from app.core.config import settings
6-
from jose import jwt
6+
from app.api.v1.routes.auth import router
7+
8+
# Set up the FastAPI app and TestClient
9+
app = FastAPI()
10+
app.include_router(router) # Include the authentication router
11+
client = TestClient(app)
712

813
@pytest.fixture
9-
def test_user_cleanup(client):
14+
def test_user_cleanup():
1015
# Store created resources for cleanup
1116
created_resources = {
1217
"users": [],
@@ -19,15 +24,15 @@ def test_user_cleanup(client):
1924
for user in created_resources["users"]:
2025
print(f"Cleaning up test user: {user['email']}")
2126
log = user
22-
response = client.post(f"/v1/auth/remove_user", json=user)
27+
response = client.post(f"/auth/remove_user", json=user)
2328
if response.status_code >= 400:
2429
print(f"Error cleaning up user {log}: {response.text}")
2530
raise Exception(f"Error cleaning up user {log}: {response.text}")
2631

2732
@pytest.mark.order(2)
28-
def test_signup_flow(client, test_user):
33+
def test_signup_flow(test_user):
2934
"""Test basic signup without verification"""
30-
response = client.post("/v1/auth/sign_up", json=test_user())
35+
response = client.post("/auth/sign_up", json=test_user())
3136

3237
# Print detailed debug information
3338
print(f"\nTest user: {test_user}")
@@ -47,35 +52,9 @@ def test_signup_flow(client, test_user):
4752
assert response.status_code == 200
4853
assert "message" in response.json()
4954
assert "email" in response.json()
50-
51-
@pytest.mark.order(3)
52-
@pytest.mark.rate_limited
53-
def test_rate_limiting_flow(client):
54-
"""Test rate limiting on signup"""
55-
print("\nTesting rate limiting on signup...")
56-
# First create a test user that we'll use for other endpoints
57-
test_email = f"test_{datetime.now(UTC).timestamp()}@example.com"
58-
test_user = {
59-
"email": test_email,
60-
"password": "TestPass123!"
61-
}
6255

63-
signup_responses = []
64-
for i in range(7): # Try more than the limit (5/minute)
65-
response = client.post("/v1/auth/sign_up", json={
66-
"email": f"test_{datetime.now(UTC).timestamp()}_{i}@example.com",
67-
"password": "TestPass123!"
68-
})
69-
print(f"Signup request {i+1}: {response.status_code} - {response.text}")
70-
signup_responses.append(response.status_code)
71-
time.sleep(0.1)
72-
73-
# Check rate limiting was triggered
74-
assert 429 in signup_responses, "Rate limiting not triggered"
75-
assert signup_responses.count(429) > 0, "No requests were rate limited"
76-
77-
@pytest.mark.order(4)
78-
def test_multiple_users_flow(client, test_user, test_user_cleanup):
56+
@pytest.mark.order(3)
57+
def test_multiple_users_flow(test_user, test_user_cleanup):
7958
"""
8059
Test multiple users creating and using API keys:
8160
1. User 1 signs up and creates an API key
@@ -92,14 +71,14 @@ def test_multiple_users_flow(client, test_user, test_user_cleanup):
9271

9372
# Step 1: User 1 signs up
9473
signup_response1 = client.post(
95-
"/v1/auth/sign_up",
74+
"/auth/sign_up",
9675
json=user1
9776
)
9877
assert signup_response1.status_code == 200, f"User 1 signup failed: {signup_response1.text}"
9978

10079
# ... and User 1 creates an API key
10180
api_key_response1 = client.post(
102-
"/v1/auth/create_api_key",
81+
"/auth/create_api_key",
10382
json=user1
10483
)
10584
assert api_key_response1.status_code == 200, f"User 1 API key creation failed: {api_key_response1.text}"
@@ -108,15 +87,15 @@ def test_multiple_users_flow(client, test_user, test_user_cleanup):
10887

10988
# Step 2: User 2 signs up
11089
signup_response2 = client.post(
111-
"/v1/auth/sign_up",
90+
"/auth/sign_up",
11291
json=user2
11392
)
11493
assert signup_response2.status_code == 200, f"User 2 signup failed: {signup_response2.text}"
11594
assert signup_response2.json()["email"] == user2["email"]
11695

11796
# ... and User 2 creates an API key
11897
api_key_response2 = client.post(
119-
"/v1/auth/create_api_key",
98+
"/auth/create_api_key",
12099
json=user2
121100
)
122101
assert api_key_response2.status_code == 200, f"User 2 API key creation failed: {api_key_response2.text}"
@@ -131,7 +110,7 @@ def test_multiple_users_flow(client, test_user, test_user_cleanup):
131110
# Due to row-level security, and User 2 being still authenticated, it should sign out User 2 and use the service_role to check whether the API key is valid.
132111
# For this example, we'll use the credits endpoint which uses API keys and verification.
133112
credits_response = client.get(
134-
"/v1/auth/credits",
113+
"/auth/credits",
135114
headers={"Authorization": f"Bearer {user1_api_key}"}
136115
)
137116
assert credits_response.status_code == 200, f"User 1 authenticated request failed: {credits_response.text}"
@@ -142,7 +121,7 @@ def test_multiple_users_flow(client, test_user, test_user_cleanup):
142121

143122
# Step 4: List API keys for User 1
144123
list_keys_response = client.post(
145-
"/v1/auth/api_keys",
124+
"/auth/api_keys",
146125
json=user1
147126
)
148127
assert list_keys_response.status_code == 200
@@ -151,14 +130,14 @@ def test_multiple_users_flow(client, test_user, test_user_cleanup):
151130

152131
# Step 5: Revoke User 1's API key
153132
revoke_response = client.delete(
154-
f"/v1/auth/revoke_api_key/{user1_api_key}",
133+
f"/auth/revoke_api_key/{user1_api_key}",
155134
headers={"Authorization": f"Bearer {user1_api_key}"}
156135
)
157136
assert revoke_response.status_code == 200
158137

159138
# Step 6: Verify the key no longer works
160139
invalid_key_response = client.get(
161-
"/v1/auth/credits",
140+
"/auth/credits",
162141
headers={"Authorization": f"Bearer {user1_api_key}"}
163142
)
164-
assert invalid_key_response.status_code == 401, "Revoked API key still works"
143+
assert invalid_key_response.status_code == 401, "Revoked API key still works"

tests/integration/test_auth_guest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33

44
@pytest.mark.order(2) # Run guest tests second
55
@pytest.mark.guest
6-
def test_guest_user_flow(client):
6+
def test_guest_user_flow(locally_running_client):
77
"""Test guest user functionality"""
88
# Guest user specific tests...

0 commit comments

Comments
 (0)