diff --git a/backend/app.py b/backend/app.py index b4ed2ad..71a4f75 100644 --- a/backend/app.py +++ b/backend/app.py @@ -9,6 +9,7 @@ from routes import account, messaging, profile, push, webrtc, devices, moderatio import logging from models import User from constants import OWNER_USERNAME +from utils import get_client_ip from db import POOL_CONFIG, SessionLocal from logging_config import access_logger # noqa: F401 - ensure loggers configured @@ -87,7 +88,7 @@ async def access_logging_middleware(request: Request, call_next): path=request.url.path, status="error", user=getattr(user, "username", None), - ip=request.client.host if request.client else None, + ip=get_client_ip(request), duration=f"{duration:.3f}s", error=str(exc), ) @@ -101,7 +102,7 @@ async def access_logging_middleware(request: Request, call_next): path=request.url.path, status=response.status_code, user=getattr(user, "username", None), - ip=request.headers.get("x-forwarded-for") or (request.client.host if request.client else None), + ip=get_client_ip(request), duration=f"{duration:.3f}s", ) return response diff --git a/backend/routes/account.py b/backend/routes/account.py index 7784c0f..d9b482b 100644 --- a/backend/routes/account.py +++ b/backend/routes/account.py @@ -11,7 +11,7 @@ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from constants import OWNER_USERNAME from dependencies import get_current_user, get_db from models import LoginRequest, RegisterRequest, ChangePasswordRequest, User, CryptoPublicKey, CryptoBackup, DeviceSession -from utils import create_token, get_password_hash, verify_password +from utils import create_token, get_password_hash, verify_password, get_client_ip from validation import is_valid_password, is_valid_username, is_valid_display_name import os @@ -67,8 +67,7 @@ def check_auth(current_user: User = Depends(get_current_user)): @router.post("/login") def login(request: LoginRequest, http: Request, db: Session = Depends(get_db)): username = request.username.strip() - x_forwarded_for = http.headers.get("x-forwarded-for") if http else None - client_ip = x_forwarded_for.split(",")[0].strip() if x_forwarded_for else (http.client.host if http and http.client else None) + client_ip = get_client_ip(http) user = db.query(User).filter(User.username == username).first() @@ -168,7 +167,7 @@ def register(request: RegisterRequest, http: Request, db: Session = Depends(get_ display_name = request.display_name.strip() password = request.password.strip() confirm_password = request.confirm_password.strip() - client_ip = http.client.host if http.client else None + client_ip = get_client_ip(http) # Determine if owner already exists owner_exists = db.query(User).filter(User.username == OWNER_USERNAME).first() is not None @@ -396,7 +395,7 @@ def logout( current_user.last_seen = datetime.now() db.commit() - client_ip = http.client.host if http.client else None + client_ip = get_client_ip(http) log_security( "logout", username=current_user.username, @@ -440,7 +439,7 @@ def change_password( ).update({DeviceSession.revoked: True}) db.commit() - client_ip = http.client.host if http.client else None + client_ip = get_client_ip(http) log_security( "password_changed", username=current_user.username, diff --git a/backend/utils.py b/backend/utils.py index 09db130..3b6da7e 100644 --- a/backend/utils.py +++ b/backend/utils.py @@ -1,6 +1,7 @@ from datetime import datetime, timedelta +from fastapi import Request import jwt -from typing import Optional +from typing import Optional, Any import bcrypt from constants import * @@ -31,4 +32,26 @@ def verify_password(plain_password: str, hashed_password: str) -> bool: return bcrypt.checkpw(plain_password.encode("utf-8"), hashed_password.encode("utf-8")) def get_password_hash(password: str) -> str: - return bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode("utf-8") \ No newline at end of file + return bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode("utf-8") + + +def get_client_ip(request: Request) -> Optional[str]: + if not request: + return None + + headers = request.headers + forwarded = headers.get("x-forwarded-for") or headers.get("X-Forwarded-For") + if forwarded: + candidate = forwarded.split(",")[0].strip() + if candidate: + return candidate + + if request.client and request.client.host: + return request.client.host + + if isinstance(request.scope, dict): + client_info = request.scope.get("client") + if isinstance(client_info, (list, tuple)) and client_info: + return client_info[0] + + return None \ No newline at end of file