diff --git a/shinkai-bin/shinkai-node/src/utils/email.rs b/shinkai-bin/shinkai-node/src/utils/email.rs new file mode 100644 index 000000000..fcf6ea024 --- /dev/null +++ b/shinkai-bin/shinkai-node/src/utils/email.rs @@ -0,0 +1,181 @@ +use ed25519_dalek::{ed25519::signature::SignerMut, SigningKey, VerifyingKey}; + +use reqwest::Client; +use serde::Deserialize; +use serde_json::json; +use shinkai_message_primitives::shinkai_utils::shinkai_logging::{shinkai_log, ShinkaiLogLevel, ShinkaiLogOption}; + +#[derive(Debug, Deserialize)] +struct ChallengeResponse { + challenge: String, +} + +#[derive(Deserialize)] +struct EmailResponse { + email: String, +} + +#[derive(Debug)] +pub struct ShinkaiEmails { + pub api_url: String, + pub verifying_key: VerifyingKey, + pub signing_key: SigningKey, +} + +impl ShinkaiEmails { + pub fn new(api_url: String, verifying_key: VerifyingKey, signing_key: SigningKey) -> Self { + ShinkaiEmails { + api_url, + verifying_key, + signing_key, + } + } + + async fn get_challenge(&self, verifying_key: &str) -> Result> { + shinkai_log( + ShinkaiLogOption::Email, + ShinkaiLogLevel::Debug, + &format!("getting challenge for verifying key: {}", verifying_key), + ); + let client = Client::new(); + let response = match client + .post(format!("{}/api/v1/challenge", self.api_url)) + .header("Content-Type", "application/json") + .json(&json!({ "verifyingKey": verifying_key })) + .send() + .await + { + Ok(response) => response, + Err(e) => { + shinkai_log( + ShinkaiLogOption::Email, + ShinkaiLogLevel::Error, + &format!("Error getting challenge: {}", e), + ); + return Err(Box::new(e)); + } + }; + + let response_body: ChallengeResponse = response.json().await?; + + shinkai_log( + ShinkaiLogOption::Email, + ShinkaiLogLevel::Debug, + &format!("successfully received challenge: {}", response_body.challenge), + ); + Ok(response_body) + } + + fn get_auth_headers( + &self, + verifying_key: &str, + challenge: &str, + signed_challenge: &str, + ) -> reqwest::header::HeaderMap { + shinkai_log( + ShinkaiLogOption::Email, + ShinkaiLogLevel::Debug, + "greating authentication headers", + ); + let mut headers = reqwest::header::HeaderMap::new(); + headers.insert("X-AUTH-CHALLENGE-VERIFYING-KEY", verifying_key.parse().unwrap()); + headers.insert("X-AUTH-CHALLENGE-CHALLENGE", challenge.parse().unwrap()); + headers.insert("X-AUTH-CHALLENGE-SIGNED-CHALLENGE", signed_challenge.parse().unwrap()); + headers + } + + async fn get_authenticated_client(&self) -> Result> { + shinkai_log( + ShinkaiLogOption::Email, + ShinkaiLogLevel::Debug, + "getting authenticated client", + ); + // Get the challenge from the server + let challenge = self + .get_challenge(&hex::encode(self.verifying_key.to_bytes())) + .await? + .challenge; + // Sign the challenge + let mut signing_key = self.signing_key.clone(); + let signed_challenge = signing_key.sign(challenge.as_bytes()); + + // Prepare the authentication headers + let headers = self.get_auth_headers( + &hex::encode(self.verifying_key.to_bytes()), + &challenge, + &hex::encode(signed_challenge.to_bytes()), + ); + // Send the authentication request + let client = Client::builder().default_headers(headers).build()?; + shinkai_log( + ShinkaiLogOption::Email, + ShinkaiLogLevel::Debug, + "successfully created authenticated client", + ); + Ok(client) + } + + async fn create_email(&self, password: &str) -> Result> { + shinkai_log(ShinkaiLogOption::Email, ShinkaiLogLevel::Info, "creating new email"); + let client = self.get_authenticated_client().await?; + let response = match client + .post(format!("{}/api/v1/email", self.api_url)) + .header("Content-Type", "application/json") + .json(&json!({ "password": password })) + .send() + .await + { + Ok(response) => response, + Err(e) => { + shinkai_log( + ShinkaiLogOption::Email, + ShinkaiLogLevel::Error, + &format!("failed to create email: {}", e), + ); + return Err(Box::new(e)); + } + }; + + let response_body: EmailResponse = response.json().await?; + shinkai_log( + ShinkaiLogOption::Email, + ShinkaiLogLevel::Info, + &format!("successfully created email: {}", response_body.email), + ); + Ok(response_body.email) + } + + pub async fn initialize_email(&self, password: &str) -> Result> { + shinkai_log(ShinkaiLogOption::Email, ShinkaiLogLevel::Info, "initializing email"); + let email = self.create_email(password).await?; + shinkai_log( + ShinkaiLogOption::Email, + ShinkaiLogLevel::Info, + &format!("successfully initialized email: {}", email), + ); + Ok(email) + } +} + +#[tokio::test] +async fn initialize_email() { + let secret_key_bytes: [u8; 32] = hex::decode("209dd64296d9b3673b9e07c64d0047f0977372a4a60f0a80fe6f3f381bf49178") + .unwrap() + .try_into() + .unwrap(); + let secret_key = SigningKey::from_bytes(&secret_key_bytes); + let public_key = secret_key.verifying_key(); + println!("secret key: {}", hex::encode(secret_key.to_bytes())); + println!("public key: {}", hex::encode(public_key.to_bytes())); + + let email = ShinkaiEmails::new( + "https://shinkai-emails-302883622007.us-central1.run.app".to_string(), + public_key, + secret_key, + ) + .initialize_email("passwordpassword") + .await; + println!("email: {:?}", email); + assert!(email.is_ok()); + println!("email: {}", email.unwrap()); +} diff --git a/shinkai-bin/shinkai-node/src/utils/environment.rs b/shinkai-bin/shinkai-node/src/utils/environment.rs index 5d57edc51..c80d2df81 100644 --- a/shinkai-bin/shinkai-node/src/utils/environment.rs +++ b/shinkai-bin/shinkai-node/src/utils/environment.rs @@ -28,6 +28,7 @@ pub struct NodeEnvironment { pub default_embedding_model: EmbeddingModelType, pub supported_embedding_models: Vec, pub api_v2_key: Option, + pub shinkai_emails_api_url: Option, } #[derive(Debug, Clone)] @@ -199,6 +200,8 @@ pub fn fetch_node_environment() -> NodeEnvironment { let api_https_listen_address = SocketAddr::new(api_ip, api_https_port); + let shinkai_emails_api_url: Option = env::var("SHINKAI_EMAILS_API_URL").ok(); + NodeEnvironment { global_identity_name, listen_address, @@ -218,5 +221,7 @@ pub fn fetch_node_environment() -> NodeEnvironment { supported_embedding_models, api_v2_key, api_https_listen_address, + shinkai_emails_api_url, } } + diff --git a/shinkai-bin/shinkai-node/src/utils/mod.rs b/shinkai-bin/shinkai-node/src/utils/mod.rs index 0feb949d7..5cdfd7474 100644 --- a/shinkai-bin/shinkai-node/src/utils/mod.rs +++ b/shinkai-bin/shinkai-node/src/utils/mod.rs @@ -5,4 +5,5 @@ pub mod keys; pub mod logging_helpers; pub mod printer; pub mod qr_code_setup; -pub mod update_global_identity; \ No newline at end of file +pub mod update_global_identity; +pub mod email; diff --git a/shinkai-libs/shinkai-message-primitives/src/shinkai_utils/shinkai_logging.rs b/shinkai-libs/shinkai-message-primitives/src/shinkai_utils/shinkai_logging.rs index 4d88eb09a..796078da1 100644 --- a/shinkai-libs/shinkai-message-primitives/src/shinkai_utils/shinkai_logging.rs +++ b/shinkai-libs/shinkai-message-primitives/src/shinkai_utils/shinkai_logging.rs @@ -37,6 +37,7 @@ pub enum ShinkaiLogOption { Node, InternalAPI, Network, + Email, Tests, }