BOLO-KESARI commited on
Commit Β·
2033599
1
Parent(s): 5a8126f
Switch to direct bcrypt to fix 72-byte error
Browse files- backend/app/core/security.py +17 -24
- test_deployment.py +21 -46
- test_log.txt +16 -0
backend/app/core/security.py
CHANGED
|
@@ -13,38 +13,31 @@ from .config import settings
|
|
| 13 |
from .database import get_db
|
| 14 |
from ..models.user import User
|
| 15 |
|
| 16 |
-
|
| 17 |
-
|
| 18 |
|
| 19 |
# HTTP Bearer token scheme
|
| 20 |
security = HTTPBearer()
|
| 21 |
|
| 22 |
-
|
| 23 |
def hash_password(password: str) -> str:
|
| 24 |
-
"""
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
Hashed password string
|
| 32 |
-
"""
|
| 33 |
-
return pwd_context.hash(password)
|
| 34 |
|
| 35 |
|
| 36 |
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
| 37 |
-
"""
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
True if password matches, False otherwise
|
| 46 |
-
"""
|
| 47 |
-
return pwd_context.verify(plain_password, hashed_password)
|
| 48 |
|
| 49 |
|
| 50 |
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
|
|
|
|
| 13 |
from .database import get_db
|
| 14 |
from ..models.user import User
|
| 15 |
|
| 16 |
+
import bcrypt
|
| 17 |
+
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
| 18 |
|
| 19 |
# HTTP Bearer token scheme
|
| 20 |
security = HTTPBearer()
|
| 21 |
|
|
|
|
| 22 |
def hash_password(password: str) -> str:
|
| 23 |
+
"""Hash a plain text password using bcrypt."""
|
| 24 |
+
# Bcrypt has a 72-byte limit. We'll truncate just in case, though
|
| 25 |
+
# passwords shouldn't be that long.
|
| 26 |
+
pwd_bytes = password.encode('utf-8')[:72]
|
| 27 |
+
salt = bcrypt.gensalt()
|
| 28 |
+
hashed = bcrypt.hashpw(pwd_bytes, salt)
|
| 29 |
+
return hashed.decode('utf-8')
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
|
| 32 |
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
| 33 |
+
"""Verify a plain text password against a hashed password."""
|
| 34 |
+
try:
|
| 35 |
+
return bcrypt.checkpw(
|
| 36 |
+
plain_password.encode('utf-8')[:72],
|
| 37 |
+
hashed_password.encode('utf-8')
|
| 38 |
+
)
|
| 39 |
+
except Exception:
|
| 40 |
+
return False
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
|
| 43 |
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
|
test_deployment.py
CHANGED
|
@@ -2,10 +2,14 @@
|
|
| 2 |
import requests
|
| 3 |
import json
|
| 4 |
import time
|
|
|
|
| 5 |
|
| 6 |
BASE_URL = "https://pranit144-finance.hf.space"
|
| 7 |
-
|
| 8 |
-
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
def test_endpoint(name, method, path, data=None, token=None):
|
| 11 |
url = f"{BASE_URL}{path}"
|
|
@@ -13,8 +17,7 @@ def test_endpoint(name, method, path, data=None, token=None):
|
|
| 13 |
if token:
|
| 14 |
headers["Authorization"] = f"Bearer {token}"
|
| 15 |
|
| 16 |
-
|
| 17 |
-
print(f" {method} {url}")
|
| 18 |
|
| 19 |
try:
|
| 20 |
if method == "GET":
|
|
@@ -22,55 +25,27 @@ def test_endpoint(name, method, path, data=None, token=None):
|
|
| 22 |
elif method == "POST":
|
| 23 |
response = requests.post(url, headers=headers, json=data, timeout=15)
|
| 24 |
|
| 25 |
-
|
| 26 |
try:
|
| 27 |
body = response.json()
|
| 28 |
-
|
| 29 |
return body
|
| 30 |
except:
|
| 31 |
-
|
| 32 |
return None
|
| 33 |
except Exception as e:
|
| 34 |
-
|
| 35 |
return None
|
| 36 |
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
# 1. Health
|
| 41 |
-
test_endpoint("Health Check", "GET", "/health")
|
| 42 |
-
|
| 43 |
-
# 2. Signup
|
| 44 |
-
timestamp = int(time.time())
|
| 45 |
-
test_email = f"test_user_{timestamp}@example.com"
|
| 46 |
-
signup_data = {
|
| 47 |
-
"email": test_email,
|
| 48 |
-
"password": "password123",
|
| 49 |
-
"name": "Test User",
|
| 50 |
-
"role": "STAFF"
|
| 51 |
-
}
|
| 52 |
-
signup_res = test_endpoint("Signup", "POST", "/auth/signup", data=signup_data)
|
| 53 |
-
|
| 54 |
-
# 3. Login
|
| 55 |
-
login_data = {
|
| 56 |
-
"email": test_email,
|
| 57 |
-
"password": "password123"
|
| 58 |
-
}
|
| 59 |
-
login_res = test_endpoint("Login", "POST", "/auth/login", data=login_data)
|
| 60 |
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
token = login_res["access_token"]
|
| 64 |
-
print(" β
Got Auth Token!")
|
| 65 |
-
else:
|
| 66 |
-
print(" β Failed to get auth token")
|
| 67 |
-
return
|
| 68 |
-
|
| 69 |
-
# 4. Protected: Get Popular Stocks
|
| 70 |
-
test_endpoint("Popular Stocks (Auth)", "GET", "/stocks/popular", token=token)
|
| 71 |
|
| 72 |
-
#
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
|
|
|
| 2 |
import requests
|
| 3 |
import json
|
| 4 |
import time
|
| 5 |
+
import sys
|
| 6 |
|
| 7 |
BASE_URL = "https://pranit144-finance.hf.space"
|
| 8 |
+
|
| 9 |
+
def log_test(msg):
|
| 10 |
+
with open("test_log.txt", "a", encoding="utf-8") as f:
|
| 11 |
+
f.write(msg + "\n")
|
| 12 |
+
print(msg)
|
| 13 |
|
| 14 |
def test_endpoint(name, method, path, data=None, token=None):
|
| 15 |
url = f"{BASE_URL}{path}"
|
|
|
|
| 17 |
if token:
|
| 18 |
headers["Authorization"] = f"Bearer {token}"
|
| 19 |
|
| 20 |
+
log_test(f"\nπ Testing {name}...")
|
|
|
|
| 21 |
|
| 22 |
try:
|
| 23 |
if method == "GET":
|
|
|
|
| 25 |
elif method == "POST":
|
| 26 |
response = requests.post(url, headers=headers, json=data, timeout=15)
|
| 27 |
|
| 28 |
+
log_test(f" Status: {response.status_code}")
|
| 29 |
try:
|
| 30 |
body = response.json()
|
| 31 |
+
log_test(f" Response: {json.dumps(body, indent=2)}")
|
| 32 |
return body
|
| 33 |
except:
|
| 34 |
+
log_test(f" Response: {response.text[:1000]}")
|
| 35 |
return None
|
| 36 |
except Exception as e:
|
| 37 |
+
log_test(f" β Error: {e}")
|
| 38 |
return None
|
| 39 |
|
| 40 |
+
if __name__ == "__main__":
|
| 41 |
+
with open("test_log.txt", "w", encoding="utf-8") as f:
|
| 42 |
+
f.write("=== LOG START ===\n")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
|
| 44 |
+
# Health
|
| 45 |
+
test_endpoint("Health", "GET", "/health")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
|
| 47 |
+
# Signup
|
| 48 |
+
ts = int(time.time())
|
| 49 |
+
email = f"user_{ts}@test.com"
|
| 50 |
+
signup_data = {"email": email, "password": "password123", "name": "Test User", "role": "STAFF"}
|
| 51 |
+
test_endpoint("Signup", "POST", "/auth/signup", data=signup_data)
|
test_log.txt
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
=== LOG START ===
|
| 2 |
+
|
| 3 |
+
π Testing Health...
|
| 4 |
+
Status: 200
|
| 5 |
+
Response: {
|
| 6 |
+
"status": "healthy",
|
| 7 |
+
"api": "operational",
|
| 8 |
+
"database": "connected (tables: portfolio, users)",
|
| 9 |
+
"sqlite_path": "sqlite:///./stock_analysis_prod.db"
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
π Testing Signup...
|
| 13 |
+
Status: 500
|
| 14 |
+
Response: {
|
| 15 |
+
"detail": "Database error: password cannot be longer than 72 bytes, truncate manually if necessary (e.g. my_password[:72]) | Trace: hecksum\n hash = _bcrypt.hashpw(secret, config)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nValueError: password cannot be longer than 72 bytes, truncate manually if necessary (e.g. my_password[:72])\n"
|
| 16 |
+
}
|