From ab6836ac141fb676adb39d8439d7ccd2bb5ef889 Mon Sep 17 00:00:00 2001 From: Utkarsh Dixit Date: Tue, 9 Apr 2024 21:15:22 +0530 Subject: [PATCH] feat: add login command with full FE E2E flow --- core/composio/composio_cli.py | 66 +++++++++++++++++++++-------------- core/composio/sdk/core.py | 36 ++++++++++--------- core/setup.py | 6 ---- 3 files changed, 60 insertions(+), 48 deletions(-) diff --git a/core/composio/composio_cli.py b/core/composio/composio_cli.py index 4f3377ceea..f9f2429a3f 100755 --- a/core/composio/composio_cli.py +++ b/core/composio/composio_cli.py @@ -7,8 +7,8 @@ from rich.console import Console import termcolor from uuid import getnode as get_mac -from .sdk.storage import get_user_connection, save_user_connection -from .sdk.core import ComposioCore +from .sdk.storage import get_user_connection, save_api_key, save_user_connection +from .sdk.core import ComposioCore, UnauthorizedAccessException from .sdk.utils import generate_enums from rich.table import Table @@ -26,6 +26,10 @@ def parse_arguments(): add_parser.add_argument('integration_name', type=str, help='Name of the integration to add') add_parser.set_defaults(func=add_integration) + # Login command + login_parser = subparsers.add_parser('login', help='Login to Composio') + login_parser.set_defaults(func=login) + # Show active triggers command show_triggers_parser = subparsers.add_parser('list-active-triggers', help='List all triggers for a given app') show_triggers_parser.set_defaults(func=list_active_triggers) @@ -94,9 +98,42 @@ def parse_arguments(): return parser.parse_args() +def login(args): + client = ComposioCore() + if client.is_authenticated(): + console.print("Already authenticated. Use [green]composio-cli logout[/green] to log out.\n") + return + + console.print(f"\n[green]> Authenticating...[/green]\n") + try: + cli_key = client.generate_cli_auth_session() + console.print(f"> Redirecting you to the login page. Please login using the following link:\n") + console.print(f"[green]https://app.composio.dev/?cliKey={cli_key}[/green]\n") + webbrowser.open(f"https://app.composio.dev/?cliKey={cli_key}") + + for attempt in range(3): + try: + session_data = client.verify_cli_auth_session(cli_key, input("> Enter the code: ")) + api_key = session_data.get('apiKey') + if api_key: + save_api_key(api_key) + console.print(f"\n[green]✔ Authenticated successfully![/green]\n") + break + except UnauthorizedAccessException as e: + if attempt == 2: # Last attempt + console.print("[red]\nAuthentication failed after 3 attempts.[/red]") + else: + console.print(f"[red] Invalid code. Please try again.[/red]") + continue # Exit the loop on unauthorized access + except Exception as e: + console.print(f"[red]Error occurred during authentication: {e}[/red]") + if attempt == 2: # Last attempt + console.print("[red]Authentication failed after 3 attempts.[/red]") + except Exception as e: + console.print(f"[red] Error occurred during authentication: {e}[/red]") + def whoami(args): client = ComposioCore() - auth_user(client) user_info = client.get_authenticated_user() console.print(f"- API Key: [green]{user_info['api_key']}[/green]\n") return @@ -113,7 +150,6 @@ def logout(args): def update_base_url(args): client = ComposioCore() - auth_user(client) base_url = args.base_url console.print(f"\n[green]> Updating base URL to: {base_url}...[/green]\n") try: @@ -125,7 +161,6 @@ def update_base_url(args): def list_active_triggers(args): client = ComposioCore() - auth_user(client) console.print(f"\n[green]Listing all your active triggers...[/green]\n") try: triggers = client.list_active_triggers() @@ -151,7 +186,6 @@ def list_active_triggers(args): def get_trigger(args): client = ComposioCore() - auth_user(client) trigger_id = args.trigger_id console.print(f"\n[green]> Getting more details about trigger: {trigger_id}...[/green]\n") try: @@ -174,7 +208,6 @@ def get_trigger(args): def disable_trigger(args): client = ComposioCore() - auth_user(client) trigger_id = args.trigger_id console.print(f"\n[green]> Disabling trigger: {trigger_id}...[/green]\n") try: @@ -186,7 +219,6 @@ def disable_trigger(args): def list_triggers(args): client = ComposioCore() - auth_user(client) app_name = args.app_name console.print(f"\n[green]> Listing triggers for app: {app_name}...[/green]\n") try: @@ -205,7 +237,6 @@ def list_triggers(args): def enable_trigger(args): client = ComposioCore() - auth_user(client) trigger_name = args.trigger_name console.print(f"\n[green]> Enabling trigger: {trigger_name}...[/green]\n") try: @@ -250,7 +281,6 @@ def enable_trigger(args): def set_global_trigger_callback(args): client = ComposioCore() - auth_user(client) console.print(f"\n[green]> Setting global trigger callback to: {args.callback_url}...[/green]\n") try: client.set_global_trigger(args.callback_url) @@ -261,13 +291,11 @@ def set_global_trigger_callback(args): def handle_update(args): client = ComposioCore() - auth_user(client) generate_enums() console.print(f"\n[green]✔ Enums updated successfully![/green]\n") def add_integration(args): client = ComposioCore() - auth_user(client) integration_name = args.integration_name existing_connection = get_user_connection(integration_name) @@ -297,7 +325,6 @@ def add_integration(args): def show_apps(args): client = ComposioCore() - auth_user(client) apps_list = client.sdk.get_list_of_apps() app_names_list = [{"name": app.get('name'), "uniqueId": app.get('key'), "appId": app.get('appId')} for app in apps_list.get('items')] console.print("\n[green]> Available apps supported by composio:[/green]\n") @@ -310,7 +337,6 @@ def show_apps(args): def list_connections(args): client = ComposioCore() - auth_user(client) appName = args.appName console.print(f"\n[green]> Listing connections for: {appName}...[/green]\n") try: @@ -342,12 +368,6 @@ def print_intro(): └───────────────────────────────────────────────────────────────────────────┘ """) -def auth_user(client: ComposioCore): - user_mac_address = get_mac() - unique_identifier = f"{user_mac_address}" - - return client.authenticate(unique_identifier) - def main(): print_intro() @@ -355,12 +375,6 @@ def main(): client = ComposioCore() - try: - user = auth_user(client) - except Exception as e: - console.print(f"> Error occurred during user identification:\n\n[red]{e}[/red]") - sys.exit(1) - if hasattr(args, 'func'): try: args.func(args) diff --git a/core/composio/sdk/core.py b/core/composio/sdk/core.py index d5207548cb..1061c596e0 100644 --- a/core/composio/sdk/core.py +++ b/core/composio/sdk/core.py @@ -14,6 +14,9 @@ class FrameworkEnum(Enum): __IS_FIRST_TIME__ = True +class UnauthorizedAccessException(Exception): + pass + class ComposioCore: sdk: Composio = None framework: FrameworkEnum = None @@ -76,22 +79,23 @@ def logout(self): delete_user_connections() save_user_data(user_data) - def authenticate(self, hash: str): - resp = self.http_client.post(f"{self.base_url}/v1/client/auth/identify", json={ - "hash": hash, - }); - if resp.status_code == 202: - api_key = resp.json().get('apiKey') - self.http_client.headers.update({ - 'Content-Type': 'application/json', - 'x-api-key': api_key - }) - self.sdk = Composio(api_key, self.base_url) - if self.manage_auth: - save_api_key(api_key) - return api_key - - raise Exception("Failed to authenticate: Please use composio-cli login to authenticate") + def generate_cli_auth_session(self): + resp = self.http_client.get(f"{self.base_url}/v1/cli/generate-cli-session"); + if resp.status_code == 200: + resp = resp.json() + if resp.get('key'): + return resp['key'] + + raise Exception("Bad request to cli/generate-cli-session") + + def verify_cli_auth_session(self, key: str, code: str): + resp = self.http_client.get(f"{self.base_url}/v1/cli/verify-cli-code?key={key}&code={code}"); + if resp.status_code == 200: + return resp.json() + elif resp.status_code == 401: + raise UnauthorizedAccessException("UnauthorizedError: Unauthorized access to cli/verify-cli-session") + + raise Exception("Bad request to cli/verify-cli-session") def initiate_connection(self, integrationId: Union[str, TestIntegration]) -> ConnectionRequest: if isinstance(integrationId, TestIntegration): diff --git a/core/setup.py b/core/setup.py index 272a95f71f..1a8f03c3ff 100644 --- a/core/setup.py +++ b/core/setup.py @@ -1,28 +1,22 @@ from setuptools import setup import os - from setuptools import setup from setuptools.command.install import install - def get_current_dir(): return os.path.dirname(os.path.realpath(__file__)) - def resolve_paths(*paths): return os.path.join(*paths) - readme_path = resolve_paths(get_current_dir(), "README.md") - class InstallCommandMiddleware(install): """Customized setuptools install command.""" def run(self): install.run(self) - setup( name="composio_core", version="0.1.83",