diff --git a/src/app.rs b/src/app.rs
index b9060d7..49c3d70 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -195,15 +195,14 @@ impl App {
 
 	async fn get_or_create_auth_secret(path: &Path) -> Result<auth::Secret, Error> {
 		match tokio::fs::read(&path).await {
-			Ok(s) => Ok(auth::Secret {
-				key: s
-					.try_into()
+			Ok(s) => Ok(auth::Secret(
+				s.try_into()
 					.map_err(|_| Error::AuthenticationSecretInvalid)?,
-			}),
+			)),
 			Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
 				let mut secret = auth::Secret::default();
-				OsRng.fill_bytes(&mut secret.key);
-				tokio::fs::write(&path, &secret.key)
+				OsRng.fill_bytes(secret.as_mut());
+				tokio::fs::write(&path, &secret)
 					.await
 					.map_err(|_| Error::AuthenticationSecretInvalid)?;
 				Ok(secret)
diff --git a/src/app/auth.rs b/src/app/auth.rs
index 6887898..f452f4b 100644
--- a/src/app/auth.rs
+++ b/src/app/auth.rs
@@ -9,8 +9,18 @@ use serde::{Deserialize, Serialize};
 use crate::app::Error;
 
 #[derive(Clone, Default)]
-pub struct Secret {
-	pub key: [u8; 32],
+pub struct Secret(pub [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)]
@@ -55,7 +65,7 @@ pub fn generate_auth_token(
 		serde_json::to_string(&authorization).or(Err(Error::AuthorizationTokenEncoding))?;
 	branca::encode(
 		serialized_authorization.as_bytes(),
-		&auth_secret.key,
+		auth_secret.as_ref(),
 		SystemTime::now()
 			.duration_since(UNIX_EPOCH)
 			.unwrap_or_default()
@@ -75,7 +85,7 @@ pub fn decode_auth_token(
 		Scope::PolarisAuth => 0, // permanent
 	};
 	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 =
 		serde_json::from_slice(&authorization[..]).map_err(|_| Error::InvalidAuthToken)?;
 	if authorization.scope != scope {
diff --git a/src/app/config.rs b/src/app/config.rs
index 4e3e6f3..c7adac9 100644
--- a/src/app/config.rs
+++ b/src/app/config.rs
@@ -24,7 +24,7 @@ pub struct Config {
 	pub reindex_every_n_seconds: Option<u64>,
 	pub album_art_pattern: Option<Regex>,
 	pub ddns_url: Option<http::Uri>,
-	pub mount_dirs: Vec<MountDir>,
+	pub mount_dirs: HashMap<String, MountDir>,
 	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> {
 		let mut users: HashMap<String, User> = HashMap::new();
 		for user in c.users {
-			if let Ok(user) = <storage::User as TryInto<User>>::try_into(user) {
-				users.insert(user.name.clone(), user);
-			}
+			let user = <storage::User as TryInto<User>>::try_into(user)?;
+			users.insert(user.name.clone(), user);
 		}
 
-		let mount_dirs = c
-			.mount_dirs
-			.into_iter()
-			.filter_map(|m| m.try_into().ok())
-			.collect();
+		let mut mount_dirs: HashMap<String, MountDir> = HashMap::new();
+		for mount_dir in c.mount_dirs {
+			let mount_dir = <storage::MountDir as TryInto<MountDir>>::try_into(mount_dir)?;
+			mount_dirs.insert(mount_dir.name.clone(), mount_dir);
+		}
 
 		let ddns_url = match c.ddns_url.map(http::Uri::try_from) {
 			Some(Ok(u)) => Some(u),
@@ -58,7 +57,7 @@ impl TryFrom<storage::Config> for 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,
 			ddns_url,
 			mount_dirs,
@@ -72,7 +71,7 @@ impl From<Config> for storage::Config {
 		Self {
 			reindex_every_n_seconds: c.reindex_every_n_seconds,
 			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()),
 			users: c.users.into_iter().map(|(_, u)| u.into()).collect(),
 		}
@@ -208,7 +207,8 @@ impl Manager {
 	}
 
 	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>>(
@@ -219,8 +219,42 @@ impl Manager {
 		config.resolve_virtual_path(virtual_path)
 	}
 
-	pub async fn set_mounts(&self, mount_dirs: Vec<storage::MountDir>) {
-		self.config.write().await.set_mounts(mount_dirs);
+	pub async fn set_mounts(&self, mount_dirs: Vec<storage::MountDir>) -> Result<(), Error> {
+		self.config.write().await.set_mounts(mount_dirs)
 		// 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());
+	}
+}
diff --git a/src/app/config/mounts.rs b/src/app/config/mounts.rs
index ec97d5d..8ca692e 100644
--- a/src/app/config/mounts.rs
+++ b/src/app/config/mounts.rs
@@ -1,4 +1,5 @@
 use std::{
+	collections::HashMap,
 	ops::Deref,
 	path::{Path, PathBuf},
 };
@@ -38,17 +39,19 @@ impl From<MountDir> for storage::MountDir {
 }
 
 impl Config {
-	pub fn set_mounts(&mut self, mount_dirs: Vec<storage::MountDir>) {
-		self.mount_dirs = mount_dirs
-			.into_iter()
-			.filter_map(|m| m.try_into().ok())
-			.collect();
+	pub fn set_mounts(&mut self, mount_dirs: Vec<storage::MountDir>) -> Result<(), Error> {
+		let mut new_mount_dirs = HashMap::new();
+		for mount_dir in mount_dirs {
+			let mount_dir = <storage::MountDir as TryInto<MountDir>>::try_into(mount_dir)?;
+			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> {
-		for mount in &self.mount_dirs {
-			let mount_path = Path::new(&mount.name);
-			if let Ok(p) = virtual_path.as_ref().strip_prefix(mount_path) {
+		for (name, mount) in &self.mount_dirs {
+			if let Ok(p) = virtual_path.as_ref().strip_prefix(name) {
 				return if p.components().count() == 0 {
 					Ok(mount.source.clone())
 				} else {
diff --git a/src/server/axum/api.rs b/src/server/axum/api.rs
index f233ab5..4725778 100644
--- a/src/server/axum/api.rs
+++ b/src/server/axum/api.rs
@@ -161,7 +161,7 @@ async fn put_mount_dirs(
 ) -> Result<(), APIError> {
 	let new_mount_dirs: Vec<config::storage::MountDir> =
 		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(())
 }
 
diff --git a/test-data/config.toml b/test-data/config.toml
index 9ad682b..61b3244 100644
--- a/test-data/config.toml
+++ b/test-data/config.toml
@@ -1,4 +1,3 @@
-[settings]
 album_art_pattern = '^Folder\.(png|jpg|jpeg)$'
 
 [[mount_dirs]]
@@ -7,5 +6,5 @@ source = 'test-data/small-collection'
 
 [[users]]
 name = 'test_user'
-password = 'very_secret_password'
+initial_password = 'very_secret_password'
 admin = true