Skip to content

Commit

Permalink
feat: add login command with full FE E2E flow
Browse files Browse the repository at this point in the history
  • Loading branch information
utkarsh-dixit committed Apr 9, 2024
1 parent 12a1631 commit ab6836a
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 48 deletions.
66 changes: 40 additions & 26 deletions core/composio/composio_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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()
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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")
Expand All @@ -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:
Expand Down Expand Up @@ -342,25 +368,13 @@ 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()

args = parse_arguments()

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)
Expand Down
36 changes: 20 additions & 16 deletions core/composio/sdk/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ class FrameworkEnum(Enum):

__IS_FIRST_TIME__ = True

class UnauthorizedAccessException(Exception):
pass

class ComposioCore:
sdk: Composio = None
framework: FrameworkEnum = None
Expand Down Expand Up @@ -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):
Expand Down
6 changes: 0 additions & 6 deletions core/setup.py
Original file line number Diff line number Diff line change
@@ -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",
Expand Down

0 comments on commit ab6836a

Please sign in to comment.