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

Use direct call to holo-client for jurisdictions #198

Merged
merged 18 commits into from
Aug 21, 2024
Merged
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
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
Loading