diff --git a/keyserver/addons/rust-node-addon/src/identity_client/config.rs b/keyserver/addons/rust-node-addon/src/identity_client/config.rs new file mode 100644 index 000000000..eb42b7e7d --- /dev/null +++ b/keyserver/addons/rust-node-addon/src/identity_client/config.rs @@ -0,0 +1,77 @@ +use super::IdentityServiceConfig; +use serde::Deserialize; +use serde_json; +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use tracing::warn; + +pub(super) fn get_identity_service_config( +) -> Result> { + const IDENTITY_SERVICE_CONFIG_NAME: ConfigName = ConfigName { + folder: ConfigFolder::Secrets, + name: "identity_service_config", + }; + let config = get_config(&IDENTITY_SERVICE_CONFIG_NAME)?; + Ok(config) +} + +#[derive(Debug)] +enum ConfigFolder { + Secrets, + #[allow(dead_code)] + Facts, +} + +impl ConfigFolder { + fn as_str(&self) -> &'static str { + match self { + ConfigFolder::Secrets => "secrets", + ConfigFolder::Facts => "facts", + } + } +} + +struct ConfigName { + folder: ConfigFolder, + name: &'static str, +} + +impl ConfigName { + fn get_key_for_config_name(&self) -> String { + format!("COMM_JSONCONFIG_{}_{}", self.folder.as_str(), self.name) + } + + fn get_path_for_config_name(&self) -> PathBuf { + Path::new(self.folder.as_str()).join(format!("{}.json", self.name)) + } +} + +fn get_config Deserialize<'de>>( + config_name: &ConfigName, +) -> Result> { + let key = config_name.get_key_for_config_name(); + + match env::var(&key) { + Ok(env_value) => match serde_json::from_str::(&env_value) { + Ok(config) => return Ok(config), + Err(e) => { + warn!("Failed to deserialize env value '{}': {}", env_value, e); + } + }, + Err(e) => { + warn!("Failed to read config from env var '{}': {}", key, e); + } + } + + let path = config_name.get_path_for_config_name(); + let file_content = fs::read_to_string(&path).map_err(|e| { + warn!("Failed to read config file '{}': {}", path.display(), e); + e + })?; + + serde_json::from_str::(&file_content).map_err(|e| { + warn!("Failed to deserialize file content: {}", e); + e.into() + }) +} diff --git a/keyserver/addons/rust-node-addon/src/identity_client/mod.rs b/keyserver/addons/rust-node-addon/src/identity_client/mod.rs index d23466885..c39a2e67e 100644 --- a/keyserver/addons/rust-node-addon/src/identity_client/mod.rs +++ b/keyserver/addons/rust-node-addon/src/identity_client/mod.rs @@ -1,213 +1,211 @@ pub mod add_reserved_usernames; +pub mod config; pub mod get_inbound_keys_for_user; pub mod login; pub mod prekey; pub mod register_user; pub mod remove_reserved_usernames; pub mod upload_one_time_keys; use client_proto::identity_client_service_client::IdentityClientServiceClient; use client_proto::{ AddReservedUsernamesRequest, DeviceKeyUpload, DeviceType, IdentityKeyInfo, PreKey, RegistrationFinishRequest, RegistrationStartRequest, RemoveReservedUsernameRequest, }; +use config::get_identity_service_config; +use generated::CODE_VERSION; use grpc_clients::identity::authenticated::ChainedInterceptedAuthClient; use grpc_clients::identity::protos::authenticated::{ InboundKeyInfo, UploadOneTimeKeysRequest, }; use grpc_clients::identity::protos::unauthenticated as client_proto; use grpc_clients::identity::shared::CodeVersionLayer; use grpc_clients::identity::{ REQUEST_METADATA_COOKIE_KEY, RESPONSE_METADATA_COOKIE_KEY, }; use lazy_static::lazy_static; use napi::bindgen_prelude::*; use serde::{Deserialize, Serialize}; use std::env::var; use tonic::codegen::InterceptedService; use tonic::{transport::Channel, Request}; use tracing::{self, info, instrument, warn, Level}; use tracing_subscriber::EnvFilter; mod generated { // We get the CODE_VERSION from this generated file include!(concat!(env!("OUT_DIR"), "/version.rs")); } -pub use generated::CODE_VERSION; -pub const DEVICE_TYPE: &str = "keyserver"; +const DEVICE_TYPE: &str = "keyserver"; const ENV_NODE_ENV: &str = "NODE_ENV"; const ENV_DEVELOPMENT: &str = "development"; lazy_static! { static ref IDENTITY_SERVICE_CONFIG: IdentityServiceConfig = { let filter = EnvFilter::builder() .with_default_directive(Level::INFO.into()) .with_env_var(EnvFilter::DEFAULT_ENV) .from_env_lossy(); let subscriber = tracing_subscriber::fmt().with_env_filter(filter).finish(); tracing::subscriber::set_global_default(subscriber) .expect("Unable to configure tracing"); - let config_json_string = - var("COMM_JSONCONFIG_secrets_identity_service_config"); - match config_json_string { - Ok(json) => serde_json::from_str(&json).unwrap(), - Err(_) => IdentityServiceConfig::default(), - } + get_identity_service_config() + .unwrap_or_else(|_| IdentityServiceConfig::default()) }; } #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct IdentityServiceConfig { identity_socket_addr: String, } impl Default for IdentityServiceConfig { fn default() -> Self { info!("Using default identity configuration based on NODE_ENV"); const DEV_SOCKET_ADDR: &str = "https://identity.staging.commtechnologies.org:50054"; const PROD_SOCKET_ADDR: &str = "https://identity.commtechnologies.org:50054"; let default_socket_addr = match var(ENV_NODE_ENV) { Ok(val) if val == ENV_DEVELOPMENT => DEV_SOCKET_ADDR, _ => PROD_SOCKET_ADDR, } .to_string(); Self { identity_socket_addr: default_socket_addr, } } } async fn get_identity_client() -> Result< IdentityClientServiceClient>, > { info!("Connecting to identity service"); grpc_clients::identity::get_unauthenticated_client( &IDENTITY_SERVICE_CONFIG.identity_socket_addr, CODE_VERSION, DEVICE_TYPE.to_string(), ) .await .map_err(|_| { Error::new( Status::GenericFailure, "Unable to connect to identity service".to_string(), ) }) } async fn get_authenticated_identity_client( user_id: String, device_id: String, access_token: String, ) -> Result { info!("Connecting to identity service"); grpc_clients::identity::get_auth_client( &IDENTITY_SERVICE_CONFIG.identity_socket_addr, user_id, device_id, access_token, CODE_VERSION, DEVICE_TYPE.to_string(), ) .await .map_err(|_| { Error::new( Status::GenericFailure, "Unable to connect to identity service".to_string(), ) }) } #[napi(object)] pub struct SignedIdentityKeysBlob { pub payload: String, pub signature: String, } #[napi(object)] pub struct UserLoginInfo { pub user_id: String, pub access_token: String, } #[napi(object)] pub struct InboundKeyInfoResponse { pub payload: String, pub payload_signature: String, pub social_proof: Option, pub content_prekey: String, pub content_prekey_signature: String, pub notif_prekey: String, pub notif_prekey_signature: String, } impl TryFrom for InboundKeyInfoResponse { type Error = Error; fn try_from(key_info: InboundKeyInfo) -> Result { let identity_info = key_info .identity_info .ok_or(Error::from_status(Status::GenericFailure))?; let IdentityKeyInfo { payload, payload_signature, social_proof, } = identity_info; let content_prekey = key_info .content_prekey .ok_or(Error::from_status(Status::GenericFailure))?; let PreKey { pre_key: content_prekey_value, pre_key_signature: content_prekey_signature, } = content_prekey; let notif_prekey = key_info .notif_prekey .ok_or(Error::from_status(Status::GenericFailure))?; let PreKey { pre_key: notif_prekey_value, pre_key_signature: notif_prekey_signature, } = notif_prekey; Ok(Self { payload, payload_signature, social_proof, content_prekey: content_prekey_value, content_prekey_signature, notif_prekey: notif_prekey_value, notif_prekey_signature, }) } } pub fn handle_grpc_error(error: tonic::Status) -> napi::Error { warn!("Received error: {}", error.message()); Error::new(Status::GenericFailure, error.message()) } #[cfg(test)] mod tests { use super::CODE_VERSION; #[test] fn test_code_version_exists() { assert!(CODE_VERSION > 0); } }