feat: update code and decouple auth

This commit is contained in:
Eduardo Chiarotti
2024-08-21 16:11:16 -03:00
parent 3b0286f592
commit 414a9ba07e
8 changed files with 290 additions and 167 deletions

View File

@@ -0,0 +1,78 @@
import time
import webbrowser
from typing import Any, Dict, Optional
import requests
from rich.console import Console
from .constants import AUTH0_CLIENT_ID, AUTH0_DOMAIN
from .utils import validate_token
console = Console()
class Authentication:
DEVICE_CODE_URL = f"https://{AUTH0_DOMAIN}/oauth/device/code"
TOKEN_URL = f"https://{AUTH0_DOMAIN}/oauth/token"
def signup(self) -> Optional[Dict[str, Any]]:
"""Sign up to CrewAI+"""
console.print("Signing Up to CrewAI+ \n", style="bold blue")
device_code_data = self._get_device_code()
self._display_auth_instructions(device_code_data)
return self._poll_for_token(device_code_data)
def _get_device_code(self) -> Dict[str, Any]:
"""Get the device code to authenticate the user."""
device_code_payload = {
"client_id": AUTH0_CLIENT_ID,
"scope": "openid profile email",
"audience": "https://dev-jzsr0j8zs0atl5ha.us.auth0.com/api/v2/",
}
response = requests.post(url=self.DEVICE_CODE_URL, data=device_code_payload)
response.raise_for_status()
return response.json()
def _display_auth_instructions(self, device_code_data: Dict[str, str]) -> None:
"""Display the authentication instructions to the user."""
console.print("1. Navigate to: ", device_code_data["verification_uri_complete"])
console.print("2. Enter the following code: ", device_code_data["user_code"])
webbrowser.open(device_code_data["verification_uri_complete"])
def _poll_for_token(
self, device_code_data: Dict[str, Any]
) -> Optional[Dict[str, Any]]:
"""Poll the server for the token."""
token_payload = {
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
"device_code": device_code_data["device_code"],
"client_id": AUTH0_CLIENT_ID,
}
attempts = 0
while True and attempts < 5:
response = requests.post(self.TOKEN_URL, data=token_payload)
token_data = response.json()
if response.status_code == 200:
validate_token(token_data["id_token"])
# current_user = jwt.decode(
# token_data["id_token"],
# algorithms=ALGORITHMS,
# options={"verify_signature": False},
# )
console.print("\nWelcome to CrewAI+ !!", style="green")
return token_data
if token_data["error"] not in ("authorization_pending", "slow_down"):
raise requests.HTTPError(token_data["error_description"])
time.sleep(device_code_data["interval"])
attempts += 1
console.print(
"Timeout: Failed to get the token. Please try again.", style="bold red"
)

View File

@@ -0,0 +1,21 @@
from auth0.authentication.token_verifier import (
AsymmetricSignatureVerifier,
TokenVerifier,
)
from .constants import AUTH0_CLIENT_ID, AUTH0_DOMAIN
def validate_token(id_token: str) -> None:
"""
Verify the token and its precedence
:param id_token:
"""
jwks_url = f"https://{AUTH0_DOMAIN}/.well-known/jwks.json"
issuer = f"https://{AUTH0_DOMAIN}/"
signature_verifier = AsymmetricSignatureVerifier(jwks_url)
token_verifier = TokenVerifier(
signature_verifier=signature_verifier, issuer=issuer, audience=AUTH0_CLIENT_ID
)
token_verifier.verify(id_token)

View File

