diff --git a/Cargo.lock b/Cargo.lock index 265989c96..e1389647a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2133,6 +2133,7 @@ dependencies = [ "serde", "serde_json", "serror", + "serror_axum", "sha2", "simple_logger", "slack_client_rs", @@ -2960,9 +2961,9 @@ dependencies = [ [[package]] name = "serror" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bc462876e265831d80297a3898a173e3d5c72a1501dec9234423de2d25ac89c" +checksum = "7fc001f673de08108f1602eafd1f7fb18894588004b9d91a6bf05a5e284fe50d" dependencies = [ "anyhow", "serde", @@ -2970,6 +2971,17 @@ dependencies = [ "serde_json", ] +[[package]] +name = "serror_axum" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7ab34bc2fc163055ee8e2731a7ec9bbf6ebff834a10579e44fddce2695b6ea5" +dependencies = [ + "anyhow", + "axum", + "serror", +] + [[package]] name = "sha-1" version = "0.10.1" diff --git a/Cargo.toml b/Cargo.toml index e25aebace..af0d789b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,8 @@ resolver_api = "0.1.6" parse_csl = "0.1.0" mungos = "0.5.4" mongo_indexed = "0.2.1" -serror = "0.1.3" +serror = "0.1.4" +serror_axum = "0.1.2" svi = "0.1.4" # external clap = { version = "4.4.13", features = ["derive"] } diff --git a/bin/core/Cargo.toml b/bin/core/Cargo.toml index f9166024c..39bb0c977 100644 --- a/bin/core/Cargo.toml +++ b/bin/core/Cargo.toml @@ -27,6 +27,7 @@ mungos.workspace = true mongo_indexed.workspace = true slack.workspace = true serror.workspace = true +serror_axum.workspace = true # external tokio.workspace = true tokio-util.workspace = true diff --git a/bin/core/src/api/auth.rs b/bin/core/src/api/auth.rs index 33e0bbdd3..528bc2b67 100644 --- a/bin/core/src/api/auth.rs +++ b/bin/core/src/api/auth.rs @@ -1,9 +1,5 @@ use async_trait::async_trait; -use monitor_client::api::auth::{ - CreateLocalUser, ExchangeForJwt, ExchangeForJwtResponse, - GetLoginOptions, GetLoginOptionsResponse, LoginLocalUser, - LoginWithSecret, -}; +use monitor_client::api::auth::*; use resolver_api::{derive::Resolver, Resolve}; use serde::{Deserialize, Serialize}; use typeshare::typeshare; @@ -19,7 +15,6 @@ pub enum AuthRequest { GetLoginOptions(GetLoginOptions), CreateLocalUser(CreateLocalUser), LoginLocalUser(LoginLocalUser), - LoginWithSecret(LoginWithSecret), ExchangeForJwt(ExchangeForJwt), } diff --git a/bin/core/src/api/execute/deployment.rs b/bin/core/src/api/execute/deployment.rs index 1f5bd50cb..91058f0a6 100644 --- a/bin/core/src/api/execute/deployment.rs +++ b/bin/core/src/api/execute/deployment.rs @@ -268,7 +268,8 @@ impl Resolve for State { let periphery = self.periphery_client(&server)?; let inner = || async move { - let mut update = make_update(&deployment, Operation::StopContainer, &user); + let mut update = + make_update(&deployment, Operation::StopContainer, &user); update.id = self.add_update(update.clone()).await?; diff --git a/bin/core/src/api/execute/mod.rs b/bin/core/src/api/execute/mod.rs index 9cb11a089..440cf4e85 100644 --- a/bin/core/src/api/execute/mod.rs +++ b/bin/core/src/api/execute/mod.rs @@ -6,14 +6,13 @@ use axum_extra::{headers::ContentType, TypedHeader}; use monitor_client::api::execute::*; use resolver_api::{derive::Resolver, Resolve, Resolver}; use serde::{Deserialize, Serialize}; +use serror_axum::AppResult; use typeshare::typeshare; use uuid::Uuid; use crate::{ auth::{auth_request, RequestUser, RequestUserExtension}, - helpers::into_response_error, state::{State, StateExtension}, - ResponseResult, }; mod build; @@ -66,23 +65,22 @@ pub fn router() -> Router { user.username, user.id ); let res = tokio::spawn(async move { - state.resolve_request(request, user).await + let res = state.resolve_request(request, user).await; + if let Err(e) = &res { + info!("/execute request {req_id} ERROR: {e:#?}"); + } + let elapsed = timer.elapsed(); + info!( + "/execute request {req_id} | resolve time: {elapsed:?}" + ); + res }) .await .context("failure in spawned execute task"); if let Err(e) = &res { - info!("/execute request {req_id} SPAWN ERROR: {e:#?}"); + info!("/execute request {req_id} SPAWN ERROR: {e:#?}",); } - let res = res.map_err(into_response_error)?; - if let Err(e) = &res { - info!("/execute request {req_id} ERROR: {e:#?}"); - } - let res = res.map_err(into_response_error)?; - let elapsed = timer.elapsed(); - info!( - "/execute request {req_id} | resolve time: {elapsed:?}" - ); - ResponseResult::Ok((TypedHeader(ContentType::json()), res)) + AppResult::Ok((TypedHeader(ContentType::json()), res??)) }, ), ) diff --git a/bin/core/src/api/read/alert.rs b/bin/core/src/api/read/alert.rs index 8523e91bd..191b68a79 100644 --- a/bin/core/src/api/read/alert.rs +++ b/bin/core/src/api/read/alert.rs @@ -1,8 +1,8 @@ use anyhow::Context; use async_trait::async_trait; use monitor_client::{ - entities::{deployment::Deployment, server::Server}, api::read::{ListAlerts, ListAlertsResponse}, + entities::{deployment::Deployment, server::Server}, }; use mungos::{ find::find_collect, diff --git a/bin/core/src/api/read/alerter.rs b/bin/core/src/api/read/alerter.rs index 4313dbaf2..66d46db3b 100644 --- a/bin/core/src/api/read/alerter.rs +++ b/bin/core/src/api/read/alerter.rs @@ -1,11 +1,11 @@ use anyhow::Context; use async_trait::async_trait; use monitor_client::{ + api::read::*, entities::{ alerter::{Alerter, AlerterListItem}, PermissionLevel, }, - api::read::*, }; use mungos::mongodb::bson::doc; use resolver_api::Resolve; diff --git a/bin/core/src/api/read/mod.rs b/bin/core/src/api/read/mod.rs index 0588bac01..cdcced464 100644 --- a/bin/core/src/api/read/mod.rs +++ b/bin/core/src/api/read/mod.rs @@ -8,14 +8,13 @@ use resolver_api::{ derive::Resolver, Resolve, ResolveToString, Resolver, }; use serde::{Deserialize, Serialize}; +use serror_axum::AppResult; use typeshare::typeshare; use uuid::Uuid; use crate::{ auth::{auth_request, RequestUser, RequestUserExtension}, - helpers::into_response_error, state::{State, StateExtension}, - ResponseResult, }; mod alert; @@ -149,12 +148,12 @@ pub fn router() -> Router { if let Err(e) = &res { warn!("/read request {req_id} ERROR: {e:#?}"); } - let res = res.map_err(into_response_error)?; + let res = res?; let elapsed = timer.elapsed(); debug!( "/read request {req_id} | resolve time: {elapsed:?}" ); - ResponseResult::Ok((TypedHeader(ContentType::json()), res)) + AppResult::Ok((TypedHeader(ContentType::json()), res)) }, ), ) diff --git a/bin/core/src/api/read/user.rs b/bin/core/src/api/read/user.rs index 27b7cac90..480776c69 100644 --- a/bin/core/src/api/read/user.rs +++ b/bin/core/src/api/read/user.rs @@ -20,9 +20,7 @@ impl Resolve for State { .await .context("failed at mongo query")? .context("no user found with id")?; - for secret in &mut user.secrets { - secret.hash = String::new(); - } + user.sanitize(); Ok(user) } } @@ -55,13 +53,10 @@ impl Resolve for State { if !user.is_admin { return Err(anyhow!("this route is only accessable by admins")); } - let mut users = find_collect(&self.db.users, None, None) .await .context("failed to pull users from db")?; - users.iter_mut().for_each(|user| { - user.secrets = Vec::new(); - }); + users.iter_mut().for_each(|user| user.sanitize()); Ok(users) } } diff --git a/bin/core/src/api/write/api_key.rs b/bin/core/src/api/write/api_key.rs new file mode 100644 index 000000000..befe18883 --- /dev/null +++ b/bin/core/src/api/write/api_key.rs @@ -0,0 +1,75 @@ +use anyhow::{anyhow, Context}; +use async_trait::async_trait; +use monitor_client::{ + api::write::*, + entities::{api_key::ApiKey, monitor_timestamp}, +}; +use mungos::mongodb::bson::doc; +use resolver_api::Resolve; + +use crate::{ + auth::{random_string, RequestUser}, + state::State, +}; + +const SECRET_LENGTH: usize = 40; +const BCRYPT_COST: u32 = 10; + +#[async_trait] +impl Resolve for State { + async fn resolve( + &self, + CreateApiKey { name, expires }: CreateApiKey, + user: RequestUser, + ) -> anyhow::Result { + let user = self.get_user(&user.id).await?; + + let key = format!("K-{}", random_string(SECRET_LENGTH)); + let secret = format!("S-{}", random_string(SECRET_LENGTH)); + let secret_hash = bcrypt::hash(&secret, BCRYPT_COST) + .context("failed at hashing secret string")?; + + let api_key = ApiKey { + name, + key: key.clone(), + secret: secret_hash, + user_id: user.id.clone(), + created_at: monitor_timestamp(), + expires, + }; + self + .db + .api_keys + .insert_one(api_key, None) + .await + .context("failed to create api key on db")?; + Ok(CreateApiKeyResponse { key, secret }) + } +} + +#[async_trait] +impl Resolve for State { + async fn resolve( + &self, + DeleteApiKey { key }: DeleteApiKey, + user: RequestUser, + ) -> anyhow::Result { + let key = self + .db + .api_keys + .find_one(doc! { "key": &key }, None) + .await + .context("failed at db query")? + .context("no api key with key found")?; + if user.id != key.user_id { + return Err(anyhow!("api key does not belong to user")); + } + self + .db + .api_keys + .delete_one(doc! { "key": key.key }, None) + .await + .context("failed to delete api key from db")?; + Ok(DeleteApiKeyResponse {}) + } +} diff --git a/bin/core/src/api/write/mod.rs b/bin/core/src/api/write/mod.rs index 661c1360d..e841874da 100644 --- a/bin/core/src/api/write/mod.rs +++ b/bin/core/src/api/write/mod.rs @@ -1,22 +1,21 @@ use std::time::Instant; -use anyhow::Context; use axum::{middleware, routing::post, Extension, Json, Router}; use axum_extra::{headers::ContentType, TypedHeader}; use monitor_client::api::write::*; use resolver_api::{derive::Resolver, Resolve, Resolver}; use serde::{Deserialize, Serialize}; +use serror_axum::AppResult; use typeshare::typeshare; use uuid::Uuid; use crate::{ auth::{auth_request, RequestUser, RequestUserExtension}, - helpers::into_response_error, state::{State, StateExtension}, - ResponseResult, }; mod alerter; +mod api_key; mod build; mod builder; mod deployment; @@ -25,7 +24,6 @@ mod launch; mod permissions; mod procedure; mod repo; -mod secret; mod server; mod tag; mod user; @@ -36,9 +34,9 @@ mod user; #[resolver_args(RequestUser)] #[serde(tag = "type", content = "params")] enum WriteRequest { - // ==== SECRET ==== - CreateLoginSecret(CreateLoginSecret), - DeleteLoginSecret(DeleteLoginSecret), + // ==== API KEY ==== + CreateApiKey(CreateApiKey), + DeleteApiKey(DeleteApiKey), // ==== USER ==== PushRecentlyViewed(PushRecentlyViewed), @@ -118,23 +116,21 @@ pub fn router() -> Router { user.username, user.id ); let res = tokio::spawn(async move { - state.resolve_request(request, user).await + let res = state.resolve_request(request, user).await; + if let Err(e) = &res { + info!("/write request {req_id} ERROR: {e:#?}"); + } + let elapsed = timer.elapsed(); + info!( + "/write request {req_id} | resolve time: {elapsed:?}" + ); + res }) - .await - .context("failure in spawned write task"); + .await; if let Err(e) = &res { info!("/write request {req_id} SPAWN ERROR: {e:#?}"); } - let res = res.map_err(into_response_error)?; - if let Err(e) = &res { - info!("/write request {req_id} ERROR: {e:#?}"); - } - let res = res.map_err(into_response_error)?; - let elapsed = timer.elapsed(); - info!( - "/write request {req_id} | resolve time: {elapsed:?}" - ); - ResponseResult::Ok((TypedHeader(ContentType::json()), res)) + AppResult::Ok((TypedHeader(ContentType::json()), res??)) }, ), ) diff --git a/bin/core/src/api/write/secret.rs b/bin/core/src/api/write/secret.rs deleted file mode 100644 index 9e174a63a..000000000 --- a/bin/core/src/api/write/secret.rs +++ /dev/null @@ -1,80 +0,0 @@ -use anyhow::{anyhow, Context}; -use async_trait::async_trait; -use monitor_client::{ - api::write::*, - entities::{monitor_timestamp, user::ApiSecret}, -}; -use mungos::{ - by_id::update_one_by_id, - mongodb::bson::{doc, to_bson}, -}; -use resolver_api::Resolve; - -use crate::{ - auth::{random_string, RequestUser}, - state::State, -}; - -const SECRET_LENGTH: usize = 40; -const BCRYPT_COST: u32 = 10; - -#[async_trait] -impl Resolve for State { - async fn resolve( - &self, - secret: CreateLoginSecret, - user: RequestUser, - ) -> anyhow::Result { - let user = self.get_user(&user.id).await?; - for s in &user.secrets { - if s.name == secret.name { - return Err(anyhow!( - "secret with name {} already exists", - secret.name - )); - } - } - let secret_str = random_string(SECRET_LENGTH); - let api_secret = ApiSecret { - name: secret.name, - created_at: monitor_timestamp(), - expires: secret.expires, - hash: bcrypt::hash(&secret_str, BCRYPT_COST) - .context("failed at hashing secret string")?, - }; - - update_one_by_id(&self.db.users, &user.id, doc! { - "$push": { - "secrets": to_bson(&api_secret).context("failed at converting secret to bson")? - } - }, None) - .await - .context("failed at mongo update query")?; - Ok(CreateLoginSecretResponse { secret: secret_str }) - } -} - -#[async_trait] -impl Resolve for State { - async fn resolve( - &self, - DeleteLoginSecret { name }: DeleteLoginSecret, - user: RequestUser, - ) -> anyhow::Result { - update_one_by_id( - &self.db.users, - &user.id, - doc! { - "$pull": { - "secrets": { - "name": name - } - } - }, - None, - ) - .await - .context("failed at mongo update query")?; - Ok(DeleteLoginSecretResponse {}) - } -} diff --git a/bin/core/src/auth/github/mod.rs b/bin/core/src/auth/github/mod.rs index ea73985e1..cb1c48281 100644 --- a/bin/core/src/auth/github/mod.rs +++ b/bin/core/src/auth/github/mod.rs @@ -1,11 +1,11 @@ use anyhow::{anyhow, Context}; use axum::{ - extract::Query, http::StatusCode, response::Redirect, routing::get, - Router, + extract::Query, response::Redirect, routing::get, Router, }; use monitor_client::entities::{monitor_timestamp, user::User}; use mungos::mongodb::bson::doc; use serde::Deserialize; +use serror_axum::AppError; use crate::state::StateExtension; @@ -28,10 +28,8 @@ pub fn router() -> Router { .route( "/callback", get(|state, query| async { - let redirect = callback(state, query).await.map_err(|e| { - (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:#?}")) - })?; - Result::<_, (StatusCode, String)>::Ok(redirect) + let redirect = callback(state, query).await?; + Result::<_, AppError>::Ok(redirect) }), ) } diff --git a/bin/core/src/auth/google/mod.rs b/bin/core/src/auth/google/mod.rs index 7395f6488..0aa40ae34 100644 --- a/bin/core/src/auth/google/mod.rs +++ b/bin/core/src/auth/google/mod.rs @@ -1,12 +1,12 @@ use anyhow::{anyhow, Context}; use async_timing_util::unix_timestamp_ms; use axum::{ - extract::Query, http::StatusCode, response::Redirect, routing::get, - Router, + extract::Query, response::Redirect, routing::get, Router, }; use monitor_client::entities::user::User; use mungos::mongodb::bson::doc; use serde::Deserialize; +use serror_axum::AppError; use crate::state::StateExtension; @@ -30,10 +30,8 @@ pub fn router() -> Router { .route( "/callback", get(|state, query| async { - let redirect = callback(state, query).await.map_err(|e| { - (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:#?}")) - })?; - Result::<_, (StatusCode, String)>::Ok(redirect) + let redirect = callback(state, query).await?; + Result::<_, AppError>::Ok(redirect) }), ) } diff --git a/bin/core/src/auth/jwt.rs b/bin/core/src/auth/jwt.rs index 160020695..7b802c231 100644 --- a/bin/core/src/auth/jwt.rs +++ b/bin/core/src/auth/jwt.rs @@ -7,7 +7,10 @@ use async_timing_util::{ use axum::{http::HeaderMap, Extension}; use hmac::{Hmac, Mac}; use jwt::{SignWithKey, VerifyWithKey}; -use monitor_client::entities::config::CoreConfig; +use monitor_client::entities::{ + config::CoreConfig, monitor_timestamp, +}; +use mungos::mongodb::bson::doc; use serde::{Deserialize, Serialize}; use sha2::Sha256; use tokio::sync::Mutex; @@ -118,17 +121,64 @@ impl State { &self, headers: &HeaderMap, ) -> anyhow::Result { - let jwt = headers - .get("authorization") - .context("no authorization header provided. must be Bearer ")? - .to_str()? - .replace("Bearer ", "") - .replace("bearer ", ""); - let user = self - .auth_jwt_check_enabled(&jwt) - .await - .context("failed to authenticate jwt")?; - Ok(user) + let user_id = match ( + headers.get("authorization"), + headers.get("x-api-key"), + headers.get("x-api-secret"), + ) { + (Some(jwt), _, _) => { + // USE JWT + let jwt = jwt + .to_str() + .context("jwt is not str")? + .replace("Bearer ", "") + .replace("bearer ", ""); + self + .auth_jwt_get_user_id(&jwt) + .await + .context("failed to authenticate jwt")? + } + (None, Some(key), Some(secret)) => { + // USE API KEY / SECRET + let key = key.to_str().context("key is not str")?; + let secret = secret.to_str().context("secret is not str")?; + self + .auth_api_key_get_user_id(key, secret) + .await + .context("failed to authenticate api key")? + } + _ => { + // AUTH FAIL + return Err(anyhow!("must attach either AUTHORIZATION header with jwt OR pass X-API-KEY and X-API-SECRET")); + } + }; + let user = self.get_user(&user_id).await?; + if user.enabled { + let user = InnerRequestUser { + id: user_id, + username: user.username, + is_admin: user.admin, + create_server_permissions: user.create_server_permissions, + create_build_permissions: user.create_build_permissions, + }; + Ok(user.into()) + } else { + Err(anyhow!("user not enabled")) + } + } + + pub async fn auth_jwt_get_user_id( + &self, + jwt: &str, + ) -> anyhow::Result { + let claims: JwtClaims = jwt + .verify_with_key(&self.jwt.key) + .context("failed to verify claims")?; + if claims.exp > unix_timestamp_ms() { + Ok(claims.id) + } else { + Err(anyhow!("token has expired")) + } } pub async fn auth_jwt_check_enabled( @@ -156,4 +206,30 @@ impl State { Err(anyhow!("token has expired")) } } + + pub async fn auth_api_key_get_user_id( + &self, + key: &str, + secret: &str, + ) -> anyhow::Result { + let key = self + .db + .api_keys + .find_one(doc! { "key": key }, None) + .await + .context("failed to query db")? + .context("no api key matching key")?; + if key.expires != 0 && key.expires < monitor_timestamp() { + return Err(anyhow!("api key expired")); + } + if bcrypt::verify(secret, &key.secret) + .context("failed to verify secret hash")? + { + // secret matches + Ok(key.user_id) + } else { + // secret mismatch + Err(anyhow!("invalid api secret")) + } + } } diff --git a/bin/core/src/auth/mod.rs b/bin/core/src/auth/mod.rs index ba5fdfec0..f1129183b 100644 --- a/bin/core/src/auth/mod.rs +++ b/bin/core/src/auth/mod.rs @@ -1,29 +1,23 @@ use std::{sync::Arc, time::Instant}; use axum::{ - extract::Request, - http::{HeaderMap, StatusCode}, - middleware::Next, - response::Response, - routing::post, - Extension, Json, Router, + extract::Request, http::HeaderMap, middleware::Next, + response::Response, routing::post, Extension, Json, Router, }; use axum_extra::{headers::ContentType, TypedHeader}; use rand::{distributions::Alphanumeric, thread_rng, Rng}; use resolver_api::Resolver; +use serror_axum::{AppError, AuthError}; use uuid::Uuid; mod github; mod google; mod jwt; mod local; -mod secret; use crate::{ - helpers::into_response_error, api::auth::AuthRequest, state::{State, StateExtension}, - ResponseResult, }; pub use self::jwt::{ @@ -37,17 +31,8 @@ pub async fn auth_request( headers: HeaderMap, mut req: Request, next: Next, -) -> ResponseResult { - let user = state - .authenticate_check_enabled(&headers) - .await - .map_err(|e| { - ( - StatusCode::UNAUTHORIZED, - TypedHeader(ContentType::json()), - format!("{e:#?}"), - ) - })?; +) -> Result { + let user = state.authenticate_check_enabled(&headers).await?; req.extensions_mut().insert(user); Ok(next.run(req).await) } @@ -64,11 +49,11 @@ pub fn router(state: &State) -> Router { if let Err(e) = &res { info!("/auth request {req_id} | ERROR: {e:?}"); } - let res = res.map_err(into_response_error)?; + let res = res?; let elapsed = timer.elapsed(); info!("/auth request {req_id} | resolve time: {elapsed:?}"); debug!("/auth request {req_id} | RESPONSE: {res}"); - ResponseResult::Ok((TypedHeader(ContentType::json()), res)) + Result::<_, AppError>::Ok((TypedHeader(ContentType::json()), res)) }, ), ); diff --git a/bin/core/src/auth/secret.rs b/bin/core/src/auth/secret.rs deleted file mode 100644 index dc0caabc2..000000000 --- a/bin/core/src/auth/secret.rs +++ /dev/null @@ -1,98 +0,0 @@ -use anyhow::{anyhow, Context}; -use async_timing_util::unix_timestamp_ms; -use axum::async_trait; -use monitor_client::api::auth::{ - LoginWithSecret, LoginWithSecretResponse, -}; -use mungos::{by_id::update_one_by_id, mongodb::bson::doc}; -use resolver_api::Resolve; - -use crate::state::State; - -#[async_trait] -impl Resolve for State { - async fn resolve( - &self, - LoginWithSecret { username, secret }: LoginWithSecret, - _: (), - ) -> anyhow::Result { - let user = self - .db - .users - .find_one(doc! { "username": &username }, None) - .await - .context("failed at mongo query")? - .ok_or(anyhow!("did not find user with username {username}"))?; - let ts = unix_timestamp_ms() as i64; - for s in user.secrets { - if let Some(expires) = s.expires { - if expires < ts { - update_one_by_id( - &self.db.users, - &user.id, - doc! { "$pull": { "secrets": { "name": s.name } } }, - None, - ) - .await - .context("failed to remove expired secret")?; - continue; - } - } - if bcrypt::verify(&secret, &s.hash) - .context("failed at verifying hash")? - { - let jwt = self - .jwt - .generate(user.id) - .context("failed at generating jwt for user")?; - return Ok(LoginWithSecretResponse { jwt }); - } - } - Err(anyhow!("invalid secret")) - } -} - -// pub fn router() -> Router { -// Router::new().route( -// "/login", -// post(|db, jwt, body| async { login(db, jwt, body).await.map_err(|e| ()) }), -// ) -// } - -// pub async fn login( -// state: StateExtension, -// Json(SecretLoginBody { username, secret }): Json, -// ) -> anyhow::Result { -// let user = state -// .db -// .users -// .find_one(doc! { "username": &username }, None) -// .await -// .context("failed at mongo query")? -// .ok_or(anyhow!("did not find user with username {username}"))?; -// let ts = unix_timestamp_ms() as i64; -// for s in user.secrets { -// if let Some(expires) = s.expires { -// let expires = unix_from_monitor_ts(&expires)?; -// if expires < ts { -// state -// .db -// .users -// .update_one::( -// &user.id, -// Update::Custom(doc! { "$pull": { "secrets": { "name": s.name } } }), -// ) -// .await -// .context("failed to remove expired secret")?; -// continue; -// } -// } -// if bcrypt::verify(&secret, &s.hash).context("failed at verifying hash")? { -// let jwt = jwt -// .generate(user.id) -// .context("failed at generating jwt for user")?; -// return Ok(jwt); -// } -// } -// Err(anyhow!("invalid secret")) -// } diff --git a/bin/core/src/helpers/mod.rs b/bin/core/src/helpers/mod.rs index 1031fdf81..091fa1038 100644 --- a/bin/core/src/helpers/mod.rs +++ b/bin/core/src/helpers/mod.rs @@ -1,8 +1,6 @@ use std::time::Duration; use anyhow::{anyhow, Context}; -use axum::http::StatusCode; -use axum_extra::{headers::ContentType, TypedHeader}; use monitor_client::entities::{ deployment::{Deployment, DockerContainerState}, monitor_timestamp, @@ -18,7 +16,6 @@ use mungos::{ }; use periphery_client::{requests, PeripheryClient}; use rand::{thread_rng, Rng}; -use serror::serialize_error_pretty; use crate::{auth::RequestUser, state::State}; @@ -61,16 +58,6 @@ pub fn make_update( } } -pub fn into_response_error( - e: anyhow::Error, -) -> (StatusCode, TypedHeader, String) { - ( - StatusCode::INTERNAL_SERVER_ERROR, - TypedHeader(ContentType::json()), - serialize_error_pretty(e), - ) -} - impl State { pub async fn get_user( &self, diff --git a/bin/core/src/main.rs b/bin/core/src/main.rs index b8360e9b0..761157e74 100644 --- a/bin/core/src/main.rs +++ b/bin/core/src/main.rs @@ -2,8 +2,7 @@ extern crate log; use anyhow::Context; -use axum::{http::StatusCode, Extension, Router}; -use axum_extra::{headers::ContentType, TypedHeader}; +use axum::{Extension, Router}; use termination_signal::tokio::immediate_term_handle; mod api; @@ -16,9 +15,6 @@ mod monitor; mod state; mod ws; -type ResponseResult = - Result, String)>; - async fn app() -> anyhow::Result<()> { let state = state::State::load().await?; diff --git a/bin/migrator/src/legacy/v0/user.rs b/bin/migrator/src/legacy/v0/user.rs index ca7d26459..98a46e66b 100644 --- a/bin/migrator/src/legacy/v0/user.rs +++ b/bin/migrator/src/legacy/v0/user.rs @@ -60,22 +60,22 @@ pub struct ApiSecret { pub expires: Option, } -impl TryFrom - for monitor_client::entities::user::ApiSecret -{ - type Error = anyhow::Error; - fn try_from(value: ApiSecret) -> Result { - let secret = Self { - name: value.name, - hash: value.hash, - created_at: unix_from_monitor_ts(&value.created_at)?, - expires: value - .expires - .and_then(|exp| unix_from_monitor_ts(&exp).ok()), - }; - Ok(secret) - } -} +// impl TryFrom +// for monitor_client::entities::user::ApiSecret +// { +// type Error = anyhow::Error; +// fn try_from(value: ApiSecret) -> Result { +// let secret = Self { +// name: value.name, +// hash: value.hash, +// created_at: unix_from_monitor_ts(&value.created_at)?, +// expires: value +// .expires +// .and_then(|exp| unix_from_monitor_ts(&exp).ok()), +// }; +// Ok(secret) +// } +// } impl TryFrom for monitor_client::entities::user::User { type Error = anyhow::Error; @@ -88,11 +88,6 @@ impl TryFrom for monitor_client::entities::user::User { create_server_permissions: value.create_server_permissions, create_build_permissions: value.create_build_permissions, avatar: value.avatar, - secrets: value - .secrets - .into_iter() - .map(|s| s.try_into()) - .collect::>()?, password: value.password, github_id: value.github_id, google_id: value.google_id, diff --git a/bin/tests/src/core.rs b/bin/tests/src/core.rs index 28bfd1033..07129130f 100644 --- a/bin/tests/src/core.rs +++ b/bin/tests/src/core.rs @@ -6,7 +6,6 @@ use monitor_client::{ server::PartialServerConfig, }, }; -use serde::Deserialize; #[allow(unused)] pub async fn tests() -> anyhow::Result<()> { @@ -118,32 +117,32 @@ async fn create_server( Ok(()) } -#[derive(Deserialize)] -struct CreateSecretEnv { - monitor_address: String, - monitor_username: String, - monitor_password: String, -} +// #[derive(Deserialize)] +// struct CreateSecretEnv { +// monitor_address: String, +// monitor_username: String, +// monitor_password: String, +// } -#[allow(unused)] -async fn create_secret() -> anyhow::Result<()> { - let env: CreateSecretEnv = envy::from_env()?; +// #[allow(unused)] +// async fn create_secret() -> anyhow::Result<()> { +// let env: CreateSecretEnv = envy::from_env()?; - let monitor = MonitorClient::new_with_new_account( - env.monitor_address, - env.monitor_username, - env.monitor_password, - ) - .await?; +// let monitor = MonitorClient::new_with_new_account( +// env.monitor_address, +// env.monitor_username, +// env.monitor_password, +// ) +// .await?; - let secret = monitor - .write(write::CreateLoginSecret { - name: "tests".to_string(), - expires: None, - }) - .await?; +// let secret = monitor +// .write(write::CreateLoginSecret { +// name: "tests".to_string(), +// expires: None, +// }) +// .await?; - println!("{secret:#?}"); +// println!("{secret:#?}"); - Ok(()) -} +// Ok(()) +// } diff --git a/bin/update_logger/src/main.rs b/bin/update_logger/src/main.rs index a516d03c7..746ee13b6 100644 --- a/bin/update_logger/src/main.rs +++ b/bin/update_logger/src/main.rs @@ -11,16 +11,16 @@ async fn app() -> anyhow::Result<()> { let monitor = MonitorClient::new_from_env().await?; - let (mut rx, _) = monitor.subscribe_to_updates(1000, 5); + // let (mut rx, _) = monitor.subscribe_to_updates(1000, 5); - loop { - let msg = rx.recv().await; - if let Err(e) = msg { - error!("🚨 recv error | {e:#?}"); - break; - } - info!("{msg:#?}") - } + // loop { + // let msg = rx.recv().await; + // if let Err(e) = msg { + // error!("🚨 recv error | {e:#?}"); + // break; + // } + // info!("{msg:#?}") + // } Ok(()) } diff --git a/client/rs/src/api/auth.rs b/client/rs/src/api/auth.rs index 86200a402..dd51b4588 100644 --- a/client/rs/src/api/auth.rs +++ b/client/rs/src/api/auth.rs @@ -80,22 +80,3 @@ pub struct ExchangeForJwtResponse { } // - -#[typeshare] -#[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, -)] -#[empty_traits(MonitorAuthRequest)] -#[response(LoginWithSecretResponse)] -pub struct LoginWithSecret { - pub username: String, - pub secret: String, -} - -#[typeshare] -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct LoginWithSecretResponse { - pub jwt: String, -} - -// diff --git a/client/rs/src/api/read/mod.rs b/client/rs/src/api/read/mod.rs index 2730cafaf..b7faded7b 100644 --- a/client/rs/src/api/read/mod.rs +++ b/client/rs/src/api/read/mod.rs @@ -27,7 +27,7 @@ pub use server::*; pub use tag::*; pub use update::*; -use crate::entities::{user::User, Timelength}; +use crate::entities::{api_key::ApiKey, user::User, Timelength}; pub trait MonitorReadRequest: HasResponse {} @@ -62,6 +62,19 @@ pub type GetUserResponse = User; // +#[typeshare] +#[derive( + Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, +)] +#[empty_traits(MonitorReadRequest)] +#[response(ListApiKeysResponse)] +pub struct ListApiKeys {} + +#[typeshare] +pub type ListApiKeysResponse = Vec; + +// + #[typeshare] #[derive( Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, diff --git a/client/rs/src/api/write/secret.rs b/client/rs/src/api/write/api_key.rs similarity index 61% rename from client/rs/src/api/write/secret.rs rename to client/rs/src/api/write/api_key.rs index 48bc11687..24550c5ac 100644 --- a/client/rs/src/api/write/secret.rs +++ b/client/rs/src/api/write/api_key.rs @@ -14,15 +14,22 @@ use super::MonitorWriteRequest; Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, )] #[empty_traits(MonitorWriteRequest)] -#[response(CreateLoginSecretResponse)] -pub struct CreateLoginSecret { +#[response(CreateApiKeyResponse)] +pub struct CreateApiKey { pub name: String, - pub expires: Option, + + #[serde(default)] + pub expires: I64, } #[typeshare] #[derive(Serialize, Deserialize, Debug, Clone)] -pub struct CreateLoginSecretResponse { +pub struct CreateApiKeyResponse { + /// X-API-KEY + pub key: String, + + /// X-API-SECRET + /// There is no way to get the secret again after it is distributed in this message pub secret: String, } @@ -33,11 +40,11 @@ pub struct CreateLoginSecretResponse { Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, )] #[empty_traits(MonitorWriteRequest)] -#[response(DeleteLoginSecretResponse)] -pub struct DeleteLoginSecret { - pub name: String, +#[response(DeleteApiKeyResponse)] +pub struct DeleteApiKey { + pub key: String, } #[typeshare] #[derive(Serialize, Deserialize, Debug, Clone)] -pub struct DeleteLoginSecretResponse {} +pub struct DeleteApiKeyResponse {} diff --git a/client/rs/src/api/write/mod.rs b/client/rs/src/api/write/mod.rs index ed886fd38..d2edc6aad 100644 --- a/client/rs/src/api/write/mod.rs +++ b/client/rs/src/api/write/mod.rs @@ -1,4 +1,5 @@ mod alerter; +mod api_key; mod build; mod builder; mod deployment; @@ -7,12 +8,12 @@ mod launch; mod permissions; mod procedure; mod repo; -mod secret; mod server; mod tags; mod user; pub use alerter::*; +pub use api_key::*; pub use build::*; pub use builder::*; pub use deployment::*; @@ -21,7 +22,6 @@ pub use launch::*; pub use permissions::*; pub use procedure::*; pub use repo::*; -pub use secret::*; pub use server::*; pub use tags::*; pub use user::*; diff --git a/client/rs/src/entities/api_key.rs b/client/rs/src/entities/api_key.rs new file mode 100644 index 000000000..0405166eb --- /dev/null +++ b/client/rs/src/entities/api_key.rs @@ -0,0 +1,37 @@ +use mongo_indexed::derive::MongoIndexed; +use serde::{Deserialize, Serialize}; +use typeshare::typeshare; + +use super::I64; + +#[typeshare] +#[derive( + Serialize, Deserialize, Debug, Clone, Default, MongoIndexed, +)] +pub struct ApiKey { + /// UNIQUE KEY ASSOCIATED WITH SECRET + #[unique_index] + pub key: String, + + /// HASH OF THE SECRET + pub secret: String, + + /// USER ASSOCIATED WITH THE API KEY + #[index] + pub user_id: String, + + /// NAME ASSOCIATED WITH THE API KEY FOR MANAGEMENT + pub name: String, + + /// TIMESTAMP OF KEY CREATION + pub created_at: I64, + + /// EXPIRY OF KEY, OR 0 IF NEVER EXPIRES + pub expires: I64, +} + +impl ApiKey { + pub fn sanitize(&mut self) { + self.secret.clear() + } +} diff --git a/client/rs/src/entities/mod.rs b/client/rs/src/entities/mod.rs index 07d7a354c..d5d03287f 100644 --- a/client/rs/src/entities/mod.rs +++ b/client/rs/src/entities/mod.rs @@ -8,6 +8,7 @@ use typeshare::typeshare; pub mod alert; pub mod alerter; +pub mod api_key; pub mod build; pub mod builder; pub mod config; diff --git a/client/rs/src/entities/procedure.rs b/client/rs/src/entities/procedure.rs index 390724aed..8734b1bac 100644 --- a/client/rs/src/entities/procedure.rs +++ b/client/rs/src/entities/procedure.rs @@ -49,5 +49,5 @@ impl From<&ProcedureConfig> for ProcedureConfigVariant { #[typeshare] #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct ProcedureActionState { - pub running: bool -} \ No newline at end of file + pub running: bool, +} diff --git a/client/rs/src/entities/user.rs b/client/rs/src/entities/user.rs index 6c322b54e..88971a822 100644 --- a/client/rs/src/entities/user.rs +++ b/client/rs/src/entities/user.rs @@ -38,9 +38,6 @@ pub struct User { pub avatar: Option, - #[serde(default)] - pub secrets: Vec, - pub password: Option, #[sparse_index] @@ -59,14 +56,9 @@ pub struct User { pub updated_at: I64, } -#[typeshare] -#[derive( - Serialize, Deserialize, Debug, Clone, Default, PartialEq, -)] -pub struct ApiSecret { - pub name: String, - #[serde(default, skip_serializing_if = "String::is_empty")] - pub hash: String, - pub created_at: I64, - pub expires: Option, +impl User { + /// Prepares user object for transport by removing any sensitive fields + pub fn sanitize(&mut self) { + self.password = None; + } } diff --git a/client/rs/src/lib.rs b/client/rs/src/lib.rs index 16d536cd9..181e82eab 100644 --- a/client/rs/src/lib.rs +++ b/client/rs/src/lib.rs @@ -1,4 +1,5 @@ -use anyhow::{anyhow, Context}; +use anyhow::Context; +use api::read::GetVersion; use serde::Deserialize; pub mod api; @@ -12,156 +13,46 @@ mod subscribe; #[derive(Deserialize)] struct MonitorEnv { monitor_address: String, - monitor_token: Option, - monitor_username: Option, - monitor_password: Option, - monitor_secret: Option, + monitor_api_key: String, + monitor_api_secret: String, } #[derive(Clone)] pub struct MonitorClient { reqwest: reqwest::Client, address: String, - jwt: String, - creds: Option, -} - -#[derive(Clone)] -struct RefreshTokenCreds { - username: String, + key: String, secret: String, } impl MonitorClient { - pub fn new_with_token( + pub async fn new( address: impl Into, - token: impl Into, - ) -> MonitorClient { - MonitorClient { - reqwest: Default::default(), - address: address.into(), - jwt: token.into(), - creds: None, - } - } - - pub async fn new_with_password( - address: impl Into, - username: impl Into, - password: impl Into, - ) -> anyhow::Result { - let mut client = MonitorClient { - reqwest: Default::default(), - address: address.into(), - jwt: Default::default(), - creds: None, - }; - - let api::auth::LoginLocalUserResponse { jwt } = client - .auth(api::auth::LoginLocalUser { - username: username.into(), - password: password.into(), - }) - .await?; - - client.jwt = jwt; - - Ok(client) - } - - pub async fn new_with_new_account( - address: impl Into, - username: impl Into, - password: impl Into, - ) -> anyhow::Result { - let mut client = MonitorClient { - reqwest: Default::default(), - address: address.into(), - jwt: Default::default(), - creds: None, - }; - - let api::auth::CreateLocalUserResponse { jwt } = client - .auth(api::auth::CreateLocalUser { - username: username.into(), - password: password.into(), - }) - .await?; - - client.jwt = jwt; - - Ok(client) - } - - pub async fn new_with_secret( - address: impl Into, - username: impl Into, + key: impl Into, secret: impl Into, ) -> anyhow::Result { - let mut client = MonitorClient { + let client = MonitorClient { reqwest: Default::default(), address: address.into(), - jwt: Default::default(), - creds: RefreshTokenCreds { - username: username.into(), - secret: secret.into(), - } - .into(), + key: key.into(), + secret: secret.into(), }; - - client.refresh_jwt().await?; - + client.read(GetVersion {}).await?; Ok(client) } pub async fn new_from_env() -> anyhow::Result { - let env = envy::from_env::() + let MonitorEnv { + monitor_address, + monitor_api_key, + monitor_api_secret, + } = envy::from_env() .context("failed to parse environment for monitor client")?; - if let Some(token) = env.monitor_token { - Ok(MonitorClient::new_with_token(&env.monitor_address, token)) - } else if let Some(password) = env.monitor_password { - let username = env.monitor_username.ok_or(anyhow!( - "must provide MONITOR_USERNAME to authenticate with MONITOR_PASSWORD" - ))?; - MonitorClient::new_with_password( - &env.monitor_address, - username, - password, - ) - .await - } else if let Some(secret) = env.monitor_secret { - let username = env.monitor_username.ok_or(anyhow!( - "must provide MONITOR_USERNAME to authenticate with MONITOR_SECRET" - ))?; - MonitorClient::new_with_secret( - &env.monitor_address, - username, - secret, - ) - .await - } else { - Err(anyhow!("failed to initialize monitor client from env | must provide one of: (MONITOR_TOKEN), (MONITOR_USERNAME and MONITOR_PASSWORD), (MONITOR_USERNAME and MONITOR_SECRET)")) - } - } - - pub async fn refresh_jwt(&mut self) -> anyhow::Result<()> { - if self.creds.is_none() { - return Err(anyhow!( - "only clients initialized using the secret login method can refresh their jwt" - )); - } - - let creds = self.creds.clone().unwrap(); - - let api::auth::LoginWithSecretResponse { jwt } = self - .auth(api::auth::LoginWithSecret { - username: creds.username, - secret: creds.secret, - }) - .await?; - - self.jwt = jwt; - - Ok(()) + MonitorClient::new( + monitor_address, + monitor_api_key, + monitor_api_secret, + ) + .await } } diff --git a/client/rs/src/request.rs b/client/rs/src/request.rs index fddb6be75..163c7f507 100644 --- a/client/rs/src/request.rs +++ b/client/rs/src/request.rs @@ -17,12 +17,11 @@ impl MonitorClient { &self, request: T, ) -> anyhow::Result { - let req_type = T::req_type(); self .post( "/auth", json!({ - "type": req_type, + "type": T::req_type(), "params": request }), ) @@ -33,13 +32,12 @@ impl MonitorClient { &self, request: T, ) -> anyhow::Result { - let req_type = T::req_type(); self .post( "/read", json!({ - "type": req_type, - "params": request + "type": T::req_type(), + "params": request }), ) .await @@ -49,13 +47,12 @@ impl MonitorClient { &self, request: T, ) -> anyhow::Result { - let req_type = T::req_type(); self .post( "/write", json!({ - "type": req_type, - "params": request + "type": T::req_type(), + "params": request }), ) .await @@ -65,13 +62,12 @@ impl MonitorClient { &self, request: T, ) -> anyhow::Result { - let req_type = T::req_type(); self .post( "/execute", json!({ - "type": req_type, - "params": request + "type": T::req_type(), + "params": request }), ) .await @@ -85,7 +81,8 @@ impl MonitorClient { let req = self .reqwest .post(format!("{}{endpoint}", self.address)) - .header("Authorization", format!("Bearer {}", self.jwt)); + .header("x-api-key", &self.key) + .header("x-api-secret", &self.secret); let req = if let Some(body) = body.into() { req.header("Content-Type", "application/json").json(&body) } else { diff --git a/client/rs/src/subscribe.rs b/client/rs/src/subscribe.rs index 9ad3e7f76..d75ff7a3e 100644 --- a/client/rs/src/subscribe.rs +++ b/client/rs/src/subscribe.rs @@ -1,185 +1,175 @@ use std::time::Duration; -use anyhow::Context; -use futures::{SinkExt, TryStreamExt}; -use serror::serialize_error; -use thiserror::Error; -use tokio::sync::broadcast; -use tokio_tungstenite::{connect_async, tungstenite::Message}; -use tokio_util::sync::CancellationToken; +// use anyhow::Context; +// use futures::{SinkExt, TryStreamExt}; +// use serror::serialize_error; +// use thiserror::Error; +// use tokio::sync::broadcast; +// use tokio_tungstenite::{connect_async, tungstenite::Message}; +// use tokio_util::sync::CancellationToken; -use crate::{entities::update::UpdateListItem, MonitorClient}; +// use crate::{entities::update::UpdateListItem, MonitorClient}; -#[derive(Debug, Clone)] -pub enum UpdateWsMessage { - Update(UpdateListItem), - Error(UpdateWsError), - Disconnected, - Reconnected, -} +// #[derive(Debug, Clone)] +// pub enum UpdateWsMessage { +// Update(UpdateListItem), +// Error(UpdateWsError), +// Disconnected, +// Reconnected, +// } -#[derive(Error, Debug, Clone)] -pub enum UpdateWsError { - #[error("failed to connect | {0}")] - ConnectionError(String), - #[error("failed to login | {0}")] - LoginError(String), - #[error("failed to recieve message | {0}")] - MessageError(String), - #[error("did not recognize message | {0}")] - MessageUnrecognized(String), -} +// #[derive(Error, Debug, Clone)] +// pub enum UpdateWsError { +// #[error("failed to connect | {0}")] +// ConnectionError(String), +// #[error("failed to login | {0}")] +// LoginError(String), +// #[error("failed to recieve message | {0}")] +// MessageError(String), +// #[error("did not recognize message | {0}")] +// MessageUnrecognized(String), +// } -impl MonitorClient { - pub fn subscribe_to_updates( - &self, - capacity: usize, - retry_cooldown_secs: u64, - ) -> (broadcast::Receiver, CancellationToken) { - let (tx, rx) = broadcast::channel(capacity); - let cancel = CancellationToken::new(); - let cancel_clone = cancel.clone(); - let address = - format!("{}/ws/update", self.address.replacen("http", "ws", 1)); - let mut client = self.clone(); +// impl MonitorClient { +// pub fn subscribe_to_updates( +// &self, +// capacity: usize, +// retry_cooldown_secs: u64, +// ) -> (broadcast::Receiver, CancellationToken) { +// let (tx, rx) = broadcast::channel(capacity); +// let cancel = CancellationToken::new(); +// let cancel_clone = cancel.clone(); +// let address = +// format!("{}/ws/update", self.address.replacen("http", "ws", 1)); +// let mut client = self.clone(); - tokio::spawn(async move { - loop { - // OUTER LOOP (LONG RECONNECT) - if cancel.is_cancelled() { - break; - } - loop { - // INNER LOOP (SHORT RECONNECT) - if cancel.is_cancelled() { - break; - } +// tokio::spawn(async move { +// loop { +// // OUTER LOOP (LONG RECONNECT) +// if cancel.is_cancelled() { +// break; +// } +// loop { +// // INNER LOOP (SHORT RECONNECT) +// if cancel.is_cancelled() { +// break; +// } - if client.creds.is_some() { - let res = client.refresh_jwt().await; - if let Err(e) = res { - let _ = tx.send(UpdateWsMessage::Error( - UpdateWsError::LoginError(serialize_error(e)), - )); - break; - } - } +// let res = connect_async(&address) +// .await +// .context("failed to connect to websocket endpoint"); - let res = connect_async(&address) - .await - .context("failed to connect to websocket endpoint"); +// if let Err(e) = res { +// let _ = tx.send(UpdateWsMessage::Error( +// UpdateWsError::ConnectionError(serialize_error(e)), +// )); +// break; +// } - if let Err(e) = res { - let _ = tx.send(UpdateWsMessage::Error( - UpdateWsError::ConnectionError(serialize_error(e)), - )); - break; - } +// let (mut ws, _) = res.unwrap(); - let (mut ws, _) = res.unwrap(); +// // ================== +// // SEND LOGIN MSG +// // ================== +// let login_send_res = ws +// .send(Message::Text(client.jwt.clone())) +// .await +// .context("failed to send login message"); - // ================== - // SEND LOGIN MSG - // ================== - let login_send_res = ws - .send(Message::Text(client.jwt.clone())) - .await - .context("failed to send login message"); +// if let Err(e) = login_send_res { +// let _ = tx.send(UpdateWsMessage::Error( +// UpdateWsError::LoginError(serialize_error(e)), +// )); +// break; +// } - if let Err(e) = login_send_res { - let _ = tx.send(UpdateWsMessage::Error( - UpdateWsError::LoginError(serialize_error(e)), - )); - break; - } +// // ================== +// // HANDLE LOGIN RES +// // ================== +// match ws.try_next().await { +// Ok(Some(Message::Text(msg))) => { +// if msg != "LOGGED_IN" { +// let _ = tx.send(UpdateWsMessage::Error( +// UpdateWsError::LoginError(msg), +// )); +// let _ = ws.close(None).await; +// break; +// } +// } +// Ok(Some(msg)) => { +// let _ = tx.send(UpdateWsMessage::Error( +// UpdateWsError::LoginError(format!("{msg:#?}")), +// )); +// let _ = ws.close(None).await; +// break; +// } +// Ok(None) => { +// let _ = tx.send(UpdateWsMessage::Error( +// UpdateWsError::LoginError(String::from( +// "got None message after login message", +// )), +// )); +// let _ = ws.close(None).await; +// break; +// } +// Err(e) => { +// let _ = tx.send(UpdateWsMessage::Error( +// UpdateWsError::LoginError(format!( +// "failed to recieve message | {e:#?}" +// )), +// )); +// let _ = ws.close(None).await; +// break; +// } +// } - // ================== - // HANDLE LOGIN RES - // ================== - match ws.try_next().await { - Ok(Some(Message::Text(msg))) => { - if msg != "LOGGED_IN" { - let _ = tx.send(UpdateWsMessage::Error( - UpdateWsError::LoginError(msg), - )); - let _ = ws.close(None).await; - break; - } - } - Ok(Some(msg)) => { - let _ = tx.send(UpdateWsMessage::Error( - UpdateWsError::LoginError(format!("{msg:#?}")), - )); - let _ = ws.close(None).await; - break; - } - Ok(None) => { - let _ = tx.send(UpdateWsMessage::Error( - UpdateWsError::LoginError(String::from( - "got None message after login message", - )), - )); - let _ = ws.close(None).await; - break; - } - Err(e) => { - let _ = tx.send(UpdateWsMessage::Error( - UpdateWsError::LoginError(format!( - "failed to recieve message | {e:#?}" - )), - )); - let _ = ws.close(None).await; - break; - } - } +// let _ = tx.send(UpdateWsMessage::Reconnected); - let _ = tx.send(UpdateWsMessage::Reconnected); +// // ================== +// // HANLDE MSGS +// // ================== +// loop { +// match ws +// .try_next() +// .await +// .context("failed to recieve message") +// { +// Ok(Some(Message::Text(msg))) => { +// match serde_json::from_str::(&msg) { +// Ok(msg) => { +// let _ = tx.send(UpdateWsMessage::Update(msg)); +// } +// Err(_) => { +// let _ = tx.send(UpdateWsMessage::Error( +// UpdateWsError::MessageUnrecognized(msg), +// )); +// } +// } +// } +// Ok(Some(Message::Close(_))) => { +// let _ = tx.send(UpdateWsMessage::Disconnected); +// let _ = ws.close(None).await; +// break; +// } +// Err(e) => { +// let _ = tx.send(UpdateWsMessage::Error( +// UpdateWsError::MessageError(serialize_error(e)), +// )); +// let _ = tx.send(UpdateWsMessage::Disconnected); +// let _ = ws.close(None).await; +// break; +// } +// Ok(_) => { +// // ignore +// } +// } +// } +// } +// tokio::time::sleep(Duration::from_secs(retry_cooldown_secs)) +// .await; +// } +// }); - // ================== - // HANLDE MSGS - // ================== - loop { - match ws - .try_next() - .await - .context("failed to recieve message") - { - Ok(Some(Message::Text(msg))) => { - match serde_json::from_str::(&msg) { - Ok(msg) => { - let _ = tx.send(UpdateWsMessage::Update(msg)); - } - Err(_) => { - let _ = tx.send(UpdateWsMessage::Error( - UpdateWsError::MessageUnrecognized(msg), - )); - } - } - } - Ok(Some(Message::Close(_))) => { - let _ = tx.send(UpdateWsMessage::Disconnected); - let _ = ws.close(None).await; - break; - } - Err(e) => { - let _ = tx.send(UpdateWsMessage::Error( - UpdateWsError::MessageError(serialize_error(e)), - )); - let _ = tx.send(UpdateWsMessage::Disconnected); - let _ = ws.close(None).await; - break; - } - Ok(_) => { - // ignore - } - } - } - } - tokio::time::sleep(Duration::from_secs(retry_cooldown_secs)) - .await; - } - }); - - (rx, cancel_clone) - } -} +// (rx, cancel_clone) +// } +// } diff --git a/lib/db_client/src/lib.rs b/lib/db_client/src/lib.rs index 2e73a4dd2..fe350bd5e 100644 --- a/lib/db_client/src/lib.rs +++ b/lib/db_client/src/lib.rs @@ -5,6 +5,7 @@ use mongo_indexed::{create_index, create_unique_index, Indexed}; use monitor_client::entities::{ alert::Alert, alerter::Alerter, + api_key::ApiKey, build::Build, builder::Builder, config::MongoConfig, @@ -23,6 +24,7 @@ use mungos::{ pub struct DbClient { pub users: Collection, + pub api_keys: Collection, pub tags: Collection, pub updates: Collection, pub alerts: Collection, @@ -76,6 +78,7 @@ impl DbClient { let client = DbClient { users: User::collection(&db, true).await?, + api_keys: ApiKey::collection(&db, true).await?, tags: CustomTag::collection(&db, true).await?, updates: Update::collection(&db, true).await?, alerts: Alert::collection(&db, true).await?,