use serde::{Deserialize, Serialize};
use std::{
collections::HashSet,
net::{IpAddr, Ipv4Addr, SocketAddr},
path::{Path, PathBuf},
};
use url::Url;
use super::backend::Backend;
use super::{Error, Result};
use sos_protocol::sdk::{signer::ecdsa::Address, vfs};
#[derive(Default, Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct ServerConfig {
pub storage: StorageConfig,
pub log: LogConfig,
pub access: Option<AccessControlConfig>,
pub net: NetworkConfig,
#[serde(skip)]
file: Option<PathBuf>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct AccessControlConfig {
pub allow: Option<HashSet<Address>>,
pub deny: Option<HashSet<Address>>,
}
impl AccessControlConfig {
pub fn is_allowed_access(&self, address: &Address) -> bool {
let has_definitions = self.allow.is_some() || self.deny.is_some();
if has_definitions {
match (&self.deny, &self.allow) {
(Some(deny), None) => {
if deny.iter().any(|a| a == address) {
return false;
}
true
}
(None, Some(allow)) => {
if allow.iter().any(|a| a == address) {
return true;
}
false
}
(Some(deny), Some(allow)) => {
if allow.iter().any(|a| a == address) {
return true;
}
if deny.iter().any(|a| a == address) {
return false;
}
false
}
_ => true,
}
} else {
true
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogConfig {
pub directory: PathBuf,
pub name: String,
pub level: String,
}
impl Default for LogConfig {
fn default() -> Self {
Self {
directory: PathBuf::from("logs"),
name: "sos-server.log".to_string(),
level: "sos_server=info".to_string(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct NetworkConfig {
pub bind: SocketAddr,
pub ssl: Option<SslConfig>,
pub cors: Option<CorsConfig>,
}
impl Default for NetworkConfig {
fn default() -> Self {
Self {
bind: SocketAddr::new(
IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
5053,
),
ssl: Default::default(),
cors: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase", untagged)]
pub enum SslConfig {
Tls(TlsConfig),
#[cfg(feature = "acme")]
Acme(AcmeConfig),
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct TlsConfig {
pub cert: PathBuf,
pub key: PathBuf,
}
#[cfg(feature = "acme")]
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct AcmeConfig {
pub cache: PathBuf,
pub domains: Vec<String>,
pub email: Vec<String>,
pub production: bool,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct CorsConfig {
pub origins: Vec<Url>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct StorageConfig {
pub path: PathBuf,
}
impl Default for StorageConfig {
fn default() -> Self {
Self {
path: PathBuf::from("."),
}
}
}
impl ServerConfig {
pub async fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
if !vfs::try_exists(path.as_ref()).await? {
return Err(Error::NotFile(path.as_ref().to_path_buf()));
}
let contents = vfs::read_to_string(path.as_ref()).await?;
let mut config: ServerConfig = toml::from_str(&contents)?;
config.file = Some(path.as_ref().canonicalize()?);
let dir = config.directory();
if let Some(SslConfig::Tls(tls)) = &mut config.net.ssl {
if tls.cert.is_relative() {
tls.cert = dir.join(&tls.cert);
}
if tls.key.is_relative() {
tls.key = dir.join(&tls.key);
}
tls.cert = tls.cert.canonicalize()?;
tls.key = tls.key.canonicalize()?;
}
Ok(config)
}
pub fn set_bind_address(&mut self, addr: SocketAddr) {
self.net.bind = addr;
}
pub fn bind_address(&self) -> &SocketAddr {
&self.net.bind
}
fn directory(&self) -> PathBuf {
self.file
.as_ref()
.unwrap()
.parent()
.map(|p| p.to_path_buf())
.unwrap()
}
pub async fn backend(&self) -> Result<Backend> {
let dir = self.directory();
let path = &self.storage.path;
let path = if path.is_relative() {
dir.join(path)
} else {
path.to_owned()
};
let path = path.canonicalize()?;
let mut backend = Backend::new(path);
backend.read_dir().await?;
Ok(backend)
}
}