From aadc67b18cc1e0075f35e8a31887bb14b5eaa390 Mon Sep 17 00:00:00 2001 From: Rohit Sarpotdar Date: Fri, 16 Feb 2024 12:45:34 +0530 Subject: [PATCH] added user metadata rest endpoints --- src/auth/generate.rs | 34 ++++++++++++-- src/auth/identity.rs | 9 +--- src/main.rs | 16 ++++++- src/metadata/mod.rs | 1 + src/metadata/user.rs | 102 ++++++++++++++++++++++++++++++++++++++++ src/store/cloudflare.rs | 2 +- 6 files changed, 148 insertions(+), 16 deletions(-) create mode 100644 src/metadata/mod.rs create mode 100644 src/metadata/user.rs diff --git a/src/auth/generate.rs b/src/auth/generate.rs index 3b2c8d6..76557d3 100644 --- a/src/auth/generate.rs +++ b/src/auth/generate.rs @@ -4,6 +4,7 @@ use k256::SecretKey; use rand::distributions::Alphanumeric; use rand::Rng; use sec1::LineEnding::CRLF; +use tracing::log::warn; pub fn key_pair() -> Result { let mnemonic = Mnemonic::new(MnemonicType::for_key_size(256).unwrap(), Language::English); @@ -48,11 +49,34 @@ fn get_random_string() -> String { .collect() } -pub fn to_hex_string(bytes: Vec) -> String { - bytes.iter().fold(String::new(), |mut acc, &byte| { - acc.push_str(&format!("{:02x}", byte)); - acc - }) +pub fn to_hex_string(number: u64) -> String { + number + .to_be_bytes() + .iter() + .fold(String::new(), |mut acc, &byte| { + acc.push_str(&format!("{:02x}", byte)); + acc + }) +} + +pub fn from_hex_string(hex_string: &str) -> Result { + let mut iter = hex_string.chars(); + let mut byte_arr = Vec::with_capacity(hex_string.len()); + + while let Some(c1) = iter.next() { + let word8 = iter + .next() + .map(|c2| u8::from_str_radix(&format!("{}{}", c1, c2), 16).unwrap()) + .unwrap(); + byte_arr.push(word8); + } + match byte_arr.as_slice().try_into() { + Ok(b) => Ok(u64::from_be_bytes(b)), + Err(error) => { + warn!("{}", error); + Err("Could not convert to u64".to_owned()) + } + } } #[derive(Default, Clone)] diff --git a/src/auth/identity.rs b/src/auth/identity.rs index 94a8a0e..aee4c2c 100644 --- a/src/auth/identity.rs +++ b/src/auth/identity.rs @@ -87,7 +87,6 @@ pub async fn get_session_response( let expiration = Utc::now() + Duration::days(30); let expiration = expiration.timestamp_nanos_opt().unwrap().unsigned_abs(); - // delegation let delegation = Delegation { pubkey: client_temp_identity.public_key().unwrap(), expiration, @@ -123,13 +122,7 @@ pub async fn get_session_response( _delegation: agent_js::DelegationChain { delegations: vec![agent_js::SignedDelegation { delegation: agent_js::Delegation { - expiration: generate::to_hex_string( - signed_delegation - .delegation - .expiration - .to_be_bytes() - .to_vec(), - ), + expiration: generate::to_hex_string(signed_delegation.delegation.expiration), pubkey: signed_delegation.delegation.pubkey, targets: None, }, diff --git a/src/main.rs b/src/main.rs index 2d9c327..44a4d63 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ mod auth; mod error_template; mod fileserve; mod init; +mod metadata; mod page; mod providers; mod store; @@ -54,8 +55,17 @@ mod handlers { #[cfg(feature = "ssr")] #[tokio::main] async fn main() { - use crate::{app::App, auth::identity, fileserve::file_and_error_handler, init}; - use axum::{routing::get, Router}; + use crate::{ + app::App, + auth::identity, + fileserve::file_and_error_handler, + init, + metadata::user::{get_user_canister, update_user_metadata}, + }; + use axum::{ + routing::{get, post}, + Router, + }; use axum_extra::extract::cookie::Key; use handlers::*; use leptos::*; @@ -90,6 +100,8 @@ async fn main() { "/api/*fn_name", get(server_fn_handler).post(server_fn_handler), ) + .route("/rest_api/update_user_metadata", post(update_user_metadata)) + .route("/rest_api/get_user_canister", post(get_user_canister)) .leptos_routes_with_handler(routes, get(leptos_routes_handler)) .fallback(file_and_error_handler) .layer(service) diff --git a/src/metadata/mod.rs b/src/metadata/mod.rs new file mode 100644 index 0000000..22d12a3 --- /dev/null +++ b/src/metadata/mod.rs @@ -0,0 +1 @@ +pub mod user; diff --git a/src/metadata/user.rs b/src/metadata/user.rs new file mode 100644 index 0000000..d3b5c20 --- /dev/null +++ b/src/metadata/user.rs @@ -0,0 +1,102 @@ +use crate::{ + auth::{agent_js, generate::from_hex_string, identity::AppState}, + store::cloudflare::{read_metadata, write_kv}, +}; +use axum::{extract::State, Json}; +use ic_agent::{ + identity::{DelegatedIdentity, Delegation, Secp256k1Identity, SignedDelegation}, + Identity, +}; +use k256::SecretKey; +use serde::Deserialize; +use std::collections::HashMap; +use tracing::log::info; + +pub async fn update_user_metadata( + State(app_state): State, + Json(user_details): Json, +) -> Result { + let delegated_identity: DelegatedIdentity = + user_details.delegation_identity.try_into().unwrap(); + let user_principal_id = delegated_identity.sender().unwrap(); + let user_pubkey = delegated_identity.public_key().unwrap(); + let mut metadata: HashMap<&str, &str> = HashMap::with_capacity(2); + metadata.insert("user_canister_id", &user_details.user_canister_id); + metadata.insert("user_name", &user_details.user_name); + + let _ignore = write_kv( + &user_principal_id.to_text(), + "", + metadata, + &app_state.cloudflare_config, + ) + .await; + + Ok("Successful".to_owned()) +} + +pub async fn get_user_canister( + State(app_state): State, + Json(user_principal_id): Json, +) -> Result { + info!("{}", user_principal_id); + + match read_metadata(&user_principal_id, &app_state.cloudflare_config).await { + Some(user_metadata) => { + let user_canister_id = match user_metadata.get("user_canister_id") { + Some(c) => c, + None => return Err("User details not found".to_owned()), + }; + Ok(user_canister_id.to_owned()) + } + None => Err("User not found".to_owned()), + } +} + +async fn verify_signature(signature: String) -> bool { + true +} + +impl TryFrom for DelegatedIdentity { + type Error = String; + + fn try_from(delegation_identity: agent_js::DelegationIdentity) -> Result { + info!("DelegationIdentity into"); + let js_signed_delegation = delegation_identity._delegation.delegations; + if js_signed_delegation.len() == 0 { + return Err("No signed delegations found".to_owned()); + } + let js_signed_delegation = js_signed_delegation.get(0).unwrap(); + let js_delegation = js_signed_delegation.delegation.to_owned(); + let exp1 = match from_hex_string(&js_delegation.expiration) { + Ok(val) => val, + Err(error) => return Err(error), + }; + + let delegation = Delegation { + pubkey: js_delegation.pubkey, + expiration: exp1, + targets: None, + }; + let signed_delegation = SignedDelegation { + delegation, + signature: js_signed_delegation.signature.to_owned(), + }; + + let secret_key = SecretKey::from_slice(delegation_identity._inner[1].as_slice()).unwrap(); + let client_temp_identity = Secp256k1Identity::from_private_key(secret_key); + let delegated_identity = DelegatedIdentity::new( + delegation_identity._delegation.public_key, + Box::new(client_temp_identity), + vec![signed_delegation], + ); + Ok(delegated_identity) + } +} + +#[derive(Deserialize, Debug)] +pub struct UserDetails { + delegation_identity: agent_js::DelegationIdentity, + user_canister_id: String, + user_name: String, +} diff --git a/src/store/cloudflare.rs b/src/store/cloudflare.rs index 31625aa..dbca9e2 100644 --- a/src/store/cloudflare.rs +++ b/src/store/cloudflare.rs @@ -41,7 +41,7 @@ pub async fn read_metadata( } }, Err(error) => { - error!("Error read_metadata: {}", error); + warn!("Error read_metadata: {}", error); None } }