Spaces:
Sleeping
Sleeping
File size: 3,511 Bytes
f84a02d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
import base64
import json
import logging
import os
from typing import Any, Dict, Optional
from urllib.parse import urlencode
from fastapi import HTTPException, Request
from pydantic import BaseModel
# Try to import crypto utils, fallback to dummy for initial setup
try:
from backend.core.encryption import decrypt_data, encrypt_data
except ImportError:
# Fallback for dev/setup phase
def encrypt_data(data: str) -> str:
return base64.b64encode(data.encode()).decode()
def decrypt_data(data: str) -> str:
return base64.b64decode(data.encode()).decode()
logger = logging.getLogger(__name__)
class OAuthState(BaseModel):
integration_type: str # 'native' or 'activepieces'
service_id: str
user_id: str
redirect_path: Optional[str] = None
extra_data: Optional[Dict[str, Any]] = None
class UniversalAuthHandler:
def __init__(self):
self.app_domain = os.getenv("APP_DOMAIN", "localhost:3000")
self.url_scheme = os.getenv("URL_SCHEME", "http")
# Ensure domain doesn't have trailing slash
if self.app_domain.endswith("/"):
self.app_domain = self.app_domain[:-1]
self.callback_url = f"{self.url_scheme}://{self.app_domain}/api/v1/integrations/universal/callback"
def generate_oauth_url(self,
auth_url: str,
client_id: str,
scopes: list,
state_payload: OAuthState,
extra_params: Dict[str, str] = None) -> str:
"""
Generates a standard OAuth 2.0 URL pointing to the provider,
but with our Universal Callback URL as the redirect_uri.
"""
# 1. Serialize and encrypt state
state_json = state_payload.json()
encrypted_state = encrypt_data(state_json)
# 2. Construct params
params = {
"response_type": "code",
"client_id": client_id,
"redirect_uri": self.callback_url,
"scope": " ".join(scopes),
"state": encrypted_state
}
if extra_params:
params.update(extra_params)
# 3. Build URL
query_string = urlencode(params)
return f"{auth_url}?{query_string}"
async def handle_callback(self, code: str, state: str) -> Dict[str, Any]:
"""
Processes the callback from the provider.
Decodes state to determine which integration strategy to use for token exchange.
"""
try:
# 1. Decrypt state
decrypted_state_json = decrypt_data(state)
state_data = json.loads(decrypted_state_json)
oauth_state = OAuthState(**state_data)
logger.info(f"Processing callback for service: {oauth_state.service_id} ({oauth_state.integration_type})")
# 2. Return info for the caller (API route) to handle the actual exchange
# The API route will look up the client_secret and token_url based on service_id
return {
"code": code,
"state": oauth_state,
"callback_url": self.callback_url
}
except Exception as e:
logger.error(f"Callback processing failed: {str(e)}")
raise HTTPException(status_code=400, detail="Invalid OAuth state or callback data")
# Singleton instance
universal_auth = UniversalAuthHandler()
|