@@ -9,6 +9,7 @@ from crewai.memory.storage.kickoff_task_outputs_storage import (
KickoffTaskOutputsSQLiteStorage,
)
from .authentication.main import Authentication
from .deploy.main import DeployCommand
from .evaluate_crew import evaluate_crew
from .replay_from_task import replay_task_command
@@ -177,23 +178,22 @@ def run():
run_crew()
# Deploy command group
@crewai.command()
def signup():
"""Sign Up/Login to CrewAI+."""
Authentication().signup()
# DEPLOY CREWAI+ COMMANDS
@crewai.group()
def deploy():
"""Deploy the Crew CLI group."""
pass
@deploy.command(name="up")
@click.option("-u", "--uuid", type=str, help="Crew UUID parameter")
def deploy_up(uuid: Optional[str]):
"""Deploy the crew."""
deploy_cmd.deploy(uuid=uuid)
@deploy.command(name="create")
def deploy_create():
"""Create a deployment."""
"""Create a Crew deployment."""
deploy_cmd.create_crew()
@@ -203,6 +203,13 @@ def deploy_list():
deploy_cmd.list_crews()
@deploy.command(name="push")
@click.option("-u", "--uuid", type=str, help="Crew UUID parameter")
def deploy_push(uuid: Optional[str]):
"""Deploy the Crew."""
deploy_cmd.deploy(uuid=uuid)
@deploy.command(name="status")
@click.option("-u", "--uuid", type=str, help="Crew UUID parameter")
def deply_status(uuid: Optional[str]):
@@ -224,11 +231,5 @@ def deploy_remove(uuid: Optional[str]):
deploy_cmd.remove_crew(uuid=uuid)
@deploy.command(name="signup")
def signup():
"""Sign up for a deployment."""
deploy_cmd.signup()
if __name__ == "__main__":
crewai()

View File

