Test config round trip

This commit is contained in:
Antoine Gersant 2024-10-08 23:03:15 -07:00
parent 0058221e88
commit fb18cb3c4f
6 changed files with 80 additions and 35 deletions
src
test-data

View file

@ -195,15 +195,14 @@ impl App {
async fn get_or_create_auth_secret(path: &Path) -> Result<auth::Secret, Error> { async fn get_or_create_auth_secret(path: &Path) -> Result<auth::Secret, Error> {
match tokio::fs::read(&path).await { match tokio::fs::read(&path).await {
Ok(s) => Ok(auth::Secret { Ok(s) => Ok(auth::Secret(
key: s s.try_into()
.try_into()
.map_err(|_| Error::AuthenticationSecretInvalid)?, .map_err(|_| Error::AuthenticationSecretInvalid)?,
}), )),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => { Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
let mut secret = auth::Secret::default(); let mut secret = auth::Secret::default();
OsRng.fill_bytes(&mut secret.key); OsRng.fill_bytes(secret.as_mut());
tokio::fs::write(&path, &secret.key) tokio::fs::write(&path, &secret)
.await .await
.map_err(|_| Error::AuthenticationSecretInvalid)?; .map_err(|_| Error::AuthenticationSecretInvalid)?;
Ok(secret) Ok(secret)

View file

@ -9,8 +9,18 @@ use serde::{Deserialize, Serialize};
use crate::app::Error; use crate::app::Error;
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct Secret { pub struct Secret(pub [u8; 32]);
pub key: [u8; 32],
impl AsRef<[u8]> for Secret {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl AsMut<[u8]> for Secret {
fn as_mut(&mut self) -> &mut [u8] {
&mut self.0
}
} }
#[derive(Debug)] #[derive(Debug)]
@ -55,7 +65,7 @@ pub fn generate_auth_token(
serde_json::to_string(&authorization).or(Err(Error::AuthorizationTokenEncoding))?; serde_json::to_string(&authorization).or(Err(Error::AuthorizationTokenEncoding))?;
branca::encode( branca::encode(
serialized_authorization.as_bytes(), serialized_authorization.as_bytes(),
&auth_secret.key, auth_secret.as_ref(),
SystemTime::now() SystemTime::now()
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.unwrap_or_default() .unwrap_or_default()
@ -75,7 +85,7 @@ pub fn decode_auth_token(
Scope::PolarisAuth => 0, // permanent Scope::PolarisAuth => 0, // permanent
}; };
let authorization = let authorization =
branca::decode(data, &auth_secret.key, ttl).map_err(|_| Error::InvalidAuthToken)?; branca::decode(data, auth_secret.as_ref(), ttl).map_err(|_| Error::InvalidAuthToken)?;
let authorization: Authorization = let authorization: Authorization =
serde_json::from_slice(&authorization[..]).map_err(|_| Error::InvalidAuthToken)?; serde_json::from_slice(&authorization[..]).map_err(|_| Error::InvalidAuthToken)?;
if authorization.scope != scope { if authorization.scope != scope {

View file

@ -24,7 +24,7 @@ pub struct Config {
pub reindex_every_n_seconds: Option<u64>, pub reindex_every_n_seconds: Option<u64>,
pub album_art_pattern: Option<Regex>, pub album_art_pattern: Option<Regex>,
pub ddns_url: Option<http::Uri>, pub ddns_url: Option<http::Uri>,
pub mount_dirs: Vec<MountDir>, pub mount_dirs: HashMap<String, MountDir>,
pub users: HashMap<String, User>, pub users: HashMap<String, User>,
} }
@ -34,16 +34,15 @@ impl TryFrom<storage::Config> for Config {
fn try_from(c: storage::Config) -> Result<Self, Self::Error> { fn try_from(c: storage::Config) -> Result<Self, Self::Error> {
let mut users: HashMap<String, User> = HashMap::new(); let mut users: HashMap<String, User> = HashMap::new();
for user in c.users { for user in c.users {
if let Ok(user) = <storage::User as TryInto<User>>::try_into(user) { let user = <storage::User as TryInto<User>>::try_into(user)?;
users.insert(user.name.clone(), user); users.insert(user.name.clone(), user);
}
} }
let mount_dirs = c let mut mount_dirs: HashMap<String, MountDir> = HashMap::new();
.mount_dirs for mount_dir in c.mount_dirs {
.into_iter() let mount_dir = <storage::MountDir as TryInto<MountDir>>::try_into(mount_dir)?;
.filter_map(|m| m.try_into().ok()) mount_dirs.insert(mount_dir.name.clone(), mount_dir);
.collect(); }
let ddns_url = match c.ddns_url.map(http::Uri::try_from) { let ddns_url = match c.ddns_url.map(http::Uri::try_from) {
Some(Ok(u)) => Some(u), Some(Ok(u)) => Some(u),
@ -58,7 +57,7 @@ impl TryFrom<storage::Config> for Config {
}; };
Ok(Config { Ok(Config {
reindex_every_n_seconds: c.reindex_every_n_seconds, // TODO validate and warn reindex_every_n_seconds: c.reindex_every_n_seconds,
album_art_pattern, album_art_pattern,
ddns_url, ddns_url,
mount_dirs, mount_dirs,
@ -72,7 +71,7 @@ impl From<Config> for storage::Config {
Self { Self {
reindex_every_n_seconds: c.reindex_every_n_seconds, reindex_every_n_seconds: c.reindex_every_n_seconds,
album_art_pattern: c.album_art_pattern.map(|p| p.as_str().to_owned()), album_art_pattern: c.album_art_pattern.map(|p| p.as_str().to_owned()),
mount_dirs: c.mount_dirs.into_iter().map(|d| d.into()).collect(), mount_dirs: c.mount_dirs.into_iter().map(|(_, d)| d.into()).collect(),
ddns_url: c.ddns_url.map(|u| u.to_string()), ddns_url: c.ddns_url.map(|u| u.to_string()),
users: c.users.into_iter().map(|(_, u)| u.into()).collect(), users: c.users.into_iter().map(|(_, u)| u.into()).collect(),
} }
@ -208,7 +207,8 @@ impl Manager {
} }
pub async fn get_mounts(&self) -> Vec<MountDir> { pub async fn get_mounts(&self) -> Vec<MountDir> {
self.config.read().await.mount_dirs.clone() let config = self.config.read().await;
config.mount_dirs.values().cloned().collect()
} }
pub async fn resolve_virtual_path<P: AsRef<Path>>( pub async fn resolve_virtual_path<P: AsRef<Path>>(
@ -219,8 +219,42 @@ impl Manager {
config.resolve_virtual_path(virtual_path) config.resolve_virtual_path(virtual_path)
} }
pub async fn set_mounts(&self, mount_dirs: Vec<storage::MountDir>) { pub async fn set_mounts(&self, mount_dirs: Vec<storage::MountDir>) -> Result<(), Error> {
self.config.write().await.set_mounts(mount_dirs); self.config.write().await.set_mounts(mount_dirs)
// TODO persistence // TODO persistence
} }
} }
#[cfg(test)]
mod test {
use super::*;
#[tokio::test]
async fn can_read_config() {
let config_path = PathBuf::from_iter(["test-data", "config.toml"]);
let manager = Manager::new(&config_path, auth::Secret([0; 32]))
.await
.unwrap();
let config: storage::Config = manager.config.read().await.clone().into();
assert_eq!(config.reindex_every_n_seconds, None);
assert_eq!(
config.album_art_pattern,
Some(r#"^Folder\.(png|jpg|jpeg)$"#.to_owned())
);
assert_eq!(
config.mount_dirs,
vec![storage::MountDir {
source: PathBuf::from("test-data/small-collection"),
name: "root".to_owned(),
}]
);
assert_eq!(config.users[0].name, "test_user");
assert_eq!(config.users[0].admin, Some(true));
assert_eq!(
config.users[0].initial_password,
Some("very_secret_password".to_owned())
);
assert!(config.users[0].hashed_password.is_some());
}
}

View file

@ -1,4 +1,5 @@
use std::{ use std::{
collections::HashMap,
ops::Deref, ops::Deref,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
@ -38,17 +39,19 @@ impl From<MountDir> for storage::MountDir {
} }
impl Config { impl Config {
pub fn set_mounts(&mut self, mount_dirs: Vec<storage::MountDir>) { pub fn set_mounts(&mut self, mount_dirs: Vec<storage::MountDir>) -> Result<(), Error> {
self.mount_dirs = mount_dirs let mut new_mount_dirs = HashMap::new();
.into_iter() for mount_dir in mount_dirs {
.filter_map(|m| m.try_into().ok()) let mount_dir = <storage::MountDir as TryInto<MountDir>>::try_into(mount_dir)?;
.collect(); new_mount_dirs.insert(mount_dir.name.clone(), mount_dir);
}
self.mount_dirs = new_mount_dirs;
Ok(())
} }
pub fn resolve_virtual_path<P: AsRef<Path>>(&self, virtual_path: P) -> Result<PathBuf, Error> { pub fn resolve_virtual_path<P: AsRef<Path>>(&self, virtual_path: P) -> Result<PathBuf, Error> {
for mount in &self.mount_dirs { for (name, mount) in &self.mount_dirs {
let mount_path = Path::new(&mount.name); if let Ok(p) = virtual_path.as_ref().strip_prefix(name) {
if let Ok(p) = virtual_path.as_ref().strip_prefix(mount_path) {
return if p.components().count() == 0 { return if p.components().count() == 0 {
Ok(mount.source.clone()) Ok(mount.source.clone())
} else { } else {

View file

@ -161,7 +161,7 @@ async fn put_mount_dirs(
) -> Result<(), APIError> { ) -> Result<(), APIError> {
let new_mount_dirs: Vec<config::storage::MountDir> = let new_mount_dirs: Vec<config::storage::MountDir> =
new_mount_dirs.iter().cloned().map(|m| m.into()).collect(); new_mount_dirs.iter().cloned().map(|m| m.into()).collect();
config_manager.set_mounts(new_mount_dirs).await; config_manager.set_mounts(new_mount_dirs).await?;
Ok(()) Ok(())
} }

View file

@ -1,4 +1,3 @@
[settings]
album_art_pattern = '^Folder\.(png|jpg|jpeg)$' album_art_pattern = '^Folder\.(png|jpg|jpeg)$'
[[mount_dirs]] [[mount_dirs]]
@ -7,5 +6,5 @@ source = 'test-data/small-collection'
[[users]] [[users]]
name = 'test_user' name = 'test_user'
password = 'very_secret_password' initial_password = 'very_secret_password'
admin = true admin = true