Implements more endpoints

This commit is contained in:
Antoine Gersant 2024-07-13 15:48:08 -07:00
parent 5c4631c673
commit 03d5568765
5 changed files with 193 additions and 13 deletions

View file

@ -34,6 +34,18 @@ impl FromRef<App> for app::config::Manager {
} }
} }
impl FromRef<App> for app::ddns::Manager {
fn from_ref(app: &App) -> Self {
app.ddns_manager.clone()
}
}
impl FromRef<App> for app::index::Index {
fn from_ref(app: &App) -> Self {
app.index.clone()
}
}
impl FromRef<App> for app::user::Manager { impl FromRef<App> for app::user::Manager {
fn from_ref(app: &App) -> Self { fn from_ref(app: &App) -> Self {
app.user_manager.clone() app.user_manager.clone()
@ -45,3 +57,9 @@ impl FromRef<App> for app::settings::Manager {
app.settings_manager.clone() app.settings_manager.clone()
} }
} }
impl FromRef<App> for app::vfs::Manager {
fn from_ref(app: &App) -> Self {
app.vfs_manager.clone()
}
}

View file

@ -1,15 +1,15 @@
use axum::{ use axum::{
extract::State, extract::{Path, State},
routing::{get, put}, routing::{delete, get, post, put},
Json, Router, Json, Router,
}; };
use crate::{ use crate::{
app::{config, settings, user, App}, app::{config, ddns, index, settings, user, vfs, App},
server::{dto, error::APIError}, server::{dto, error::APIError},
}; };
use super::auth::AdminRights; use super::auth::{AdminRights, Auth};
pub fn router() -> Router<App> { pub fn router() -> Router<App> {
Router::new() Router::new()
@ -18,6 +18,18 @@ pub fn router() -> Router<App> {
.route("/config", put(put_config)) .route("/config", put(put_config))
.route("/settings", get(get_settings)) .route("/settings", get(get_settings))
.route("/settings", put(put_settings)) .route("/settings", put(put_settings))
.route("/mount_dirs", get(get_mount_dirs))
.route("/mount_dirs", put(put_mount_dirs))
.route("/ddns", get(get_ddns))
.route("/ddns", put(put_ddns))
.route("/auth", post(post_auth))
.route("/user", post(post_user))
.route("/user/:name", delete(delete_user))
.route("/user/:name", put(put_user))
.route("/users", get(get_users))
.route("/preferences", get(get_preferences))
.route("/preferences", put(put_preferences))
.route("/trigger_index", post(post_trigger_index))
} }
async fn get_version() -> Json<dto::Version> { async fn get_version() -> Json<dto::Version> {
@ -68,3 +80,142 @@ async fn put_settings(
.await?; .await?;
Ok(()) Ok(())
} }
async fn get_mount_dirs(
_admin_rights: AdminRights,
State(vfs_manager): State<vfs::Manager>,
) -> Result<Json<Vec<dto::MountDir>>, APIError> {
let mount_dirs = vfs_manager.mount_dirs().await?;
let mount_dirs = mount_dirs.into_iter().map(|m| m.into()).collect();
Ok(Json(mount_dirs))
}
async fn put_mount_dirs(
_admin_rights: AdminRights,
State(vfs_manager): State<vfs::Manager>,
new_mount_dirs: Json<Vec<dto::MountDir>>,
) -> Result<(), APIError> {
let new_mount_dirs: Vec<vfs::MountDir> =
new_mount_dirs.iter().cloned().map(|m| m.into()).collect();
vfs_manager.set_mount_dirs(&new_mount_dirs).await?;
Ok(())
}
async fn get_ddns(
_admin_rights: AdminRights,
State(ddns_manager): State<ddns::Manager>,
) -> Result<Json<dto::DDNSConfig>, APIError> {
let ddns_config = ddns_manager.config().await?;
Ok(Json(ddns_config.into()))
}
async fn put_ddns(
_admin_rights: AdminRights,
State(ddns_manager): State<ddns::Manager>,
Json(new_ddns_config): Json<dto::DDNSConfig>,
) -> Result<(), APIError> {
ddns_manager.set_config(&new_ddns_config.into()).await?;
Ok(())
}
async fn post_auth(
State(user_manager): State<user::Manager>,
credentials: Json<dto::Credentials>,
) -> Result<Json<dto::Authorization>, APIError> {
let username = credentials.username.clone();
let user::AuthToken(token) = user_manager
.login(&credentials.username, &credentials.password)
.await?;
let is_admin = user_manager.is_admin(&credentials.username).await?;
let authorization = dto::Authorization {
username: username.clone(),
token,
is_admin,
};
Ok(Json(authorization))
}
async fn get_users(
_admin_rights: AdminRights,
State(user_manager): State<user::Manager>,
) -> Result<Json<Vec<dto::User>>, APIError> {
let users = user_manager.list().await?;
let users = users.into_iter().map(|u| u.into()).collect();
Ok(Json(users))
}
async fn post_user(
_admin_rights: AdminRights,
State(user_manager): State<user::Manager>,
Json(new_user): Json<dto::NewUser>,
) -> Result<(), APIError> {
user_manager.create(&new_user.into()).await?;
Ok(())
}
async fn put_user(
admin_rights: AdminRights,
State(user_manager): State<user::Manager>,
Path(name): Path<String>,
user_update: Json<dto::UserUpdate>,
) -> Result<(), APIError> {
if let Some(auth) = &admin_rights.get_auth() {
if auth.get_username() == name.as_str() && user_update.new_is_admin == Some(false) {
return Err(APIError::OwnAdminPrivilegeRemoval);
}
}
if let Some(password) = &user_update.new_password {
user_manager.set_password(&name, password).await?;
}
if let Some(is_admin) = &user_update.new_is_admin {
user_manager.set_is_admin(&name, *is_admin).await?;
}
Ok(())
}
async fn delete_user(
admin_rights: AdminRights,
State(user_manager): State<user::Manager>,
Path(name): Path<String>,
) -> Result<(), APIError> {
if let Some(auth) = &admin_rights.get_auth() {
if auth.get_username() == name.as_str() {
return Err(APIError::DeletingOwnAccount);
}
}
user_manager.delete(&name).await?;
Ok(())
}
async fn get_preferences(
auth: Auth,
State(user_manager): State<user::Manager>,
) -> Result<Json<user::Preferences>, APIError> {
let preferences = user_manager.read_preferences(auth.get_username()).await?;
Ok(Json(preferences))
}
async fn put_preferences(
auth: Auth,
State(user_manager): State<user::Manager>,
Json(preferences): Json<user::Preferences>,
) -> Result<(), APIError> {
user_manager
.write_preferences(auth.get_username(), &preferences)
.await?;
Ok(())
}
async fn post_trigger_index(
_admin_rights: AdminRights,
State(index): State<index::Index>,
) -> Result<(), APIError> {
index.trigger_reindex();
Ok(())
}

View file

@ -15,6 +15,12 @@ pub struct Auth {
username: String, username: String,
} }
impl Auth {
pub fn get_username(&self) -> &String {
return &self.username;
}
}
#[async_trait] #[async_trait]
impl<S> FromRequestParts<S> for Auth impl<S> FromRequestParts<S> for Auth
where where
@ -58,6 +64,12 @@ pub struct AdminRights {
auth: Option<Auth>, auth: Option<Auth>,
} }
impl AdminRights {
pub fn get_auth(&self) -> &Option<Auth> {
return &self.auth;
}
}
#[async_trait] #[async_trait]
impl<S> FromRequestParts<S> for AdminRights impl<S> FromRequestParts<S> for AdminRights
where where
@ -69,14 +81,12 @@ where
async fn from_request_parts(parts: &mut Parts, app: &S) -> Result<Self, Self::Rejection> { async fn from_request_parts(parts: &mut Parts, app: &S) -> Result<Self, Self::Rejection> {
let user_manager = user::Manager::from_ref(app); let user_manager = user::Manager::from_ref(app);
let auth_future = Auth::from_request_parts(parts, app);
let user_count = user_manager.count().await?; let user_count = user_manager.count().await?;
if user_count == 0 { if user_count == 0 {
return Ok(AdminRights { auth: None }); return Ok(AdminRights { auth: None });
} }
let auth = auth_future.await?; let auth = Auth::from_request_parts(parts, app).await?;
if user_manager.is_admin(&auth.username).await? { if user_manager.is_admin(&auth.username).await? {
Ok(AdminRights { auth: Some(auth) }) Ok(AdminRights { auth: Some(auth) })
} else { } else {

View file

@ -8,7 +8,7 @@ impl IntoResponse for APIError {
let message = self.to_string(); let message = self.to_string();
let status_code = match self { let status_code = match self {
APIError::AuthorizationTokenEncoding => StatusCode::INTERNAL_SERVER_ERROR, APIError::AuthorizationTokenEncoding => StatusCode::INTERNAL_SERVER_ERROR,
APIError::AdminPermissionRequired => StatusCode::UNAUTHORIZED, APIError::AdminPermissionRequired => StatusCode::FORBIDDEN,
APIError::AudioFileIOError => StatusCode::NOT_FOUND, APIError::AudioFileIOError => StatusCode::NOT_FOUND,
APIError::AuthenticationRequired => StatusCode::UNAUTHORIZED, APIError::AuthenticationRequired => StatusCode::UNAUTHORIZED,
APIError::BrancaTokenEncoding => StatusCode::INTERNAL_SERVER_ERROR, APIError::BrancaTokenEncoding => StatusCode::INTERNAL_SERVER_ERROR,

View file

@ -32,13 +32,14 @@ async fn get_ddns_config_golden_path() {
#[tokio::test] #[tokio::test]
async fn put_ddns_config_requires_admin() { async fn put_ddns_config_requires_admin() {
let mut service = ServiceType::new(&test_name!()).await; let mut service = ServiceType::new(&test_name!()).await;
let request = protocol::put_ddns_config(dto::DDNSConfig {
host: "test".to_owned(),
username: "test".to_owned(),
password: "test".to_owned(),
});
service.complete_initial_setup().await; service.complete_initial_setup().await;
let request = protocol::put_ddns_config(dto::DDNSConfig {
host: "host".to_owned(),
username: "ddns_user".to_owned(),
password: "ddns_password".to_owned(),
});
let response = service.fetch(&request).await; let response = service.fetch(&request).await;
assert_eq!(response.status(), StatusCode::UNAUTHORIZED); assert_eq!(response.status(), StatusCode::UNAUTHORIZED);