@@ -8,8 +8,9 @@ class CrewAPI:
CrewAPI class to interact with the crewAI+ API.
"""
CREW_BASE_URL = getenv("BASE_URL", "http://localhost:3000/crewai_plus/api/v1/crews")
MAIN_BASE_URL = getenv("MAIN_BASE_URL", "http://localhost:3000/crewai_plus/api/v1")
CREWAI_BASE_URL = getenv(
"CREWAI_BASE_URL", "http://localhost:3000/crewai_plus/api/v1/crews"
)
def __init__(self, api_key: str) -> None:
self.api_key = api_key
@@ -18,24 +19,25 @@ class CrewAPI:
"Content-Type": "application/json",
}
def _make_request(
self, method: str, endpoint: str, base_url: str = CREW_BASE_URL, **kwargs
) -> requests.Response:
url = f"{base_url}/{endpoint}"
def _make_request(self, method: str, endpoint: str, **kwargs) -> requests.Response:
url = f"{self.CREWAI_BASE_URL}/{endpoint}"
return requests.request(method, url, headers=self.headers, **kwargs)
# Deploy
def deploy_by_name(self, project_name: str) -> requests.Response:
return self._make_request("POST", f"by-name/{project_name}/deploy")
def deploy_by_uuid(self, uuid: str) -> requests.Response:
return self._make_request("POST", f"{uuid}/deploy")
# Status
def status_by_name(self, project_name: str) -> requests.Response:
return self._make_request("GET", f"by-name/{project_name}/status")
def status_by_uuid(self, uuid: str) -> requests.Response:
return self._make_request("GET", f"{uuid}/status")
# Logs
def logs_by_name(
self, project_name: str, log_type: str = "deployment"
) -> requests.Response:
@@ -46,17 +48,17 @@ class CrewAPI:
) -> requests.Response:
return self._make_request("GET", f"{uuid}/logs/{log_type}")
# Delete
def delete_by_name(self, project_name: str) -> requests.Response:
return self._make_request("DELETE", f"by-name/{project_name}")
def delete_by_uuid(self, uuid: str) -> requests.Response:
return self._make_request("DELETE", f"{uuid}")
# List
def list_crews(self) -> requests.Response:
return self._make_request("GET", "")
# Create
def create_crew(self, payload) -> requests.Response:
return self._make_request("POST", "", json=payload)
def signup(self) -> requests.Response:
return self._make_request("GET", "signup_link", base_url=self.MAIN_BASE_URL)

View File

@@ -1,50 +1,85 @@
import time
import webbrowser
from os import getenv
from typing import Optional
from typing import Any, Dict, List, Optional
import requests
from rich.console import Console
from .api import CrewAPI
from .constants import AUTH0_CLIENT_ID, AUTH0_DOMAIN
from .utils import (
fetch_and_json_env_file,
get_auth_token,
get_git_remote_url,
get_project_name,
validate_token,
)
console = Console()
class DeployCommand:
BASE_URL = getenv("BASE_URL", "http://localhost:3000/crewai_plus/api")
"""
A class to handle deployment-related operations for CrewAI projects.
"""
def __init__(self):
"""
Initialize the DeployCommand with project name and API client.
"""
self.project_name = get_project_name()
self.client = CrewAPI(api_key=get_auth_token())
def _handle_error(self, json_response: dict) -> None:
error = json_response.get("error")
message = json_response.get("message")
console.print(
f"Error: {error}",
style="bold red",
)
console.print(
f"Message: {message}",
style="bold red",
)
def _handle_error(self, json_response: Dict[str, Any]) -> None:
"""
Handle and display error messages from API responses.
Args:
json_response (Dict[str, Any]): The JSON response containing error information.
"""
error = json_response.get("error", "Unknown error")
message = json_response.get("message", "No message provided")
console.print(f"Error: {error}", style="bold red")
console.print(f"Message: {message}", style="bold red")
def _standard_no_param_error_message(self) -> None:
"""
Display a standard error message when no UUID or project name is available.
"""
console.print(
"No uuid provided, project pyproject.toml not found or with error.",
"No UUID provided, project pyproject.toml not found or with error.",
style="bold red",
)
def _display_deployment_info(self, json_response: Dict[str, Any]) -> None:
"""
Display deployment information.
Args:
json_response (Dict[str, Any]): The deployment information to display.
"""
console.print("Deploying the crew...\n", style="bold blue")
for key, value in json_response.items():
console.print(f"{key.title()}: [green]{value}[/green]")
console.print("\nTo check the status of the deployment, run:")
console.print("crewai deploy status")
console.print(" or")
console.print(f"crewai deploy status --uuid \"{json_response['uuid']}\"")
def _display_logs(self, log_messages: List[Dict[str, Any]]) -> None:
"""
Display log messages.
Args:
log_messages (List[Dict[str, Any]]): The log messages to display.
"""
for log_message in log_messages:
console.print(
f"{log_message['timestamp']} - {log_message['level']}: {log_message['message']}"
)
def deploy(self, uuid: Optional[str] = None) -> None:
"""
Deploy a crew using either UUID or project name.
Args:
uuid (Optional[str]): The UUID of the crew to deploy.
"""
console.print("Starting deployment...", style="bold blue")
if uuid:
response = self.client.deploy_by_uuid(uuid)
@@ -56,29 +91,54 @@ class DeployCommand:
json_response = response.json()
if response.status_code == 200:
console.print("Deploying the crew...\n", style="bold blue")
for key, value in json_response.items():
console.print(f"{key.title()}: [green]{value}[/green]")
console.print("\nTo check the status of the deployment, run:")
console.print("crewai deploy status")
console.print(" or")
console.print(f"crewai deploy status --uuid \"{json_response['uuid']}\"")
self._display_deployment_info(json_response)
else:
self._handle_error(json_response)
def create_crew(self) -> None:
"""
Create a new crew deployment.
"""
console.print("Creating deployment...", style="bold blue")
env_vars = fetch_and_json_env_file()
remote_repo_url = get_git_remote_url()
self._confirm_input(env_vars, remote_repo_url)
payload = self._create_payload(remote_repo_url, env_vars)
response = self.client.create_crew(payload)
if response.status_code == 201:
self._display_creation_success(response.json())
else:
self._handle_error(response.json())
def _confirm_input(self, env_vars: Dict[str, str], remote_repo_url: str) -> None:
"""
Confirm input parameters with the user.
Args:
env_vars (Dict[str, str]): Environment variables.
remote_repo_url (str): Remote repository URL.
"""
input(f"Press Enter to continue with the following Env vars: {env_vars}")
input(
f"Press Enter to continue with the following remote repository: {remote_repo_url}\n"
)
payload = {
def _create_payload(
self, remote_repo_url: str, env_vars: Dict[str, str]
) -> Dict[str, Any]:
"""
Create the payload for crew creation.
Args:
remote_repo_url (str): Remote repository URL.
env_vars (Dict[str, str]): Environment variables.
Returns:
Dict[str, Any]: The payload for crew creation.
"""
return {
"deploy": {
"name": self.project_name,
"repo_clone_url": remote_repo_url,
@@ -86,39 +146,62 @@ class DeployCommand:
}
}
response = self.client.create_crew(payload)
if response.status_code == 201:
json_response = response.json()
console.print("Deployment created successfully!\n", style="bold green")
console.print(
f"Name: {self.project_name} ({json_response['uuid']})",
style="bold green",
)
console.print(f"Status: {json_response['status']}", style="bold green")
console.print("\nTo (re)deploy the crew, run:")
console.print("crewai deploy up")
console.print(" or")
console.print(f"crewai deploy --uuid {json_response['uuid']}")
else:
self._handle_error(response.json())
def _display_creation_success(self, json_response: Dict[str, Any]) -> None:
"""
Display success message after crew creation.
Args:
json_response (Dict[str, Any]): The response containing crew information.
"""
console.print("Deployment created successfully!\n", style="bold green")
console.print(
f"Name: {self.project_name} ({json_response['uuid']})", style="bold green"
)
console.print(f"Status: {json_response['status']}", style="bold green")
console.print("\nTo (re)deploy the crew, run:")
console.print("crewai deploy push")
console.print(" or")
console.print(f"crewai deploy push --uuid {json_response['uuid']}")
def list_crews(self) -> None:
"""
List all available crews.
"""
console.print("Listing all Crews\n", style="bold blue")
response = self.client.list_crews()
json_response = response.json()
if response.status_code == 200:
for crew_data in json_response:
console.print(
f"- {crew_data['name']} ({crew_data['uuid']}) [blue]{crew_data['status']}[/blue]"
)
self._display_crews(json_response)
else:
self._display_no_crews_message()
def _display_crews(self, crews_data: List[Dict[str, Any]]) -> None:
"""
Display the list of crews.
Args:
crews_data (List[Dict[str, Any]]): List of crew data to display.
"""
for crew_data in crews_data:
console.print(
"You don't have any crews yet. Let's create one!", style="yellow"
f"- {crew_data['name']} ({crew_data['uuid']}) [blue]{crew_data['status']}[/blue]"
)
console.print(" [green]crewai create --name [name][/green]")
def _display_no_crews_message(self) -> None:
"""
Display a message when no crews are available.
"""
console.print("You don't have any Crews yet. Let's create one!", style="yellow")
console.print(" crewai create crew <crew_name>", style="green")
def get_crew_status(self, uuid: Optional[str] = None) -> None:
"""
Get the status of a crew.
Args:
uuid (Optional[str]): The UUID of the crew to check.
"""
console.print("Fetching deployment status...", style="bold blue")
if uuid:
response = self.client.status_by_uuid(uuid)
@@ -130,16 +213,29 @@ class DeployCommand:
json_response = response.json()
if response.status_code == 200:
console.print(f"Name:\t {json_response['name']}")
console.print(f"Status:\t {json_response['status']}")
self._display_crew_status(json_response)
else:
self._handle_error(json_response)
def get_crew_logs(
self, uuid: Optional[str], log_type: str = "dExacployment"
) -> None:
console.print(f"Getting {log_type} logs...", style="bold blue")
def _display_crew_status(self, status_data: Dict[str, str]) -> None:
"""
Display the status of a crew.
Args:
status_data (Dict[str, str]): The status data to display.
"""
console.print(f"Name:\t {status_data['name']}")
console.print(f"Status:\t {status_data['status']}")
def get_crew_logs(self, uuid: Optional[str], log_type: str = "deployment") -> None:
"""
Get logs for a crew.
Args:
uuid (Optional[str]): The UUID of the crew to get logs for.
log_type (str): The type of logs to retrieve (default: "deployment").
"""
console.print(f"Fetching {log_type} logs...", style="bold blue")
if uuid:
response = self.client.logs_by_uuid(uuid, log_type)
@@ -150,15 +246,17 @@ class DeployCommand:
return
if response.status_code == 200:
log_messages = response.json()
for log_message in log_messages:
console.print(
f"{log_message['timestamp']} - {log_message['level']}: {log_message['message']}"
)
self._display_logs(response.json())
else:
console.print(response.text, style="bold red")
self._handle_error(response.json())
def remove_crew(self, uuid: Optional[str]) -> None:
"""
Remove a crew deployment.
Args:
uuid (Optional[str]): The UUID of the crew to remove.
"""
console.print("Removing deployment...", style="bold blue")
if uuid:
@@ -177,59 +275,3 @@ class DeployCommand:
console.print(
f"Failed to remove crew '{self.project_name}'", style="bold red"
)
def signup(self) -> None:
console.print("Signing Up", style="bold blue")
device_code_payload = {
"client_id": AUTH0_CLIENT_ID,
"scope": "openid profile email",
"audience": "https://dev-jzsr0j8zs0atl5ha.us.auth0.com/api/v2/",
}
device_code_response = requests.post(
f"https://{AUTH0_DOMAIN}/oauth/device/code",
data=device_code_payload,
)
if device_code_response.status_code != 200:
console.print("Error generating the device code")
raise
device_code_data = device_code_response.json()
console.print(
"1. Navigate to: ",
device_code_data["verification_uri_complete"],
)
console.print("2. Enter the following code: ", device_code_data["user_code"])
webbrowser.open(device_code_data["verification_uri_complete"])
token_payload = {
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
"device_code": device_code_data["device_code"],
"client_id": AUTH0_CLIENT_ID,
}
authenticated = False
while not authenticated:
token_response = requests.post(
f"https://{AUTH0_DOMAIN}/oauth/token", data=token_payload
)
token_data = token_response.json()
if token_response.status_code == 200:
validate_token(token_data["id_token"])
# current_user = jwt.decode(
# token_data["id_token"],
# algorithms=ALGORITHMS,
# options={"verify_signature": False},
# )
authenticated = True
console.print("\nWelcome to CrewAI+ !!", style="green")
elif token_data["error"] not in ("authorization_pending", "slow_down"):
console.print(token_data["error_description"])
raise
else:
time.sleep(device_code_data["interval"])

View File

@@ -2,12 +2,6 @@ import re
import subprocess
import tomllib
from auth0.authentication.token_verifier import (
AsymmetricSignatureVerifier,
TokenVerifier,
)
from .constants import AUTH0_CLIENT_ID, AUTH0_DOMAIN
def get_git_remote_url():
@@ -87,20 +81,5 @@ def fetch_and_json_env_file(env_file_path: str = ".env") -> dict:
return {}
def validate_token(id_token: str) -> None:
"""
Verify the token and its precedence
:param id_token:
"""
jwks_url = f"https://{AUTH0_DOMAIN}/.well-known/jwks.json"
issuer = f"https://{AUTH0_DOMAIN}/"
signature_verifier = AsymmetricSignatureVerifier(jwks_url)
token_verifier = TokenVerifier(
signature_verifier=signature_verifier, issuer=issuer, audience=AUTH0_CLIENT_ID
)
token_verifier.verify(id_token)
def get_auth_token():
return ""
return "<token>"