Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: initialize ai email #774

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 181 additions & 0 deletions shinkai-bin/shinkai-node/src/utils/email.rs
Original file line number Diff line number Diff line change
@@ -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<ChallengeResponse, Box<dyn std::error::Error>> {
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<Client, Box<dyn std::error::Error>> {
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<String, Box<dyn std::error::Error>> {
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<String, Box<dyn std::error::Error>> {
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());
}
5 changes: 5 additions & 0 deletions shinkai-bin/shinkai-node/src/utils/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub struct NodeEnvironment {
pub default_embedding_model: EmbeddingModelType,
pub supported_embedding_models: Vec<EmbeddingModelType>,
pub api_v2_key: Option<String>,
pub shinkai_emails_api_url: Option<String>,
}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -206,6 +207,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<String> = env::var("SHINKAI_EMAILS_API_URL").ok();

NodeEnvironment {
global_identity_name,
listen_address,
Expand All @@ -225,5 +228,7 @@ pub fn fetch_node_environment() -> NodeEnvironment {
supported_embedding_models,
api_v2_key,
api_https_listen_address,
shinkai_emails_api_url,
}
}

3 changes: 2 additions & 1 deletion shinkai-bin/shinkai-node/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ pub mod keys;
pub mod logging_helpers;
pub mod printer;
pub mod qr_code_setup;
pub mod update_global_identity;
pub mod update_global_identity;
pub mod email;
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pub enum ShinkaiLogOption {
Node,
InternalAPI,
Network,
Email,
Tests,
}

Expand Down