Skip to content

Commit 1dcb36f

Browse files
authored
Merge branch 'dev-v1' into dev-1
2 parents ffcd98f + ae597d6 commit 1dcb36f

File tree

10 files changed

+1072
-838
lines changed

10 files changed

+1072
-838
lines changed

backend/auth.py

Lines changed: 33 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -5,103 +5,76 @@
55
from dotenv import load_dotenv
66
import traceback
77

8-
load_dotenv()
8+
# Load environment variables
9+
def get_database_url():
10+
env_path = os.getenv("ENV_PATH")
11+
if env_path:
12+
print(f"[auth.py] Loading environment from: {env_path}")
13+
load_dotenv(env_path)
14+
return os.getenv("DATABASE_URL", "").replace("+asyncpg", "").replace("@db", "@localhost")
15+
else:
16+
print("[auth.py] Loading from default .env file")
17+
load_dotenv(".env")
18+
return os.getenv("DATABASE_URL", "").replace("+asyncpg", "")
19+
20+
DATABASE_URL = get_database_url()
21+
22+
# Ensure required environment variables are present
23+
if not os.getenv("JWT_SECRET"):
24+
raise ValueError("JWT_SECRET environment variable is not set")
925

1026
SECRET_KEY = os.getenv("JWT_SECRET")
1127
ALGORITHM = "HS256"
12-
ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24
28+
ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 * 30 # 30 days
1329

14-
def create_access_token(data: dict):
30+
def create_jwt_token(data: dict):
31+
"""Create a new JWT token with expiration"""
1532
to_encode = data.copy()
1633
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
1734
to_encode.update({"exp": expire})
1835
jwt_token = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
1936
print(f"[auth.py] Created JWT token: {jwt_token[:20]}...")
2037
return jwt_token
2138

39+
def create_access_token(data: dict):
40+
"""Alias for create_jwt_token for backward compatibility"""
41+
return create_jwt_token(data)
42+
2243
def verify_token(token: str):
2344
"""Verify and decode a JWT token"""
24-
print(f"[auth.py] Verifying token: {token[:20]}...")
45+
2546
try:
2647
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
27-
print(f"[auth.py] Token decoded successfully: {payload}")
28-
2948
if "exp" not in payload:
30-
print("[auth.py] Token missing 'exp' field")
3149
return None
32-
33-
# Check if token has expired
34-
expiration_timestamp = payload["exp"]
35-
try:
36-
# Print both values for comparison
37-
current_timestamp = datetime.utcnow().timestamp()
38-
print(f"[auth.py] Current time: {current_timestamp}, Token expiration: {expiration_timestamp}")
39-
40-
# Check expiration
41-
if current_timestamp > expiration_timestamp:
42-
print("[auth.py] Token has expired")
43-
return None
44-
45-
except Exception as e:
46-
print(f"[auth.py] Error comparing timestamps: {str(e)}")
50+
if datetime.utcnow() > datetime.fromtimestamp(payload["exp"]):
4751
return None
48-
4952
return payload
5053
except JWTError as e:
51-
print(f"[auth.py] JWT verification error: {str(e)}")
54+
print(f"JWT verification error: {str(e)}")
5255
return None
5356
except Exception as e:
54-
print(f"[auth.py] Token verification error: {str(e)}")
55-
print(traceback.format_exc())
57+
print(f"Token verification error: {str(e)}")
58+
5659
return None
5760

5861
async def get_current_user(authorization: str = Header(...)):
5962
"""Get current user from authorization header"""
6063
try:
61-
print(f"[auth.py] Authorization header: {authorization[:30]}...")
62-
63-
if not authorization or not authorization.startswith("Bearer "):
64-
print(f"[auth.py] Invalid authorization header format: {authorization[:15]}...")
65-
raise HTTPException(
66-
status_code=401,
67-
detail="Invalid authorization header format"
68-
)
69-
64+
7065
token = authorization.replace("Bearer ", "")
71-
print(f"[auth.py] Processing token: {token[:20]}... (length: {len(token)})")
72-
73-
if not token:
74-
print("[auth.py] Empty token after Bearer prefix")
75-
raise HTTPException(
76-
status_code=401,
77-
detail="Empty token"
78-
)
79-
8066
payload = verify_token(token)
8167
if not payload:
82-
print(f"[auth.py] Token verification failed for token: {token[:20]}...")
68+
8369
raise HTTPException(
8470
status_code=401,
8571
detail="Invalid or expired token"
8672
)
87-
88-
# Verify essential fields
89-
if "github_id" not in payload:
90-
print(f"[auth.py] Missing required field 'github_id' in token payload: {payload}")
91-
raise HTTPException(
92-
status_code=401,
93-
detail="Token missing required fields"
94-
)
95-
96-
print(f"[auth.py] Authenticated user with GitHub ID: {payload.get('github_id')}")
73+
9774
return payload
98-
except HTTPException:
99-
# Re-raise HTTP exceptions
100-
raise
10175
except Exception as e:
102-
print(f"[auth.py] Authentication error: {str(e)}")
103-
print(traceback.format_exc())
10476
raise HTTPException(
10577
status_code=401,
10678
detail=f"Authorization failed: {str(e)}"
10779
)
80+

