Skip to content

Commit

Permalink
Use direct call to holo-client for jurisdictions (#198)
Browse files Browse the repository at this point in the history
Replace hpos-api call with direct call to holo-client for jurisdictions

> NB: This PR is associated with holo-nixpkgs pr #2255 and should be
merged in once it is, to avoid any regressions.

---------

Co-authored-by: Zeeshan Abid <[email protected]>
  • Loading branch information
JettTech and zeeshan595 authored Aug 21, 2024
1 parent d98152f commit 5b169bd
Show file tree
Hide file tree
Showing 9 changed files with 255 additions and 45 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ tmp
.cargo
logs
.vscode
.env
35 changes: 33 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions crates/configure-holochain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ holo_happ_manager = { version = "0.1.0", path = "../holo_happ_manager" }
hpos_hc_connect = { path = "../hpos_connect_hc" }
hpos-config-core = { workspace = true }
holochain_types = { workspace = true }
reqwest = { workspace = true }

[dev-dependencies]
test-case = "2.2.2"
serial_test = { version = "1.0.0", features = ["async"] }
holochain_env_setup = { path = "../holochain_env_setup" }
dotenv = "0.15.0"
env_logger = "0.10.0"
150 changes: 127 additions & 23 deletions crates/configure-holochain/src/jurisdictions.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,15 @@
use anyhow::Result;
use anyhow::{anyhow, Context, Result};
use holochain_types::{
dna::AgentPubKey,
prelude::{FunctionName, ZomeName},
prelude::{ExternIO, FunctionName, Timestamp, ZomeName},
};
use hpos_hc_connect::{
app_connection::CoreAppRoleName, hha_agent::CoreAppAgent, holo_config::Config,
host_keys::HostKeys,
};
use reqwest::{Client, Response};
use serde::{Deserialize, Serialize};
use std::process::{Command, Output};

#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct HostingCriteria {
id: String,
jurisdiction: String,
kyc: String,
}

pub async fn get_jurisdiction() -> Result<String> {
let output: Output = Command::new("hpos-holochain-client")
.args(["--url=http://localhost/api/v2/", "hosting-criteria"])
.output()?;

let output_str = String::from_utf8_lossy(&output.stdout).to_string();

let hosting_criteria: HostingCriteria = serde_json::from_str(&output_str)?;

Ok(hosting_criteria.jurisdiction)
}
use tracing::{debug, trace};

