Spaces:
Sleeping
Sleeping
Commit ·
df8187a
1
Parent(s): 90064f2
refactor(logging): change debug to info level for operational logs
Browse filesStandardize logging level to INFO across application components to ensure important operational logs are captured in production. This provides better visibility into system behavior while reducing noise from debug logs.
- app/app.py +4 -3
- app/middleware/security_middleware.py +1 -2
- app/models/favorite_model.py +1 -1
- app/models/otp_model.py +12 -12
- app/models/social_security_model.py +3 -3
- app/models/user_model.py +11 -11
- app/routers/user_router.py +15 -15
- app/services/user_service.py +17 -17
app/app.py
CHANGED
|
@@ -8,14 +8,15 @@ from app.middleware.security_middleware import SecurityMiddleware
|
|
| 8 |
import logging
|
| 9 |
import sys
|
| 10 |
|
| 11 |
-
# Configure logging
|
| 12 |
logging.basicConfig(
|
| 13 |
-
level=logging.
|
| 14 |
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
| 15 |
handlers=[
|
| 16 |
logging.StreamHandler(sys.stdout),
|
| 17 |
logging.FileHandler('app.log')
|
| 18 |
-
]
|
|
|
|
| 19 |
)
|
| 20 |
|
| 21 |
# Set specific logger levels
|
|
|
|
| 8 |
import logging
|
| 9 |
import sys
|
| 10 |
|
| 11 |
+
# Configure logging (force ensures this overrides any prior configs)
|
| 12 |
logging.basicConfig(
|
| 13 |
+
level=logging.INFO,
|
| 14 |
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
| 15 |
handlers=[
|
| 16 |
logging.StreamHandler(sys.stdout),
|
| 17 |
logging.FileHandler('app.log')
|
| 18 |
+
],
|
| 19 |
+
force=True,
|
| 20 |
)
|
| 21 |
|
| 22 |
# Set specific logger levels
|
app/middleware/security_middleware.py
CHANGED
|
@@ -6,8 +6,7 @@ import logging
|
|
| 6 |
from typing import Dict, Any
|
| 7 |
from app.core.nosql_client import db
|
| 8 |
|
| 9 |
-
#
|
| 10 |
-
logging.basicConfig(level=logging.INFO)
|
| 11 |
logger = logging.getLogger(__name__)
|
| 12 |
|
| 13 |
class SecurityMiddleware(BaseHTTPMiddleware):
|
|
|
|
| 6 |
from typing import Dict, Any
|
| 7 |
from app.core.nosql_client import db
|
| 8 |
|
| 9 |
+
# Use app-level logging configuration
|
|
|
|
| 10 |
logger = logging.getLogger(__name__)
|
| 11 |
|
| 12 |
class SecurityMiddleware(BaseHTTPMiddleware):
|
app/models/favorite_model.py
CHANGED
|
@@ -165,7 +165,7 @@ class BookMyServiceFavoriteModel:
|
|
| 165 |
@staticmethod
|
| 166 |
async def is_favorite(customer_id: str, merchant_id: str) -> bool:
|
| 167 |
"""Check if a merchant is in user's favorites"""
|
| 168 |
-
logger.
|
| 169 |
|
| 170 |
try:
|
| 171 |
favorite = await BookMyServiceFavoriteModel.collection.find_one({
|
|
|
|
| 165 |
@staticmethod
|
| 166 |
async def is_favorite(customer_id: str, merchant_id: str) -> bool:
|
| 167 |
"""Check if a merchant is in user's favorites"""
|
| 168 |
+
logger.info(f"Checking if merchant {merchant_id} is favorite for customer {customer_id}")
|
| 169 |
|
| 170 |
try:
|
| 171 |
favorite = await BookMyServiceFavoriteModel.collection.find_one({
|
app/models/otp_model.py
CHANGED
|
@@ -23,7 +23,7 @@ class BookMyServiceOTPModel:
|
|
| 23 |
|
| 24 |
try:
|
| 25 |
redis = await get_redis()
|
| 26 |
-
logger.
|
| 27 |
|
| 28 |
# Check if account is locked
|
| 29 |
if await BookMyServiceOTPModel.is_account_locked(identifier):
|
|
@@ -32,14 +32,14 @@ class BookMyServiceOTPModel:
|
|
| 32 |
|
| 33 |
# Rate limit: max 3 OTPs per identifier per 10 minutes
|
| 34 |
rate_key = f"otp_rate_limit:{identifier}"
|
| 35 |
-
logger.
|
| 36 |
|
| 37 |
attempts = await redis.incr(rate_key)
|
| 38 |
-
logger.
|
| 39 |
|
| 40 |
if attempts == 1:
|
| 41 |
await redis.expire(rate_key, BookMyServiceOTPModel.RATE_LIMIT_WINDOW)
|
| 42 |
-
logger.
|
| 43 |
elif attempts > BookMyServiceOTPModel.RATE_LIMIT_MAX:
|
| 44 |
logger.warning(f"Rate limit exceeded for {identifier}: {attempts} attempts")
|
| 45 |
raise HTTPException(status_code=429, detail="Too many OTP requests. Try again later.")
|
|
@@ -85,11 +85,11 @@ class BookMyServiceOTPModel:
|
|
| 85 |
@staticmethod
|
| 86 |
async def verify_otp(identifier: str, otp: str, client_ip: str = None):
|
| 87 |
logger.info(f"Verifying OTP for identifier: {identifier}, IP: {client_ip}")
|
| 88 |
-
logger.
|
| 89 |
|
| 90 |
try:
|
| 91 |
redis = await get_redis()
|
| 92 |
-
logger.
|
| 93 |
|
| 94 |
# Check if account is locked
|
| 95 |
if await BookMyServiceOTPModel.is_account_locked(identifier):
|
|
@@ -97,19 +97,19 @@ class BookMyServiceOTPModel:
|
|
| 97 |
raise HTTPException(status_code=423, detail="Account temporarily locked due to too many failed attempts")
|
| 98 |
|
| 99 |
key = f"bms_otp:{identifier}"
|
| 100 |
-
logger.
|
| 101 |
|
| 102 |
stored = await redis.get(key)
|
| 103 |
-
logger.
|
| 104 |
|
| 105 |
if stored:
|
| 106 |
-
logger.
|
| 107 |
if stored == otp:
|
| 108 |
logger.info(f"OTP verification successful for {identifier}")
|
| 109 |
await redis.delete(key)
|
| 110 |
# Clear failed attempts on successful verification
|
| 111 |
await BookMyServiceOTPModel.clear_failed_attempts(identifier)
|
| 112 |
-
logger.
|
| 113 |
return True
|
| 114 |
else:
|
| 115 |
logger.warning(f"OTP mismatch for {identifier}: provided='{otp}' vs stored='{stored}'")
|
|
@@ -166,7 +166,7 @@ class BookMyServiceOTPModel:
|
|
| 166 |
if ip_attempts == 1:
|
| 167 |
await redis.expire(ip_failed_key, BookMyServiceOTPModel.FAILED_ATTEMPTS_WINDOW)
|
| 168 |
|
| 169 |
-
logger.
|
| 170 |
|
| 171 |
except Exception as e:
|
| 172 |
logger.error(f"Error tracking failed attempt for {identifier}: {str(e)}", exc_info=True)
|
|
@@ -178,7 +178,7 @@ class BookMyServiceOTPModel:
|
|
| 178 |
redis = await get_redis()
|
| 179 |
failed_key = f"failed_otp:{identifier}"
|
| 180 |
await redis.delete(failed_key)
|
| 181 |
-
logger.
|
| 182 |
except Exception as e:
|
| 183 |
logger.error(f"Error clearing failed attempts for {identifier}: {str(e)}", exc_info=True)
|
| 184 |
|
|
|
|
| 23 |
|
| 24 |
try:
|
| 25 |
redis = await get_redis()
|
| 26 |
+
logger.info(f"Redis connection established for OTP storage")
|
| 27 |
|
| 28 |
# Check if account is locked
|
| 29 |
if await BookMyServiceOTPModel.is_account_locked(identifier):
|
|
|
|
| 32 |
|
| 33 |
# Rate limit: max 3 OTPs per identifier per 10 minutes
|
| 34 |
rate_key = f"otp_rate_limit:{identifier}"
|
| 35 |
+
logger.info(f"Checking rate limit with key: {rate_key}")
|
| 36 |
|
| 37 |
attempts = await redis.incr(rate_key)
|
| 38 |
+
logger.info(f"Current OTP attempts for {identifier}: {attempts}")
|
| 39 |
|
| 40 |
if attempts == 1:
|
| 41 |
await redis.expire(rate_key, BookMyServiceOTPModel.RATE_LIMIT_WINDOW)
|
| 42 |
+
logger.info(f"Set rate limit expiry for {identifier}")
|
| 43 |
elif attempts > BookMyServiceOTPModel.RATE_LIMIT_MAX:
|
| 44 |
logger.warning(f"Rate limit exceeded for {identifier}: {attempts} attempts")
|
| 45 |
raise HTTPException(status_code=429, detail="Too many OTP requests. Try again later.")
|
|
|
|
| 85 |
@staticmethod
|
| 86 |
async def verify_otp(identifier: str, otp: str, client_ip: str = None):
|
| 87 |
logger.info(f"Verifying OTP for identifier: {identifier}, IP: {client_ip}")
|
| 88 |
+
logger.info(f"Provided OTP: {otp}")
|
| 89 |
|
| 90 |
try:
|
| 91 |
redis = await get_redis()
|
| 92 |
+
logger.info("Redis connection established for OTP verification")
|
| 93 |
|
| 94 |
# Check if account is locked
|
| 95 |
if await BookMyServiceOTPModel.is_account_locked(identifier):
|
|
|
|
| 97 |
raise HTTPException(status_code=423, detail="Account temporarily locked due to too many failed attempts")
|
| 98 |
|
| 99 |
key = f"bms_otp:{identifier}"
|
| 100 |
+
logger.info(f"Looking up OTP with key: {key}")
|
| 101 |
|
| 102 |
stored = await redis.get(key)
|
| 103 |
+
logger.info(f"Stored OTP value: {stored}")
|
| 104 |
|
| 105 |
if stored:
|
| 106 |
+
logger.info(f"OTP found in Redis. Comparing: provided='{otp}' vs stored='{stored}'")
|
| 107 |
if stored == otp:
|
| 108 |
logger.info(f"OTP verification successful for {identifier}")
|
| 109 |
await redis.delete(key)
|
| 110 |
# Clear failed attempts on successful verification
|
| 111 |
await BookMyServiceOTPModel.clear_failed_attempts(identifier)
|
| 112 |
+
logger.info(f"OTP deleted from Redis after successful verification")
|
| 113 |
return True
|
| 114 |
else:
|
| 115 |
logger.warning(f"OTP mismatch for {identifier}: provided='{otp}' vs stored='{stored}'")
|
|
|
|
| 166 |
if ip_attempts == 1:
|
| 167 |
await redis.expire(ip_failed_key, BookMyServiceOTPModel.FAILED_ATTEMPTS_WINDOW)
|
| 168 |
|
| 169 |
+
logger.info(f"IP {client_ip} failed attempts: {ip_attempts}")
|
| 170 |
|
| 171 |
except Exception as e:
|
| 172 |
logger.error(f"Error tracking failed attempt for {identifier}: {str(e)}", exc_info=True)
|
|
|
|
| 178 |
redis = await get_redis()
|
| 179 |
failed_key = f"failed_otp:{identifier}"
|
| 180 |
await redis.delete(failed_key)
|
| 181 |
+
logger.info(f"Cleared failed attempts for {identifier}")
|
| 182 |
except Exception as e:
|
| 183 |
logger.error(f"Error clearing failed attempts for {identifier}: {str(e)}", exc_info=True)
|
| 184 |
|
app/models/social_security_model.py
CHANGED
|
@@ -52,7 +52,7 @@ class SocialSecurityModel:
|
|
| 52 |
if count == 1:
|
| 53 |
await redis.expire(rate_key, SocialSecurityModel.OAUTH_RATE_LIMIT_WINDOW)
|
| 54 |
|
| 55 |
-
logger.
|
| 56 |
|
| 57 |
except Exception as e:
|
| 58 |
logger.error(f"Error incrementing OAuth rate limit: {str(e)}", exc_info=True)
|
|
@@ -76,7 +76,7 @@ class SocialSecurityModel:
|
|
| 76 |
await SocialSecurityModel.lock_oauth_ip(client_ip, provider)
|
| 77 |
logger.warning(f"IP {client_ip} locked for provider {provider} after {attempts} failed attempts")
|
| 78 |
|
| 79 |
-
logger.
|
| 80 |
|
| 81 |
except Exception as e:
|
| 82 |
logger.error(f"Error tracking OAuth failed attempt: {str(e)}", exc_info=True)
|
|
@@ -117,7 +117,7 @@ class SocialSecurityModel:
|
|
| 117 |
redis = await get_redis()
|
| 118 |
failed_key = f"oauth_failed:{client_ip}:{provider}"
|
| 119 |
await redis.delete(failed_key)
|
| 120 |
-
logger.
|
| 121 |
except Exception as e:
|
| 122 |
logger.error(f"Error clearing OAuth failed attempts: {str(e)}", exc_info=True)
|
| 123 |
|
|
|
|
| 52 |
if count == 1:
|
| 53 |
await redis.expire(rate_key, SocialSecurityModel.OAUTH_RATE_LIMIT_WINDOW)
|
| 54 |
|
| 55 |
+
logger.info(f"OAuth rate limit count for {client_ip}:{provider} = {count}")
|
| 56 |
|
| 57 |
except Exception as e:
|
| 58 |
logger.error(f"Error incrementing OAuth rate limit: {str(e)}", exc_info=True)
|
|
|
|
| 76 |
await SocialSecurityModel.lock_oauth_ip(client_ip, provider)
|
| 77 |
logger.warning(f"IP {client_ip} locked for provider {provider} after {attempts} failed attempts")
|
| 78 |
|
| 79 |
+
logger.info(f"OAuth failed attempts for {client_ip}:{provider} = {attempts}")
|
| 80 |
|
| 81 |
except Exception as e:
|
| 82 |
logger.error(f"Error tracking OAuth failed attempt: {str(e)}", exc_info=True)
|
|
|
|
| 117 |
redis = await get_redis()
|
| 118 |
failed_key = f"oauth_failed:{client_ip}:{provider}"
|
| 119 |
await redis.delete(failed_key)
|
| 120 |
+
logger.info(f"Cleared OAuth failed attempts for {client_ip}:{provider}")
|
| 121 |
except Exception as e:
|
| 122 |
logger.error(f"Error clearing OAuth failed attempts: {str(e)}", exc_info=True)
|
| 123 |
|
app/models/user_model.py
CHANGED
|
@@ -11,13 +11,13 @@ class BookMyServiceUserModel:
|
|
| 11 |
|
| 12 |
@staticmethod
|
| 13 |
async def find_by_email(email: str):
|
| 14 |
-
logger.
|
| 15 |
try:
|
| 16 |
user = await BookMyServiceUserModel.collection.find_one({"email": email})
|
| 17 |
if user:
|
| 18 |
-
logger.
|
| 19 |
else:
|
| 20 |
-
logger.
|
| 21 |
return user
|
| 22 |
except Exception as e:
|
| 23 |
logger.error(f"Error finding user by email {email}: {str(e)}", exc_info=True)
|
|
@@ -25,13 +25,13 @@ class BookMyServiceUserModel:
|
|
| 25 |
|
| 26 |
@staticmethod
|
| 27 |
async def find_by_phone(phone: str):
|
| 28 |
-
logger.
|
| 29 |
try:
|
| 30 |
user = await BookMyServiceUserModel.collection.find_one({"phone": phone})
|
| 31 |
if user:
|
| 32 |
-
logger.
|
| 33 |
else:
|
| 34 |
-
logger.
|
| 35 |
return user
|
| 36 |
except Exception as e:
|
| 37 |
logger.error(f"Error finding user by phone {phone}: {str(e)}", exc_info=True)
|
|
@@ -40,7 +40,7 @@ class BookMyServiceUserModel:
|
|
| 40 |
@staticmethod
|
| 41 |
async def find_by_mobile(mobile: str):
|
| 42 |
"""Legacy method for backward compatibility - redirects to find_by_phone"""
|
| 43 |
-
logger.
|
| 44 |
return await BookMyServiceUserModel.find_by_phone(mobile)
|
| 45 |
|
| 46 |
@staticmethod
|
|
@@ -50,13 +50,13 @@ class BookMyServiceUserModel:
|
|
| 50 |
try:
|
| 51 |
# Validate and determine identifier type
|
| 52 |
identifier_type = validate_identifier(identifier)
|
| 53 |
-
logger.
|
| 54 |
|
| 55 |
if identifier_type == "email":
|
| 56 |
-
logger.
|
| 57 |
user = await BookMyServiceUserModel.find_by_email(identifier)
|
| 58 |
elif identifier_type == "phone":
|
| 59 |
-
logger.
|
| 60 |
user = await BookMyServiceUserModel.find_by_phone(identifier)
|
| 61 |
else:
|
| 62 |
logger.error(f"Invalid identifier type: {identifier_type}")
|
|
@@ -67,7 +67,7 @@ class BookMyServiceUserModel:
|
|
| 67 |
raise HTTPException(status_code=404, detail="User not found with this email or phone")
|
| 68 |
|
| 69 |
logger.info(f"User found successfully for identifier: {identifier}")
|
| 70 |
-
logger.
|
| 71 |
return user
|
| 72 |
|
| 73 |
except ValueError as ve:
|
|
|
|
| 11 |
|
| 12 |
@staticmethod
|
| 13 |
async def find_by_email(email: str):
|
| 14 |
+
logger.info(f"Searching for user by email: {email}")
|
| 15 |
try:
|
| 16 |
user = await BookMyServiceUserModel.collection.find_one({"email": email})
|
| 17 |
if user:
|
| 18 |
+
logger.info(f"User found by email: {email}")
|
| 19 |
else:
|
| 20 |
+
logger.info(f"No user found with email: {email}")
|
| 21 |
return user
|
| 22 |
except Exception as e:
|
| 23 |
logger.error(f"Error finding user by email {email}: {str(e)}", exc_info=True)
|
|
|
|
| 25 |
|
| 26 |
@staticmethod
|
| 27 |
async def find_by_phone(phone: str):
|
| 28 |
+
logger.info(f"Searching for user by phone: {phone}")
|
| 29 |
try:
|
| 30 |
user = await BookMyServiceUserModel.collection.find_one({"phone": phone})
|
| 31 |
if user:
|
| 32 |
+
logger.info(f"User found by phone: {phone}")
|
| 33 |
else:
|
| 34 |
+
logger.info(f"No user found with phone: {phone}")
|
| 35 |
return user
|
| 36 |
except Exception as e:
|
| 37 |
logger.error(f"Error finding user by phone {phone}: {str(e)}", exc_info=True)
|
|
|
|
| 40 |
@staticmethod
|
| 41 |
async def find_by_mobile(mobile: str):
|
| 42 |
"""Legacy method for backward compatibility - redirects to find_by_phone"""
|
| 43 |
+
logger.info(f"Legacy find_by_mobile called, redirecting to find_by_phone for: {mobile}")
|
| 44 |
return await BookMyServiceUserModel.find_by_phone(mobile)
|
| 45 |
|
| 46 |
@staticmethod
|
|
|
|
| 50 |
try:
|
| 51 |
# Validate and determine identifier type
|
| 52 |
identifier_type = validate_identifier(identifier)
|
| 53 |
+
logger.info(f"Identifier type determined: {identifier_type}")
|
| 54 |
|
| 55 |
if identifier_type == "email":
|
| 56 |
+
logger.info(f"Searching by email for identifier: {identifier}")
|
| 57 |
user = await BookMyServiceUserModel.find_by_email(identifier)
|
| 58 |
elif identifier_type == "phone":
|
| 59 |
+
logger.info(f"Searching by phone for identifier: {identifier}")
|
| 60 |
user = await BookMyServiceUserModel.find_by_phone(identifier)
|
| 61 |
else:
|
| 62 |
logger.error(f"Invalid identifier type: {identifier_type}")
|
|
|
|
| 67 |
raise HTTPException(status_code=404, detail="User not found with this email or phone")
|
| 68 |
|
| 69 |
logger.info(f"User found successfully for identifier: {identifier}")
|
| 70 |
+
logger.info(f"User data keys: {list(user.keys()) if user else 'None'}")
|
| 71 |
return user
|
| 72 |
|
| 73 |
except ValueError as ve:
|
app/routers/user_router.py
CHANGED
|
@@ -40,7 +40,7 @@ def get_bearer_token(api_key: str = Security(api_key_scheme)) -> str:
|
|
| 40 |
)
|
| 41 |
|
| 42 |
# If "Bearer " prefix is included, strip it
|
| 43 |
-
logger.
|
| 44 |
if api_key.lower().startswith("bearer "):
|
| 45 |
return api_key[7:] # Remove "Bearer " prefix
|
| 46 |
|
|
@@ -67,7 +67,7 @@ async def send_otp_login_handler(payload: OTPRequestWithLogin):
|
|
| 67 |
# Validate identifier format
|
| 68 |
try:
|
| 69 |
identifier_type = validate_identifier(payload.login_input)
|
| 70 |
-
logger.
|
| 71 |
except ValueError as ve:
|
| 72 |
logger.error(f"Invalid login input format: {str(ve)}")
|
| 73 |
raise HTTPException(status_code=400, detail=str(ve))
|
|
@@ -79,22 +79,22 @@ async def send_otp_login_handler(payload: OTPRequestWithLogin):
|
|
| 79 |
elif identifier_type == "phone":
|
| 80 |
user_exists = await BookMyServiceUserModel.exists_by_email_or_phone(phone=payload.login_input)
|
| 81 |
|
| 82 |
-
logger.
|
| 83 |
|
| 84 |
# Send OTP via service
|
| 85 |
-
logger.
|
| 86 |
await UserService.send_otp(payload.login_input)
|
| 87 |
logger.info(f"OTP sent successfully to: {payload.login_input}")
|
| 88 |
|
| 89 |
# Create temporary token
|
| 90 |
-
logger.
|
| 91 |
temp_token = create_temp_token({
|
| 92 |
"sub": payload.login_input,
|
| 93 |
"type": "otp_verification"
|
| 94 |
}, expires_minutes=10)
|
| 95 |
|
| 96 |
logger.info(f"Temporary token created for: {payload.login_input}")
|
| 97 |
-
logger.
|
| 98 |
|
| 99 |
return {
|
| 100 |
"message": "OTP sent",
|
|
@@ -116,13 +116,13 @@ async def otp_login_handler(
|
|
| 116 |
temp_token: str = Depends(get_bearer_token)
|
| 117 |
):
|
| 118 |
logger.info(f"OTP login attempt started - login_input: {payload.login_input}")
|
| 119 |
-
logger.
|
| 120 |
|
| 121 |
try:
|
| 122 |
# Decode and validate temporary token
|
| 123 |
-
logger.
|
| 124 |
decoded = decode_token(temp_token)
|
| 125 |
-
logger.
|
| 126 |
|
| 127 |
if not decoded:
|
| 128 |
logger.warning("Failed to decode temporary token - token is invalid or expired")
|
|
@@ -131,7 +131,7 @@ async def otp_login_handler(
|
|
| 131 |
# Validate token subject matches login input
|
| 132 |
token_sub = decoded.get("sub")
|
| 133 |
token_type = decoded.get("type")
|
| 134 |
-
logger.
|
| 135 |
|
| 136 |
if token_sub != payload.login_input:
|
| 137 |
logger.warning(f"Token subject mismatch - token_sub: {token_sub}, login_input: {payload.login_input}")
|
|
@@ -144,7 +144,7 @@ async def otp_login_handler(
|
|
| 144 |
logger.info(f"Temporary token validation successful for: {payload.login_input}")
|
| 145 |
|
| 146 |
# Call user service for OTP verification and login
|
| 147 |
-
logger.
|
| 148 |
result = await UserService.otp_login_handler(payload.login_input, payload.otp)
|
| 149 |
|
| 150 |
logger.info(f"OTP login successful for: {payload.login_input}")
|
|
@@ -257,7 +257,7 @@ async def oauth_login_handler(payload: OAuthLoginRequest, request: Request):
|
|
| 257 |
}, expires_minutes=10)
|
| 258 |
|
| 259 |
# Log temporary OAuth session token (truncated)
|
| 260 |
-
logger.
|
| 261 |
|
| 262 |
return {"access_token": temp_token, "token_type": "bearer", "user_exists": user_exists}
|
| 263 |
|
|
@@ -291,9 +291,9 @@ async def register_user(
|
|
| 291 |
at = result.get("access_token")
|
| 292 |
rt = result.get("refresh_token")
|
| 293 |
if at:
|
| 294 |
-
logger.
|
| 295 |
if rt:
|
| 296 |
-
logger.
|
| 297 |
return result
|
| 298 |
|
| 299 |
# 🔄 Refresh access token using refresh token
|
|
@@ -304,7 +304,7 @@ async def refresh_token_handler(refresh_token: str = Depends(get_bearer_token)):
|
|
| 304 |
try:
|
| 305 |
# Decode and validate refresh token
|
| 306 |
decoded = decode_token(refresh_token)
|
| 307 |
-
logger.
|
| 308 |
|
| 309 |
if not decoded:
|
| 310 |
logger.warning("Failed to decode refresh token - token is invalid or expired")
|
|
|
|
| 40 |
)
|
| 41 |
|
| 42 |
# If "Bearer " prefix is included, strip it
|
| 43 |
+
logger.info(f"Received Authorization header: {api_key}")
|
| 44 |
if api_key.lower().startswith("bearer "):
|
| 45 |
return api_key[7:] # Remove "Bearer " prefix
|
| 46 |
|
|
|
|
| 67 |
# Validate identifier format
|
| 68 |
try:
|
| 69 |
identifier_type = validate_identifier(payload.login_input)
|
| 70 |
+
logger.info(f"Login input type: {identifier_type}")
|
| 71 |
except ValueError as ve:
|
| 72 |
logger.error(f"Invalid login input format: {str(ve)}")
|
| 73 |
raise HTTPException(status_code=400, detail=str(ve))
|
|
|
|
| 79 |
elif identifier_type == "phone":
|
| 80 |
user_exists = await BookMyServiceUserModel.exists_by_email_or_phone(phone=payload.login_input)
|
| 81 |
|
| 82 |
+
logger.info(f"User existence check result: {user_exists}")
|
| 83 |
|
| 84 |
# Send OTP via service
|
| 85 |
+
logger.info(f"Calling UserService.send_otp with identifier: {payload.login_input}")
|
| 86 |
await UserService.send_otp(payload.login_input)
|
| 87 |
logger.info(f"OTP sent successfully to: {payload.login_input}")
|
| 88 |
|
| 89 |
# Create temporary token
|
| 90 |
+
logger.info("Creating temporary token for OTP verification")
|
| 91 |
temp_token = create_temp_token({
|
| 92 |
"sub": payload.login_input,
|
| 93 |
"type": "otp_verification"
|
| 94 |
}, expires_minutes=10)
|
| 95 |
|
| 96 |
logger.info(f"Temporary token created for: {payload.login_input}")
|
| 97 |
+
logger.info(f"Temp token (first 20 chars): {temp_token[:20]}...")
|
| 98 |
|
| 99 |
return {
|
| 100 |
"message": "OTP sent",
|
|
|
|
| 116 |
temp_token: str = Depends(get_bearer_token)
|
| 117 |
):
|
| 118 |
logger.info(f"OTP login attempt started - login_input: {payload.login_input}")
|
| 119 |
+
logger.info(f"Received temp_token: {temp_token[:20]}..." if temp_token else "No temp_token received")
|
| 120 |
|
| 121 |
try:
|
| 122 |
# Decode and validate temporary token
|
| 123 |
+
logger.info("Attempting to decode temporary token")
|
| 124 |
decoded = decode_token(temp_token)
|
| 125 |
+
logger.info(f"Decoded token payload: {decoded}")
|
| 126 |
|
| 127 |
if not decoded:
|
| 128 |
logger.warning("Failed to decode temporary token - token is invalid or expired")
|
|
|
|
| 131 |
# Validate token subject matches login input
|
| 132 |
token_sub = decoded.get("sub")
|
| 133 |
token_type = decoded.get("type")
|
| 134 |
+
logger.info(f"Token subject: {token_sub}, Token type: {token_type}")
|
| 135 |
|
| 136 |
if token_sub != payload.login_input:
|
| 137 |
logger.warning(f"Token subject mismatch - token_sub: {token_sub}, login_input: {payload.login_input}")
|
|
|
|
| 144 |
logger.info(f"Temporary token validation successful for: {payload.login_input}")
|
| 145 |
|
| 146 |
# Call user service for OTP verification and login
|
| 147 |
+
logger.info(f"Calling UserService.otp_login_handler with identifier: {payload.login_input}, otp: {payload.otp}")
|
| 148 |
result = await UserService.otp_login_handler(payload.login_input, payload.otp)
|
| 149 |
|
| 150 |
logger.info(f"OTP login successful for: {payload.login_input}")
|
|
|
|
| 257 |
}, expires_minutes=10)
|
| 258 |
|
| 259 |
# Log temporary OAuth session token (truncated)
|
| 260 |
+
logger.info(f"OAuth temp token generated (first 25 chars): {temp_token[:25]}...")
|
| 261 |
|
| 262 |
return {"access_token": temp_token, "token_type": "bearer", "user_exists": user_exists}
|
| 263 |
|
|
|
|
| 291 |
at = result.get("access_token")
|
| 292 |
rt = result.get("refresh_token")
|
| 293 |
if at:
|
| 294 |
+
logger.info(f"Register access token (first 25 chars): {at[:25]}...")
|
| 295 |
if rt:
|
| 296 |
+
logger.info(f"Register refresh token (first 25 chars): {rt[:25]}...")
|
| 297 |
return result
|
| 298 |
|
| 299 |
# 🔄 Refresh access token using refresh token
|
|
|
|
| 304 |
try:
|
| 305 |
# Decode and validate refresh token
|
| 306 |
decoded = decode_token(refresh_token)
|
| 307 |
+
logger.info(f"Decoded refresh token payload: {decoded}")
|
| 308 |
|
| 309 |
if not decoded:
|
| 310 |
logger.warning("Failed to decode refresh token - token is invalid or expired")
|
app/services/user_service.py
CHANGED
|
@@ -24,7 +24,7 @@ class UserService:
|
|
| 24 |
try:
|
| 25 |
# Validate identifier format
|
| 26 |
identifier_type = validate_identifier(identifier)
|
| 27 |
-
logger.
|
| 28 |
|
| 29 |
# Enhanced rate limiting by IP and identifier
|
| 30 |
if client_ip:
|
|
@@ -46,7 +46,7 @@ class UserService:
|
|
| 46 |
|
| 47 |
# Generate OTP - hardcoded for testing purposes
|
| 48 |
otp = '777777'
|
| 49 |
-
logger.
|
| 50 |
|
| 51 |
await BookMyServiceOTPModel.store_otp(identifier, phone_number, otp)
|
| 52 |
|
|
@@ -55,7 +55,7 @@ class UserService:
|
|
| 55 |
await BookMyServiceOTPModel.increment_rate_limit(ip_rate_key, 3600) # 1 hour window
|
| 56 |
|
| 57 |
logger.info(f"OTP stored successfully for identifier: {identifier}")
|
| 58 |
-
logger.
|
| 59 |
|
| 60 |
except ValueError as ve:
|
| 61 |
logger.error(f"Validation error for identifier {identifier}: {str(ve)}")
|
|
@@ -71,7 +71,7 @@ class UserService:
|
|
| 71 |
try:
|
| 72 |
# Validate identifier format
|
| 73 |
identifier_type = validate_identifier(identifier)
|
| 74 |
-
logger.
|
| 75 |
|
| 76 |
# Check if account is locked
|
| 77 |
if await BookMyServiceOTPModel.is_account_locked(identifier):
|
|
@@ -79,9 +79,9 @@ class UserService:
|
|
| 79 |
raise HTTPException(status_code=423, detail="Account temporarily locked due to too many failed attempts")
|
| 80 |
|
| 81 |
# Verify OTP with client IP tracking
|
| 82 |
-
logger.
|
| 83 |
otp_valid = await BookMyServiceOTPModel.verify_otp(identifier, otp, client_ip)
|
| 84 |
-
logger.
|
| 85 |
|
| 86 |
if not otp_valid:
|
| 87 |
logger.warning(f"Invalid or expired OTP for identifier: {identifier}")
|
|
@@ -94,9 +94,9 @@ class UserService:
|
|
| 94 |
logger.info(f"OTP verification successful for identifier: {identifier}")
|
| 95 |
|
| 96 |
# Find user by identifier
|
| 97 |
-
logger.
|
| 98 |
user = await BookMyServiceUserModel.find_by_identifier(identifier)
|
| 99 |
-
logger.
|
| 100 |
|
| 101 |
if not user:
|
| 102 |
logger.warning(f"No user found for identifier: {identifier}")
|
|
@@ -105,12 +105,12 @@ class UserService:
|
|
| 105 |
logger.info(f"User found for identifier: {identifier}, customer_id: {user.get('customer_id')}")
|
| 106 |
|
| 107 |
# Create JWT token
|
| 108 |
-
logger.
|
| 109 |
token_data = {
|
| 110 |
"sub": user.get("customer_id"),
|
| 111 |
"exp": datetime.utcnow() + timedelta(hours=8)
|
| 112 |
}
|
| 113 |
-
logger.
|
| 114 |
|
| 115 |
access_token = jwt.encode(token_data, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
|
| 116 |
|
|
@@ -122,8 +122,8 @@ class UserService:
|
|
| 122 |
refresh_token = jwt.encode(refresh_token_data, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
|
| 123 |
|
| 124 |
# Log generated tokens (truncated for security)
|
| 125 |
-
logger.
|
| 126 |
-
logger.
|
| 127 |
|
| 128 |
logger.info(f"JWT tokens created successfully for user: {user.get('customer_id')}")
|
| 129 |
|
|
@@ -174,7 +174,7 @@ class UserService:
|
|
| 174 |
identifier_type = validate_identifier(identifier)
|
| 175 |
if identifier_type != "phone":
|
| 176 |
raise ValueError("Phone number format is invalid")
|
| 177 |
-
logger.
|
| 178 |
except ValueError as ve:
|
| 179 |
logger.error(f"Invalid phone format during registration: {str(ve)}")
|
| 180 |
raise HTTPException(status_code=400, detail=str(ve))
|
|
@@ -230,8 +230,8 @@ class UserService:
|
|
| 230 |
refresh_token = jwt.encode(refresh_token_data, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
|
| 231 |
|
| 232 |
# Log generated tokens for existing linked user (truncated)
|
| 233 |
-
logger.
|
| 234 |
-
logger.
|
| 235 |
|
| 236 |
return {
|
| 237 |
"access_token": access_token,
|
|
@@ -297,8 +297,8 @@ class UserService:
|
|
| 297 |
refresh_token = jwt.encode(refresh_token_data, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
|
| 298 |
|
| 299 |
# Log generated tokens for new registration (truncated)
|
| 300 |
-
logger.
|
| 301 |
-
logger.
|
| 302 |
|
| 303 |
return {
|
| 304 |
"access_token": access_token,
|
|
|
|
| 24 |
try:
|
| 25 |
# Validate identifier format
|
| 26 |
identifier_type = validate_identifier(identifier)
|
| 27 |
+
logger.info(f"Identifier type: {identifier_type}")
|
| 28 |
|
| 29 |
# Enhanced rate limiting by IP and identifier
|
| 30 |
if client_ip:
|
|
|
|
| 46 |
|
| 47 |
# Generate OTP - hardcoded for testing purposes
|
| 48 |
otp = '777777'
|
| 49 |
+
logger.info(f"Generated hardcoded OTP for identifier: {identifier}")
|
| 50 |
|
| 51 |
await BookMyServiceOTPModel.store_otp(identifier, phone_number, otp)
|
| 52 |
|
|
|
|
| 55 |
await BookMyServiceOTPModel.increment_rate_limit(ip_rate_key, 3600) # 1 hour window
|
| 56 |
|
| 57 |
logger.info(f"OTP stored successfully for identifier: {identifier}")
|
| 58 |
+
logger.info(f"OTP sent to {identifier}")
|
| 59 |
|
| 60 |
except ValueError as ve:
|
| 61 |
logger.error(f"Validation error for identifier {identifier}: {str(ve)}")
|
|
|
|
| 71 |
try:
|
| 72 |
# Validate identifier format
|
| 73 |
identifier_type = validate_identifier(identifier)
|
| 74 |
+
logger.info(f"Identifier type: {identifier_type}")
|
| 75 |
|
| 76 |
# Check if account is locked
|
| 77 |
if await BookMyServiceOTPModel.is_account_locked(identifier):
|
|
|
|
| 79 |
raise HTTPException(status_code=423, detail="Account temporarily locked due to too many failed attempts")
|
| 80 |
|
| 81 |
# Verify OTP with client IP tracking
|
| 82 |
+
logger.info(f"Verifying OTP for identifier: {identifier}")
|
| 83 |
otp_valid = await BookMyServiceOTPModel.verify_otp(identifier, otp, client_ip)
|
| 84 |
+
logger.info(f"OTP verification result: {otp_valid}")
|
| 85 |
|
| 86 |
if not otp_valid:
|
| 87 |
logger.warning(f"Invalid or expired OTP for identifier: {identifier}")
|
|
|
|
| 94 |
logger.info(f"OTP verification successful for identifier: {identifier}")
|
| 95 |
|
| 96 |
# Find user by identifier
|
| 97 |
+
logger.info(f"Looking up user by identifier: {identifier}")
|
| 98 |
user = await BookMyServiceUserModel.find_by_identifier(identifier)
|
| 99 |
+
logger.info(f"User lookup result: {user is not None}")
|
| 100 |
|
| 101 |
if not user:
|
| 102 |
logger.warning(f"No user found for identifier: {identifier}")
|
|
|
|
| 105 |
logger.info(f"User found for identifier: {identifier}, customer_id: {user.get('customer_id')}")
|
| 106 |
|
| 107 |
# Create JWT token
|
| 108 |
+
logger.info("Creating JWT token for authenticated user")
|
| 109 |
token_data = {
|
| 110 |
"sub": user.get("customer_id"),
|
| 111 |
"exp": datetime.utcnow() + timedelta(hours=8)
|
| 112 |
}
|
| 113 |
+
logger.info(f"Token data: {token_data}")
|
| 114 |
|
| 115 |
access_token = jwt.encode(token_data, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
|
| 116 |
|
|
|
|
| 122 |
refresh_token = jwt.encode(refresh_token_data, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
|
| 123 |
|
| 124 |
# Log generated tokens (truncated for security)
|
| 125 |
+
logger.info(f"Access token generated (first 25 chars): {access_token[:25]}...")
|
| 126 |
+
logger.info(f"Refresh token generated (first 25 chars): {refresh_token[:25]}...")
|
| 127 |
|
| 128 |
logger.info(f"JWT tokens created successfully for user: {user.get('customer_id')}")
|
| 129 |
|
|
|
|
| 174 |
identifier_type = validate_identifier(identifier)
|
| 175 |
if identifier_type != "phone":
|
| 176 |
raise ValueError("Phone number format is invalid")
|
| 177 |
+
logger.info(f"Registration identifier type: {identifier_type}")
|
| 178 |
except ValueError as ve:
|
| 179 |
logger.error(f"Invalid phone format during registration: {str(ve)}")
|
| 180 |
raise HTTPException(status_code=400, detail=str(ve))
|
|
|
|
| 230 |
refresh_token = jwt.encode(refresh_token_data, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
|
| 231 |
|
| 232 |
# Log generated tokens for existing linked user (truncated)
|
| 233 |
+
logger.info(f"Access token for existing user (first 25 chars): {access_token[:25]}...")
|
| 234 |
+
logger.info(f"Refresh token for existing user (first 25 chars): {refresh_token[:25]}...")
|
| 235 |
|
| 236 |
return {
|
| 237 |
"access_token": access_token,
|
|
|
|
| 297 |
refresh_token = jwt.encode(refresh_token_data, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
|
| 298 |
|
| 299 |
# Log generated tokens for new registration (truncated)
|
| 300 |
+
logger.info(f"Access token on register (first 25 chars): {access_token[:25]}...")
|
| 301 |
+
logger.info(f"Refresh token on register (first 25 chars): {refresh_token[:25]}...")
|
| 302 |
|
| 303 |
return {
|
| 304 |
"access_token": access_token,
|