backend/database.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,18 @@
1010
import os
1111
import sys
1212

13-
load_dotenv()
13+
# env load
14+
env_path = os.getenv("ENV_PATH")
15+
if env_path:
16+
print(f"[database.py] Loading custom ENV_PATH: {env_path}")
17+
load_dotenv(env_path)
18+
else:
19+
print("[database.py] Loading default .env/.env.railway")
20+
load_dotenv(".env") # Railway default
1421

1522
DATABASE_URL = os.getenv("DATABASE_URL")
1623
print(f"[database.py] Loaded DATABASE_URL: {DATABASE_URL}", flush=True)
1724

18-
# try:
19-
# engine = create_async_engine(DATABASE_URL, future=True, echo=True)
20-
# except Exception as e:
21-
# print(f"[database.py] Failed to create engine: {e}", flush=True)
22-
# raise
23-
24-
# SessionLocal = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)
25-
# Base = declarative_base()
26-
2725
if "alembic" in sys.argv[0]:
2826
engine = create_engine(DATABASE_URL.replace("+asyncpg", ""), future=True, echo=True)
2927
SessionLocal = sessionmaker(bind=engine)

backend/github_oauth.py

Lines changed: 105 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -14,55 +14,130 @@
1414
logger.error(f"GITHUB_CLIENT_SECRET: {'present' if GITHUB_CLIENT_SECRET else 'missing'}")
1515
raise ValueError("❌ GitHub OAuth credentials not found")
1616

17+
print(f"🔑 GitHub OAuth Configuration: Client ID: {GITHUB_CLIENT_ID[:5]}...")
18+
1719
async def exchange_code_for_token(code: str):
18-
if not code:
19-
raise HTTPException(status_code=400, detail="GitHub code is required")
20-
20+
21+
"""Exchange GitHub OAuth code for token data"""
2122
try:
2223
async with httpx.AsyncClient() as client:
23-
logger.info(f"Exchanging code for token with GitHub")
24+
print(f"🚀 Exchanging code for token with GitHub...")
25+
print(f"Using code: {code[:10]}...")
26+
27+
# Log credentials being used (partially obscured for security)
28+
print(f"🔑 Using Client ID: {GITHUB_CLIENT_ID[:5]}...")
29+
print(f"🔑 Using Client Secret: {GITHUB_CLIENT_SECRET[:5]}...")
30+
2431
response = await client.post(
2532
"https://github.com/login/oauth/access_token",
26-
headers={"Accept": "application/json"},
33+
headers={
34+
"Accept": "application/json"
35+
},
36+
2737
data={
2838
"client_id": GITHUB_CLIENT_ID,
2939
"client_secret": GITHUB_CLIENT_SECRET,
3040
"code": code
3141
}
3242
)
33-
response.raise_for_status()
34-
data = response.json()
35-
logger.info("Successfully exchanged code for token")
36-
return data
37-
except httpx.HTTPError as e:
38-
logger.error(f"HTTP error during token exchange: {str(e)}")
39-
logger.error(f"Response status: {e.response.status_code if hasattr(e, 'response') else 'unknown'}")
40-
logger.error(f"Response body: {e.response.text if hasattr(e, 'response') else 'unknown'}")
41-
raise HTTPException(status_code=500, detail=f"GitHub API error: {str(e)}")
43+
44+
print(f"✅ GitHub token exchange status: {response.status_code}")
45+
print(f"Response headers: {dict(response.headers)}")
46+
47+
try:
48+
response_text = response.text
49+
print(f"Raw response text: {response_text}")
50+
data = response.json()
51+
print(f"Parsed response data keys: {list(data.keys())}")
52+
except Exception as e:
53+
print(f"❌ Failed to parse JSON response: {response_text}")
54+
raise Exception(f"Failed to parse GitHub response: {str(e)}")
55+
56+
if "error" in data:
57+
error_description = data.get("error_description", "No description provided")
58+
print(f"❌ GitHub OAuth error: {data['error']} - {error_description}")
59+
raise Exception(f"GitHub OAuth error: {data['error']} - {error_description}")
60+
61+
if "access_token" not in data:
62+
print("❌ Access token is missing from response")
63+
print(f"Available fields in response: {list(data.keys())}")
64+
raise Exception("No access token in GitHub response")
65+
66+
# Ensure token is properly stored and formatted
67+
access_token = data.get("access_token", "").strip()
68+
if not access_token:
69+
print("❌ Access token is empty")
70+
raise Exception("Empty access token received from GitHub")
71+
72+
# Create a new clean data object to avoid any issues
73+
token_data = {
74+
"access_token": access_token,
75+
"token_type": data.get("token_type", "bearer"),
76+
"scope": data.get("scope", "")
77+
}
78+
79+
print(f"🔑 Successfully obtained token data with fields: {list(token_data.keys())}")
80+
print(f"🔑 Access token value: {access_token[:10]}...")
81+
82+
# Directly log the full token JUST FOR DEBUGGING (would remove in production)
83+
print(f"🔑 FULL TOKEN FOR DEBUG: {access_token}")
84+
85+
# Verify token data is correctly formed
86+
if not token_data.get("access_token"):
87+
print("⚠️ WARNING: access_token field is empty or missing in final token_data")
88+
# Try once more to ensure it's set
89+
token_data["access_token"] = access_token
90+
91+
return token_data
92+
4293
except Exception as e:
43-
logger.exception("Error exchanging code for token:")
44-
raise HTTPException(status_code=500, detail=str(e))
94+
print(f"🔥 Error in token exchange: {str(e)}")
95+
raise
4596