pub async fn update_jurisdiction_if_changed(
config: &Config,
Expand Down Expand Up @@ -70,3 +52,125 @@ pub async fn update_jurisdiction_if_changed(

Ok(())
}

#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct RegistrationRecord {
pub id: String,
email: String,
pub access_token: String,
permissions: Vec<String>,
pub kyc: String,
pub jurisdiction: String,
public_key: String,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct HoloClientPayload {
pub email: String,
pub timestamp: u64,
pub pub_key: String,
}

#[derive(Debug, Clone)]
pub struct HbsClient {
hbs_url: String,
keys: HostKeys,
}
impl HbsClient {
pub async fn connect() -> Result<Self> {
let hbs_url =
std::env::var("HBS_URL").context("Failed to read HBS_URL. Is it set in env?")?;
// Creates a keypair that contains email from config, pubkey to_holochain_encoded_agent_key and signing_key
let keys = HostKeys::new().await?;
Ok(Self { hbs_url, keys })
}

/// Handles post request to HBS server under /auth/api/v1/holo-client path
/// Creates signature from agent's key that is verified by HBS
/// Returns the host's registration record
pub async fn get_host_registration(&self) -> Result<RegistrationRecord> {
// Extracts email
let email = self.keys.email.clone();

// Extracts host pub key
let pub_key = self.keys.pubkey_base36.clone();

// Formats timestamp to the one with milisecs
let now = Timestamp::now().as_seconds_and_nanos();
let timestamp: u64 = <i64 as TryInto<u64>>::try_into(now.0 * 1000).unwrap()
+ <u32 as Into<u64>>::into(now.1 / 1_000_000);

let payload = HoloClientPayload {
email,
timestamp,
pub_key,
};
trace!("HBS `holo-client` payload: {:?}", payload);

// Msgpack encodes payload
let encoded_payload = ExternIO::encode(&payload)?;

// Signs encoded bytes
let signature = self.keys.sign(encoded_payload).await?;

let mut response = self
.call_holo_client(payload.clone(), signature.clone())
.await?;
debug!("HBS Response: {:?}", response);
response = response.error_for_status()?;
let mut body = response.text().await?;

// 504 Gateway Timeout
// here we either need to retry once more or end the script
if body.contains("error code: 504") {
tracing::warn!(
"Gateway Timeout during `holo-client` call to HBS. Retrying once more..."
);
response = self.call_holo_client(payload, signature).await?;
body = response.text().await?;
if body.contains("error code: 504") {
tracing::warn!("Gateway Timeout during `holo-client` call to HBS. Exiting...");
return Err(anyhow!(
"Failed to call holo-client and fetch host jurisdiction."
));
}
}

let result: serde_json::Value = serde_json::from_str(&body)?;
let record: RegistrationRecord =
serde_json::from_value(result).context("Failed to parse response body")?;
Ok(record)
}

async fn call_holo_client(
&self,
payload: HoloClientPayload,
signature: String,
) -> Result<Response> {
let client = Client::new();
Ok(client
.post(format!("{}/auth/api/v1/holo-client", self.hbs_url))
.json(&payload)
.header("X-Signature", signature)
.send()
.await?)
}
}

#[tokio::test]
async fn get_host_registration_details_call() {
env_logger::init();
use dotenv::dotenv;
dotenv().ok();
// Point HPOS_CONFIG_PATH to test config file
std::env::set_var(
"HPOS_CONFIG_PATH",
"../holochain_env_setup/config/hp-primary-bzywj.json",
);
std::env::set_var("DEVICE_SEED_DEFAULT_PASSWORD", "pass");
std::env::set_var("HBS_URL", "https://hbs.dev.holotest.net".to_string());
let hbs = HbsClient::connect().await.unwrap();
hbs.get_host_registration().await.unwrap();
}
12 changes: 8 additions & 4 deletions crates/configure-holochain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ use hpos_hc_connect::{hpos_agent::Agent, hpos_membrane_proof};
use std::collections::HashMap;
use std::sync::Arc;
use tracing::{debug, info, instrument, warn};
pub mod jurisdictions;

mod utils;

pub mod jurisdictions;
use jurisdictions::HbsClient;

#[instrument(err, skip(config))]
pub async fn run(config: Config) -> Result<()> {
debug!("Starting configure holochain...");
Expand Down Expand Up @@ -131,10 +134,11 @@ pub async fn update_host_jurisdiction_if_changed(config: &Config) -> Result<()>
}

// get current jurisdiction in hbs
let hbs_jurisdiction = match jurisdictions::get_jurisdiction().await {
Ok(hbs_jurisdiction) => hbs_jurisdiction,
let hbs = HbsClient::connect().await?;
let hbs_jurisdiction = match hbs.get_host_registration().await {
Ok(r) => r.jurisdiction,
Err(e) => {
debug!("Failed to get jurisdiction from hbs {}", e);
debug!("Failed to get jurisdiction from hbs. Error: {}", e);
return Ok(());
}
};
Expand Down
41 changes: 41 additions & 0 deletions crates/hpos_connect_hc/src/host_keys.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use super::hpos_agent::get_signing_admin;
use anyhow::{Context, Result};
use base64::encode_config;
use ed25519_dalek::*;
use holochain_types::prelude::ExternIO;
use hpos_config_core::public_key;

#[derive(Clone, Debug)]
pub struct HostKeys {
pub email: String,
keypair: SigningKey,
pub pubkey_base36: String,
pub holoport_id: String,
}

impl HostKeys {
pub async fn new() -> Result<Self> {
let (keypair, email) = get_signing_admin().await?;
let pubkey_base36 = public_key::to_holochain_encoded_agent_key(&keypair.verifying_key());
let holoport_id = public_key::to_base36_id(&keypair.verifying_key());

Ok(Self {
email,
keypair,
pubkey_base36,
holoport_id,
})
}

pub async fn sign(&self, payload: ExternIO) -> Result<String> {
let signature = self
.keypair
.try_sign(payload.as_bytes())
.context("Failed to sign payload")?;

Ok(encode_config(
&signature.to_bytes()[..],
base64::STANDARD_NO_PAD,
))
}
}
25 changes: 25 additions & 0 deletions crates/hpos_connect_hc/src/hpos_agent.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use super::admin_ws::AdminWebsocket;
use super::hpos_membrane_proof::{delete_mem_proof_file, get_mem_proof};
use anyhow::{Context, Result};
use ed25519_dalek::*;
use holochain_types::dna::AgentPubKey;
use holochain_types::prelude::MembraneProof;
use hpos_config_core::Config;
use hpos_config_seed_bundle_explorer::unlock;
use std::{env, fs, fs::File, io::prelude::*};
use tracing::{info, instrument};

Expand Down Expand Up @@ -34,6 +36,29 @@ impl Agent {
}
}

pub async fn get_signing_admin() -> Result<(SigningKey, String)> {
let password = bundle_default_password()?;
let config_path = env::var("HPOS_CONFIG_PATH")
.context("Failed to read HPOS_CONFIG_PATH. Is it set in env?")?;
match get_hpos_config()? {
Config::V2 {
device_bundle,
settings,
..
} => {
// take in password
let signing_key = unlock(&device_bundle, Some(password))
.await
.context(format!(
"unable to unlock the device bundle from {}",
&config_path
))?;
Ok((signing_key, settings.admin.email))
}
_ => Err(AuthError::ConfigVersionError.into()),
}
}

#[derive(thiserror::Error, Debug)]
pub enum AuthError {
#[error("Error: Invalid config version used. please upgrade to hpos-config v2")]
Expand Down
1 change: 1 addition & 0 deletions crates/hpos_connect_hc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pub mod hha_agent;
pub mod hha_types;
pub mod holo_config;
pub mod holofuel_types;
pub mod host_keys;
pub mod hpos_agent;
pub mod hpos_membrane_proof;
pub mod sl_utils;
Expand Down
Loading

0 comments on commit 5b169bd

Please sign in to comment.