Add subcription feature 6
This commit is contained in:
@@ -14,6 +14,7 @@ from shared.exceptions.auth_exceptions import (
|
||||
RegistrationError,
|
||||
PaymentOrchestrationError
|
||||
)
|
||||
from shared.auth.decorators import get_current_user_dep
|
||||
|
||||
# Configure logging
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -291,8 +292,15 @@ async def login(
|
||||
|
||||
logger.info(f"Login successful, email={login_data.email}, user_id={result['user'].id}")
|
||||
|
||||
# Extract tokens from result for top-level response
|
||||
tokens = result.get('tokens', {})
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"access_token": tokens.get('access_token'),
|
||||
"refresh_token": tokens.get('refresh_token'),
|
||||
"token_type": tokens.get('token_type'),
|
||||
"expires_in": tokens.get('expires_in'),
|
||||
"user": {
|
||||
"id": str(result['user'].id),
|
||||
"email": result['user'].email,
|
||||
@@ -300,7 +308,6 @@ async def login(
|
||||
"is_active": result['user'].is_active,
|
||||
"last_login": result['user'].last_login.isoformat() if result['user'].last_login else None
|
||||
},
|
||||
"tokens": result.get('tokens', {}),
|
||||
"subscription": result.get('subscription', {}),
|
||||
"message": "Login successful"
|
||||
}
|
||||
@@ -317,3 +324,309 @@ async def login(
|
||||
) from e
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# TOKEN MANAGEMENT ENDPOINTS - NEWLY ADDED
|
||||
# ============================================================================
|
||||
|
||||
@router.post("/refresh",
|
||||
response_model=Dict[str, Any],
|
||||
summary="Refresh access token using refresh token")
|
||||
async def refresh_token(
|
||||
request: Request,
|
||||
refresh_data: Dict[str, Any],
|
||||
auth_service: AuthService = Depends(get_auth_service)
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Refresh access token using a valid refresh token
|
||||
|
||||
This endpoint:
|
||||
1. Validates the refresh token
|
||||
2. Generates new access and refresh tokens
|
||||
3. Returns the new tokens
|
||||
|
||||
Args:
|
||||
refresh_data: Dictionary containing refresh_token
|
||||
|
||||
Returns:
|
||||
New authentication tokens
|
||||
|
||||
Raises:
|
||||
HTTPException: 401 for invalid refresh tokens
|
||||
"""
|
||||
try:
|
||||
logger.info("Token refresh request initiated")
|
||||
|
||||
# Extract refresh token from request
|
||||
refresh_token = refresh_data.get("refresh_token")
|
||||
if not refresh_token:
|
||||
logger.warning("Refresh token missing from request")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Refresh token is required"
|
||||
)
|
||||
|
||||
# Use service layer to refresh tokens
|
||||
tokens = await auth_service.refresh_auth_tokens(refresh_token)
|
||||
|
||||
logger.info("Token refresh successful via service layer")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"access_token": tokens.get("access_token"),
|
||||
"refresh_token": tokens.get("refresh_token"),
|
||||
"token_type": "bearer",
|
||||
"expires_in": 1800, # 30 minutes
|
||||
"message": "Token refresh successful"
|
||||
}
|
||||
|
||||
except HTTPException:
|
||||
# Re-raise HTTP exceptions
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Token refresh failed: {str(e)}", exc_info=True)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Token refresh failed: {str(e)}"
|
||||
) from e
|
||||
|
||||
|
||||
@router.post("/verify",
|
||||
response_model=Dict[str, Any],
|
||||
summary="Verify token validity")
|
||||
async def verify_token(
|
||||
request: Request,
|
||||
token_data: Dict[str, Any]
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Verify the validity of an access token
|
||||
|
||||
Args:
|
||||
token_data: Dictionary containing access_token
|
||||
|
||||
Returns:
|
||||
Token validation result
|
||||
"""
|
||||
try:
|
||||
logger.info("Token verification request initiated")
|
||||
|
||||
# Extract access token from request
|
||||
access_token = token_data.get("access_token")
|
||||
if not access_token:
|
||||
logger.warning("Access token missing from verification request")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Access token is required"
|
||||
)
|
||||
|
||||
# Use service layer to verify token
|
||||
result = await auth_service.verify_access_token(access_token)
|
||||
|
||||
logger.info("Token verification successful via service layer")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"valid": result.get("valid"),
|
||||
"user_id": result.get("user_id"),
|
||||
"email": result.get("email"),
|
||||
"message": "Token is valid"
|
||||
}
|
||||
|
||||
except HTTPException:
|
||||
# Re-raise HTTP exceptions
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Token verification failed: {str(e)}", exc_info=True)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Token verification failed: {str(e)}"
|
||||
) from e
|
||||
|
||||
|
||||
@router.post("/logout",
|
||||
response_model=Dict[str, Any],
|
||||
summary="Logout and revoke refresh token")
|
||||
async def logout(
|
||||
request: Request,
|
||||
logout_data: Dict[str, Any],
|
||||
auth_service: AuthService = Depends(get_auth_service)
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Logout user and revoke refresh token
|
||||
|
||||
Args:
|
||||
logout_data: Dictionary containing refresh_token
|
||||
|
||||
Returns:
|
||||
Logout confirmation
|
||||
"""
|
||||
try:
|
||||
logger.info("Logout request initiated")
|
||||
|
||||
# Extract refresh token from request
|
||||
refresh_token = logout_data.get("refresh_token")
|
||||
if not refresh_token:
|
||||
logger.warning("Refresh token missing from logout request")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Refresh token is required"
|
||||
)
|
||||
|
||||
# Use service layer to revoke refresh token
|
||||
try:
|
||||
await auth_service.revoke_refresh_token(refresh_token)
|
||||
logger.info("Logout successful via service layer")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Logout successful"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error during logout: {str(e)}")
|
||||
# Don't fail logout if revocation fails
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Logout successful (token revocation failed but user logged out)"
|
||||
}
|
||||
|
||||
except HTTPException:
|
||||
# Re-raise HTTP exceptions
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Logout failed: {str(e)}", exc_info=True)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Logout failed: {str(e)}"
|
||||
) from e
|
||||
|
||||
|
||||
@router.post("/change-password",
|
||||
response_model=Dict[str, Any],
|
||||
summary="Change user password")
|
||||
async def change_password(
|
||||
request: Request,
|
||||
password_data: Dict[str, Any],
|
||||
auth_service: AuthService = Depends(get_auth_service)
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Change user password
|
||||
|
||||
Args:
|
||||
password_data: Dictionary containing current_password and new_password
|
||||
|
||||
Returns:
|
||||
Password change confirmation
|
||||
"""
|
||||
try:
|
||||
logger.info("Password change request initiated")
|
||||
|
||||
# Extract user from request state
|
||||
if not hasattr(request.state, 'user') or not request.state.user:
|
||||
logger.warning("Unauthorized password change attempt - no user context")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Authentication required"
|
||||
)
|
||||
|
||||
user_id = request.state.user.get("user_id")
|
||||
if not user_id:
|
||||
logger.warning("Unauthorized password change attempt - no user_id")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid user context"
|
||||
)
|
||||
|
||||
# Extract password data
|
||||
current_password = password_data.get("current_password")
|
||||
new_password = password_data.get("new_password")
|
||||
|
||||
if not current_password or not new_password:
|
||||
logger.warning("Password change missing required fields")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Current password and new password are required"
|
||||
)
|
||||
|
||||
if len(new_password) < 8:
|
||||
logger.warning("New password too short")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="New password must be at least 8 characters long"
|
||||
)
|
||||
|
||||
# Use service layer to change password
|
||||
await auth_service.change_user_password(user_id, current_password, new_password)
|
||||
|
||||
logger.info(f"Password change successful via service layer, user_id={user_id}")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Password changed successfully"
|
||||
}
|
||||
|
||||
except HTTPException:
|
||||
# Re-raise HTTP exceptions
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Password change failed: {str(e)}", exc_info=True)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Password change failed: {str(e)}"
|
||||
) from e
|
||||
|
||||
|
||||
@router.post("/verify-email",
|
||||
response_model=Dict[str, Any],
|
||||
summary="Verify user email")
|
||||
async def verify_email(
|
||||
request: Request,
|
||||
email_data: Dict[str, Any],
|
||||
auth_service: AuthService = Depends(get_auth_service)
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Verify user email (placeholder implementation)
|
||||
|
||||
Args:
|
||||
email_data: Dictionary containing email and verification_token
|
||||
|
||||
Returns:
|
||||
Email verification confirmation
|
||||
"""
|
||||
try:
|
||||
logger.info("Email verification request initiated")
|
||||
|
||||
# Extract email and token
|
||||
email = email_data.get("email")
|
||||
verification_token = email_data.get("verification_token")
|
||||
|
||||
if not email or not verification_token:
|
||||
logger.warning("Email verification missing required fields")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Email and verification token are required"
|
||||
)
|
||||
|
||||
# Use service layer to verify email
|
||||
await auth_service.verify_user_email(email, verification_token)
|
||||
|
||||
logger.info("Email verification successful via service layer")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Email verified successfully"
|
||||
}
|
||||
|
||||
except HTTPException:
|
||||
# Re-raise HTTP exceptions
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Email verification failed: {str(e)}", exc_info=True)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Email verification failed: {str(e)}"
|
||||
) from e
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user