2022-04-30 19:26:09 +00:00
|
|
|
use std::path::{Path, PathBuf};
|
2021-04-09 00:22:17 +00:00
|
|
|
use std::str::FromStr;
|
|
|
|
|
2021-12-21 00:14:12 +00:00
|
|
|
use log::{Level as LogLevel};
|
2021-11-18 00:22:41 +00:00
|
|
|
use rsa::RsaPrivateKey;
|
2021-04-09 00:22:17 +00:00
|
|
|
use serde::{de, Deserialize, Deserializer};
|
2021-11-10 14:32:27 +00:00
|
|
|
use url::Url;
|
2021-04-09 00:22:17 +00:00
|
|
|
|
2022-05-02 23:29:04 +00:00
|
|
|
use crate::activitypub::constants::ACTOR_KEY_SUFFIX;
|
2021-11-18 00:51:56 +00:00
|
|
|
use crate::activitypub::views::get_instance_actor_url;
|
2021-04-09 00:22:17 +00:00
|
|
|
use crate::errors::ConversionError;
|
2022-01-25 22:17:28 +00:00
|
|
|
use crate::ethereum::utils::{parse_caip2_chain_id, ChainIdError};
|
2022-04-27 18:14:15 +00:00
|
|
|
use crate::models::profiles::currencies::Currency;
|
2022-04-30 19:26:09 +00:00
|
|
|
use crate::utils::crypto::{
|
|
|
|
deserialize_private_key,
|
|
|
|
generate_private_key,
|
|
|
|
serialize_private_key,
|
|
|
|
};
|
|
|
|
use crate::utils::files::{set_file_permissions, write_file};
|
2021-04-09 00:22:17 +00:00
|
|
|
|
2021-11-19 17:32:51 +00:00
|
|
|
#[derive(Clone, Debug)]
|
2021-04-09 00:22:17 +00:00
|
|
|
pub enum Environment {
|
|
|
|
Development,
|
|
|
|
Production,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FromStr for Environment {
|
|
|
|
type Err = ConversionError;
|
|
|
|
|
|
|
|
fn from_str(val: &str) -> Result<Self, Self::Err> {
|
|
|
|
let environment = match val {
|
|
|
|
"development" => Environment::Development,
|
|
|
|
"production" => Environment::Production,
|
|
|
|
_ => return Err(ConversionError),
|
|
|
|
};
|
|
|
|
Ok(environment)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn environment_from_str<'de, D>(deserializer: D) -> Result<Environment, D::Error>
|
|
|
|
where
|
|
|
|
D: Deserializer<'de>,
|
|
|
|
{
|
|
|
|
let s: String = Deserialize::deserialize(deserializer)?;
|
|
|
|
Environment::from_str(&s).map_err(de::Error::custom)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct EnvConfig {
|
|
|
|
pub environment: Option<Environment>,
|
|
|
|
pub config_path: String,
|
|
|
|
pub crate_version: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_env() -> EnvConfig {
|
|
|
|
dotenv::from_filename(".env.local").ok();
|
|
|
|
dotenv::dotenv().ok();
|
|
|
|
let environment_str = std::env::var("ENVIRONMENT").ok();
|
|
|
|
let environment = environment_str
|
|
|
|
.map(|val| Environment::from_str(&val).expect("invalid environment type"));
|
|
|
|
let config_path = std::env::var("CONFIG_PATH")
|
|
|
|
.unwrap_or("config.yaml".to_string());
|
|
|
|
let crate_version = env!("CARGO_PKG_VERSION").to_string();
|
|
|
|
EnvConfig {
|
|
|
|
environment,
|
|
|
|
config_path,
|
|
|
|
crate_version,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn default_environment() -> Environment { Environment::Development }
|
|
|
|
|
2021-12-21 00:14:12 +00:00
|
|
|
fn default_log_level() -> LogLevel { LogLevel::Info }
|
|
|
|
|
2022-02-08 21:04:28 +00:00
|
|
|
fn default_post_character_limit() -> usize { 2000 }
|
|
|
|
|
2021-04-09 00:22:17 +00:00
|
|
|
#[derive(Clone, Deserialize)]
|
2022-01-25 22:17:28 +00:00
|
|
|
pub struct BlockchainConfig {
|
|
|
|
pub chain_id: String,
|
|
|
|
pub contract_address: String,
|
|
|
|
pub contract_dir: PathBuf,
|
|
|
|
pub api_url: String,
|
|
|
|
pub explorer_url: Option<String>,
|
2021-04-09 00:22:17 +00:00
|
|
|
pub signing_key: String,
|
|
|
|
}
|
|
|
|
|
2022-01-25 22:17:28 +00:00
|
|
|
impl BlockchainConfig {
|
|
|
|
fn try_ethereum_chain_id(&self) -> Result<u32, ChainIdError> {
|
|
|
|
parse_caip2_chain_id(&self.chain_id)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn ethereum_chain_id(&self) -> u32 {
|
|
|
|
self.try_ethereum_chain_id().unwrap()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-09 00:22:17 +00:00
|
|
|
#[derive(Clone, Deserialize)]
|
|
|
|
pub struct Config {
|
|
|
|
#[serde(default = "default_environment")]
|
|
|
|
#[serde(deserialize_with = "environment_from_str")]
|
|
|
|
pub environment: Environment,
|
|
|
|
|
|
|
|
#[serde(skip)]
|
|
|
|
pub version: String,
|
|
|
|
|
|
|
|
// Core settings
|
|
|
|
pub database_url: String,
|
|
|
|
pub storage_dir: PathBuf,
|
|
|
|
|
|
|
|
pub http_host: String,
|
|
|
|
pub http_port: u32,
|
|
|
|
|
2022-02-10 23:34:58 +00:00
|
|
|
#[serde(default)]
|
|
|
|
pub http_cors_allowlist: Vec<String>,
|
|
|
|
|
2021-12-21 00:14:12 +00:00
|
|
|
#[serde(default = "default_log_level")]
|
|
|
|
pub log_level: LogLevel,
|
|
|
|
|
2021-04-09 00:22:17 +00:00
|
|
|
// Instance info
|
2021-11-10 14:32:27 +00:00
|
|
|
instance_uri: String,
|
2021-04-09 00:22:17 +00:00
|
|
|
pub instance_title: String,
|
|
|
|
pub instance_short_description: String,
|
|
|
|
pub instance_description: String,
|
2022-04-30 19:26:09 +00:00
|
|
|
|
|
|
|
#[serde(skip)]
|
|
|
|
instance_rsa_key: Option<RsaPrivateKey>,
|
2021-04-09 00:22:17 +00:00
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub registrations_open: bool, // default is false
|
|
|
|
|
2022-04-30 16:25:41 +00:00
|
|
|
// EIP-4361 login message
|
2021-04-09 00:22:17 +00:00
|
|
|
pub login_message: String,
|
|
|
|
|
2022-02-08 21:04:28 +00:00
|
|
|
#[serde(default = "default_post_character_limit")]
|
|
|
|
pub post_character_limit: usize,
|
|
|
|
|
2022-02-23 23:31:56 +00:00
|
|
|
#[serde(default)]
|
|
|
|
pub blocked_instances: Vec<String>,
|
|
|
|
|
2022-01-25 22:17:28 +00:00
|
|
|
// Blockchain integration
|
|
|
|
pub blockchain: Option<BlockchainConfig>,
|
|
|
|
|
|
|
|
// IPFS
|
2021-04-09 00:22:17 +00:00
|
|
|
pub ipfs_api_url: Option<String>,
|
|
|
|
pub ipfs_gateway_url: Option<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Config {
|
2021-11-10 14:32:27 +00:00
|
|
|
fn try_instance_url(&self) -> Result<Url, ConversionError> {
|
2021-04-09 00:22:17 +00:00
|
|
|
// TODO: allow http in production
|
|
|
|
let scheme = match self.environment {
|
|
|
|
Environment::Development => "http",
|
|
|
|
Environment::Production => "https",
|
|
|
|
};
|
|
|
|
let url_str = format!("{}://{}", scheme, self.instance_uri);
|
2021-11-10 14:32:27 +00:00
|
|
|
let url = Url::parse(&url_str).map_err(|_| ConversionError)?;
|
|
|
|
url.host().ok_or(ConversionError)?; // validates URL
|
|
|
|
Ok(url)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn instance(&self) -> Instance {
|
2021-11-18 00:22:41 +00:00
|
|
|
Instance {
|
|
|
|
_url: self.try_instance_url().unwrap(),
|
2022-02-08 19:51:40 +00:00
|
|
|
_version: self.version.clone(),
|
2022-04-30 19:26:09 +00:00
|
|
|
actor_key: self.instance_rsa_key.clone().unwrap(),
|
2021-11-19 17:32:51 +00:00
|
|
|
is_private: matches!(self.environment, Environment::Development),
|
2021-11-18 00:22:41 +00:00
|
|
|
}
|
2021-04-09 00:22:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn instance_url(&self) -> String {
|
2021-11-10 14:32:27 +00:00
|
|
|
self.instance().url()
|
2021-04-09 00:22:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn media_dir(&self) -> PathBuf {
|
|
|
|
self.storage_dir.join("media")
|
|
|
|
}
|
2022-04-27 18:14:15 +00:00
|
|
|
|
|
|
|
pub fn default_currency(&self) -> Currency {
|
|
|
|
Currency::Ethereum
|
|
|
|
}
|
2021-04-09 00:22:17 +00:00
|
|
|
}
|
|
|
|
|
2021-11-10 14:32:27 +00:00
|
|
|
pub struct Instance {
|
|
|
|
_url: Url,
|
2022-02-08 19:51:40 +00:00
|
|
|
_version: String,
|
2021-11-18 00:22:41 +00:00
|
|
|
// Instance actor
|
|
|
|
pub actor_key: RsaPrivateKey,
|
2021-11-18 14:56:52 +00:00
|
|
|
// Private instance won't send signed HTTP requests
|
|
|
|
pub is_private: bool,
|
2021-11-10 14:32:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Instance {
|
|
|
|
|
|
|
|
pub fn url(&self) -> String {
|
|
|
|
self._url.origin().ascii_serialization()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn host(&self) -> String {
|
|
|
|
self._url.host_str().unwrap().to_string()
|
|
|
|
}
|
2021-11-18 00:51:56 +00:00
|
|
|
|
|
|
|
pub fn actor_id(&self) -> String {
|
|
|
|
get_instance_actor_url(&self.url())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn actor_key_id(&self) -> String {
|
2022-05-02 23:29:04 +00:00
|
|
|
format!("{}{}", self.actor_id(), ACTOR_KEY_SUFFIX)
|
2021-11-18 00:51:56 +00:00
|
|
|
}
|
2022-02-08 19:51:40 +00:00
|
|
|
|
|
|
|
pub fn agent(&self) -> String {
|
|
|
|
format!(
|
|
|
|
"Mitra {version}; {instance_url}",
|
|
|
|
version=self._version,
|
|
|
|
instance_url=self.url(),
|
|
|
|
)
|
|
|
|
}
|
2021-11-10 14:32:27 +00:00
|
|
|
}
|
|
|
|
|
2022-04-30 19:26:09 +00:00
|
|
|
/// Generates new instance RSA key or returns existing key
|
|
|
|
fn read_instance_rsa_key(storage_dir: &Path) -> RsaPrivateKey {
|
|
|
|
let private_key_path = storage_dir.join("instance_rsa_key");
|
|
|
|
if private_key_path.exists() {
|
|
|
|
let private_key_str = std::fs::read_to_string(&private_key_path)
|
|
|
|
.expect("failed to read instance RSA key");
|
|
|
|
let private_key = deserialize_private_key(&private_key_str)
|
|
|
|
.expect("failed to read instance RSA key");
|
|
|
|
private_key
|
|
|
|
} else {
|
|
|
|
let private_key = generate_private_key()
|
|
|
|
.expect("failed to generate RSA key");
|
|
|
|
let private_key_str = serialize_private_key(&private_key)
|
|
|
|
.expect("failed to serialize RSA key");
|
|
|
|
write_file(private_key_str.as_bytes(), &private_key_path)
|
|
|
|
.expect("failed to write instance RSA key");
|
|
|
|
set_file_permissions(&private_key_path, 0o600)
|
|
|
|
.expect("failed to set permissions on RSA key file");
|
|
|
|
private_key
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-09 00:22:17 +00:00
|
|
|
pub fn parse_config() -> Config {
|
|
|
|
let env = parse_env();
|
|
|
|
let config_yaml = std::fs::read_to_string(env.config_path)
|
|
|
|
.expect("failed to load config file");
|
|
|
|
let mut config = serde_yaml::from_str::<Config>(&config_yaml)
|
|
|
|
.expect("invalid yaml data");
|
|
|
|
// Override environment parameter in config if env variable is set
|
|
|
|
config.environment = env.environment.unwrap_or(config.environment);
|
2022-04-30 19:26:09 +00:00
|
|
|
// Set version
|
2021-04-09 00:22:17 +00:00
|
|
|
config.version = env.crate_version;
|
|
|
|
// Validate config
|
|
|
|
if !config.storage_dir.exists() {
|
2021-11-05 23:41:30 +00:00
|
|
|
panic!("storage directory does not exist");
|
2021-04-09 00:22:17 +00:00
|
|
|
};
|
2022-04-30 19:26:09 +00:00
|
|
|
config.try_instance_url().expect("invalid instance URI");
|
2022-01-25 22:17:28 +00:00
|
|
|
if let Some(blockchain_config) = config.blockchain.as_ref() {
|
|
|
|
blockchain_config.try_ethereum_chain_id().unwrap();
|
|
|
|
if !blockchain_config.contract_dir.exists() {
|
2021-11-05 23:41:30 +00:00
|
|
|
panic!("contract directory does not exist");
|
|
|
|
};
|
2021-04-09 00:22:17 +00:00
|
|
|
};
|
2021-12-02 23:31:24 +00:00
|
|
|
if config.ipfs_api_url.is_some() != config.ipfs_gateway_url.is_some() {
|
|
|
|
panic!("both ipfs_api_url and ipfs_gateway_url must be set");
|
|
|
|
};
|
2021-04-09 00:22:17 +00:00
|
|
|
|
2022-04-30 19:26:09 +00:00
|
|
|
// Insert instance RSA key
|
|
|
|
config.instance_rsa_key = Some(read_instance_rsa_key(&config.storage_dir));
|
|
|
|
|
2021-04-09 00:22:17 +00:00
|
|
|
config
|
|
|
|
}
|
2021-11-10 14:32:27 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2022-04-30 19:26:09 +00:00
|
|
|
use crate::utils::crypto::generate_weak_private_key;
|
2021-11-10 14:32:27 +00:00
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_instance_url_https_dns() {
|
|
|
|
let instance_url = Url::parse("https://example.com/").unwrap();
|
2022-04-30 19:26:09 +00:00
|
|
|
let instance_rsa_key = generate_weak_private_key().unwrap();
|
2021-11-18 00:22:41 +00:00
|
|
|
let instance = Instance {
|
|
|
|
_url: instance_url,
|
2022-02-08 19:51:40 +00:00
|
|
|
_version: "1.0.0".to_string(),
|
2021-11-18 00:22:41 +00:00
|
|
|
actor_key: instance_rsa_key,
|
2021-11-18 14:56:52 +00:00
|
|
|
is_private: true,
|
2021-11-18 00:22:41 +00:00
|
|
|
};
|
2021-11-10 14:32:27 +00:00
|
|
|
|
|
|
|
assert_eq!(instance.url(), "https://example.com");
|
|
|
|
assert_eq!(instance.host(), "example.com");
|
2022-02-08 19:51:40 +00:00
|
|
|
assert_eq!(instance.agent(), "Mitra 1.0.0; https://example.com");
|
2021-11-10 14:32:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_instance_url_http_ipv4() {
|
|
|
|
let instance_url = Url::parse("http://1.2.3.4:3777/").unwrap();
|
2022-04-30 19:26:09 +00:00
|
|
|
let instance_rsa_key = generate_weak_private_key().unwrap();
|
2021-11-18 00:22:41 +00:00
|
|
|
let instance = Instance {
|
|
|
|
_url: instance_url,
|
2022-02-08 19:51:40 +00:00
|
|
|
_version: "1.0.0".to_string(),
|
2021-11-18 00:22:41 +00:00
|
|
|
actor_key: instance_rsa_key,
|
2021-11-18 14:56:52 +00:00
|
|
|
is_private: true,
|
2021-11-18 00:22:41 +00:00
|
|
|
};
|
2021-11-10 14:32:27 +00:00
|
|
|
|
|
|
|
assert_eq!(instance.url(), "http://1.2.3.4:3777");
|
|
|
|
assert_eq!(instance.host(), "1.2.3.4");
|
|
|
|
}
|
|
|
|
}
|