Files
komodo/core/src/api/mod.rs
2023-05-24 07:45:52 +00:00

237 lines
7.4 KiB
Rust

use anyhow::{anyhow, Context};
use axum::{
body::Body,
extract::Path,
http::{Request, StatusCode},
middleware,
routing::{get, post},
Extension, Json, Router,
};
use futures_util::Future;
use helpers::handle_anyhow_error;
use mungos::mongodb::bson::doc;
use serde::Deserialize;
use types::{PermissionLevel, UpdateTarget, User};
use typeshare::typeshare;
use crate::{
auth::{auth_request, JwtExtension, RequestUser, RequestUserExtension},
response,
state::{State, StateExtension},
ResponseResult,
};
mod build;
mod command;
mod deployment;
mod github_listener;
mod group;
mod permissions;
mod procedure;
mod secret;
mod server;
mod update;
#[typeshare]
#[derive(Deserialize)]
struct UpdateDescriptionBody {
target: UpdateTarget,
description: String,
}
#[derive(Deserialize)]
struct UserId {
id: String,
}
pub fn router() -> Router {
Router::new()
.route(
"/title",
get(|state: StateExtension| async move { state.config.title.clone() }),
)
.route("/user", get(get_request_user))
.nest("/listener", github_listener::router())
.nest(
"/",
Router::new()
.route("/user/:id", get(get_user_at_id))
.route(
"/username/:id",
get(|state: StateExtension, Path(UserId { id })| async move {
let user = state
.db
.get_user(&id)
.await
.context("failed to find user at id")
.map_err(handle_anyhow_error)?;
response!(Json(user.username))
}),
)
.route(
"/github_webhook_base_url",
get(|state: StateExtension| async move {
state
.config
.github_webhook_base_url
.as_ref()
.unwrap_or(&state.config.host)
.to_string()
}),
)
.route(
"/update_description",
post(
|state: StateExtension,
user: RequestUserExtension,
body: Json<UpdateDescriptionBody>| async move {
state
.update_description(&body.target, &body.description, &user)
.await
.map_err(handle_anyhow_error)
},
),
)
.route("/users", get(get_users))
.nest("/build", build::router())
.nest("/deployment", deployment::router())
.nest("/server", server::router())
.nest("/command", command::router())
.nest("/procedure", procedure::router())
.nest("/group", group::router())
.nest("/update", update::router())
.nest("/permissions", permissions::router())
.nest("/secret", secret::router())
.layer(middleware::from_fn(auth_request)),
)
}
async fn get_request_user(
Extension(jwt): JwtExtension,
req: Request<Body>,
) -> ResponseResult<Json<User>> {
let mut user = jwt.authenticate(&req).await.map_err(handle_anyhow_error)?;
user.password = None;
for secret in &mut user.secrets {
secret.hash = String::new();
}
Ok(Json(user))
}
async fn get_users(
state: StateExtension,
user: RequestUserExtension,
) -> ResponseResult<Json<Vec<User>>> {
if user.is_admin {
let users = state
.db
.users
.get_some(None, None)
.await
.context("failed to get users from db")
.map_err(handle_anyhow_error)?
.into_iter()
.map(|u| User {
password: None,
secrets: vec![],
..u
})
.collect::<Vec<_>>();
Ok(Json(users))
} else {
Err((StatusCode::UNAUTHORIZED, "user is not admin".to_string()))
}
}
async fn get_user_at_id(
state: StateExtension,
Path(UserId { id }): Path<UserId>,
user: RequestUserExtension,
) -> ResponseResult<Json<User>> {
if user.is_admin {
let mut user = state
.db
.users
.find_one_by_id(&id)
.await
.context("failed at query to get user from mongo")
.map_err(handle_anyhow_error)?
.ok_or(anyhow!(""))
.map_err(handle_anyhow_error)?;
user.password = None;
for secret in &mut user.secrets {
secret.hash = String::new();
}
Ok(Json(user))
} else {
Err((StatusCode::UNAUTHORIZED, "user is not admin".to_string()))
}
}
// need to run requested actions in here to prevent them being dropped mid action when user disconnects prematurely
pub async fn spawn_request_action<A>(action: A) -> ResponseResult<A::Output>
where
A: Future + Send + 'static,
A::Output: Send + 'static,
{
let res = tokio::spawn(action)
.await
.context("failure at action thread spawn")
.map_err(handle_anyhow_error)?;
Ok(res)
}
impl State {
pub async fn update_description(
&self,
target: &UpdateTarget,
description: &str,
user: &RequestUser,
) -> anyhow::Result<()> {
match target {
UpdateTarget::Build(id) => {
self.get_build_check_permissions(id, user, PermissionLevel::Update)
.await?;
self.db
.builds
.update_one::<()>(id, mungos::Update::Set(doc! { "description": description }))
.await?;
}
UpdateTarget::Deployment(id) => {
self.get_deployment_check_permissions(id, user, PermissionLevel::Update)
.await?;
self.db
.deployments
.update_one::<()>(id, mungos::Update::Set(doc! { "description": description }))
.await?;
}
UpdateTarget::Server(id) => {
self.get_server_check_permissions(id, user, PermissionLevel::Update)
.await?;
self.db
.servers
.update_one::<()>(id, mungos::Update::Set(doc! { "description": description }))
.await?;
}
UpdateTarget::Group(id) => {
self.get_group_check_permissions(id, user, PermissionLevel::Update)
.await?;
self.db
.groups
.update_one::<()>(id, mungos::Update::Set(doc! { "description": description }))
.await?;
}
UpdateTarget::Procedure(id) => {
self.get_procedure_check_permissions(id, user, PermissionLevel::Update)
.await?;
self.db
.procedures
.update_one::<()>(id, mungos::Update::Set(doc! { "description": description }))
.await?;
}
_ => return Err(anyhow!("invalid target: {target:?}")),
}
Ok(())
}
}