agent-control-center/backend/server/app/api/connectors.py

97 lines
3.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""OAuth коннекторы — GitHub, Gmail, Google Drive, Yandex Disk, Google Calendar"""
from __future__ import annotations
import secrets, json
from fastapi import APIRouter
from pydantic import BaseModel
router = APIRouter(prefix="/api/v1/connectors", tags=["connectors"])
# In-memory OAuth state store (заменить на БД в production)
_pending: dict[str, dict] = {}
_connections: dict[str, list[dict]] = {}
OAUTH_CONFIG = {
"github": {
"name": "GitHub",
"auth_url": "https://github.com/login/oauth/authorize",
"scope": "repo,user",
},
"gmail": {
"name": "Gmail",
"auth_url": "https://accounts.google.com/o/oauth2/v2/auth",
"scope": "https://www.googleapis.com/auth/gmail.readonly",
},
"gdrive": {
"name": "Google Drive",
"auth_url": "https://accounts.google.com/o/oauth2/v2/auth",
"scope": "https://www.googleapis.com/auth/drive.readonly",
},
"gcal": {
"name": "Google Calendar",
"auth_url": "https://accounts.google.com/o/oauth2/v2/auth",
"scope": "https://www.googleapis.com/auth/calendar.readonly",
},
"ydisk": {
"name": "Yandex Disk",
"auth_url": "https://oauth.yandex.ru/authorize",
"scope": "cloud_api:disk.read",
},
}
class OAuthInitRequest(BaseModel):
provider: str
project_id: str
@router.post("/oauth/init")
def oauth_init(req: OAuthInitRequest):
if req.provider not in OAUTH_CONFIG:
return {"error": "unknown_provider"}
cfg = OAUTH_CONFIG[req.provider]
state = secrets.token_urlsafe(16)
_pending[state] = {"provider": req.provider, "project_id": req.project_id}
# В production здесь формируется полный URL с client_id и redirect_uri
client_id = "ACC_CLIENT_ID_PLACEHOLDER"
redirect_uri = "http://localhost:8100/api/v1/connectors/oauth/callback"
auth_url = f"{cfg['auth_url']}?client_id={client_id}&redirect_uri={redirect_uri}&scope={cfg['scope']}&state={state}&response_type=code"
return {"auth_url": auth_url, "state": state, "provider": req.provider}
@router.get("/oauth/callback")
def oauth_callback(code: str, state: str):
"""OAuth callback — обработка кода авторизации"""
if state not in _pending:
return {"error": "invalid_state"}
pending = _pending.pop(state)
provider = pending["provider"]
# В production: обменять code на access_token через POST к token endpoint
connection = {
"provider": provider,
"account": f"{provider}_user",
"label": OAUTH_CONFIG[provider]["name"],
"token_valid": True,
"state": state,
}
if pending["project_id"] not in _connections:
_connections[pending["project_id"]] = []
_connections[pending["project_id"]].append(connection)
return {"connected": True, "provider": provider, **connection}
@router.get("/oauth/check")
def oauth_check(state: str):
"""Проверка статуса OAuth-авторизации (polling)"""
if state in _pending:
return {"connected": False, "status": "pending"}
# Найти connection по state
for conns in _connections.values():
for c in conns:
if c.get("state") == state:
return {"connected": True, **c}
return {"connected": False, "status": "expired"}