4697
async def get_user_info(access_token: str):
47-
if not access_token:
48-
raise HTTPException(status_code=400, detail="Access token is required")
49-
98+
"""Get GitHub user information using access token"""
5099
try:
51100
async with httpx.AsyncClient() as client:
52-
logger.info("Fetching user info from GitHub")
101+
print(f"👤 Fetching user info from GitHub...")
102+
print(f"👤 Using token: {access_token[:10]}...")
103+
104+
# Try both authentication methods
105+
headers = {
106+
"Authorization": f"token {access_token}", # GitHub preferred format
107+
"Accept": "application/vnd.github.v3+json",
108+
"User-Agent": "LIT1337-App"
109+
}
110+
53111
response = await client.get(
54112
"https://api.github.com/user",
55-
headers={"Authorization": f"Bearer {access_token}"}
113+
headers=headers
56114
)
57-
response.raise_for_status()
115+
116+
if response.status_code != 200:
117+
# Try alternative Bearer format if token format fails
118+
print(f"First attempt failed with status {response.status_code}, trying Bearer format")
119+
alt_headers = {
120+
"Authorization": f"Bearer {access_token}", # OAuth standard
121+
"Accept": "application/vnd.github.v3+json",
122+
"User-Agent": "LIT1337-App"
123+
}
124+
125+
response = await client.get(
126+
"https://api.github.com/user",
127+
headers=alt_headers
128+
)
129+
130+
print(f"GitHub API response status: {response.status_code}")
131+
132+
if response.status_code != 200:
133+
error_data = response.json()
134+
print(f"❌ GitHub API error response: {error_data}")
135+
raise Exception(f"GitHub API error: {response.status_code} - {error_data.get('message', 'Unknown error')}")
136+
58137
data = response.json()
59-
logger.info("Successfully fetched user info")
138+
print(f"✅ GitHub user info received for: {data.get('login')}")
60139
return data
61-
except httpx.HTTPError as e:
62-
logger.error(f"HTTP error fetching user info: {str(e)}")
63-
logger.error(f"Response status: {e.response.status_code if hasattr(e, 'response') else 'unknown'}")
64-
logger.error(f"Response body: {e.response.text if hasattr(e, 'response') else 'unknown'}")
65-
raise HTTPException(status_code=500, detail=f"GitHub API error: {str(e)}")
140+
66141
except Exception as e:
67-
logger.exception("Error fetching user info:")
68-
raise HTTPException(status_code=500, detail=str(e))
142+
print(f"❌ Error getting user info: {str(e)}")
143+
raise

0 commit comments

Comments
 (0)