diff --git a/bin/core/src/auth/google/client.rs b/bin/core/src/auth/google/client.rs index 80fc7f7e7..9c3915761 100644 --- a/bin/core/src/auth/google/client.rs +++ b/bin/core/src/auth/google/client.rs @@ -63,9 +63,9 @@ impl GoogleOauthClient { pub async fn get_login_redirect_url(&self) -> String { let state = random_string(40); let redirect_url = format!( - "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&state={state}&client_id={}&redirect_uri={}&scope={}", - self.client_id, self.redirect_uri, self.scopes - ); + "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&state={state}&client_id={}&redirect_uri={}&scope={}", + self.client_id, self.redirect_uri, self.scopes + ); let mut states = self.states.lock().await; states.push(state); redirect_url diff --git a/bin/core/src/auth/jwt.rs b/bin/core/src/auth/jwt.rs index 7b802c231..11327151f 100644 --- a/bin/core/src/auth/jwt.rs +++ b/bin/core/src/auth/jwt.rs @@ -185,26 +185,8 @@ impl State { &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() { - let user = self.get_user(&claims.id).await?; - if user.enabled { - let user = InnerRequestUser { - id: claims.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")) - } - } else { - Err(anyhow!("token has expired")) - } + let user_id = self.auth_jwt_get_user_id(jwt).await?; + self.check_enabled(user_id).await } pub async fn auth_api_key_get_user_id( @@ -232,4 +214,32 @@ impl State { Err(anyhow!("invalid api secret")) } } + + pub async fn auth_api_key_check_enabled( + &self, + key: &str, + secret: &str, + ) -> anyhow::Result { + let user_id = self.auth_api_key_get_user_id(key, secret).await?; + self.check_enabled(user_id).await + } + + async fn check_enabled( + &self, + user_id: String, + ) -> anyhow::Result { + 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")) + } + } } diff --git a/bin/core/src/ws.rs b/bin/core/src/ws.rs index 805d53d97..424edf8f7 100644 --- a/bin/core/src/ws.rs +++ b/bin/core/src/ws.rs @@ -23,6 +23,7 @@ use monitor_client::{ PermissionLevel, }, permissioned::Permissioned, + ws::WsLoginMessage, }; use mungos::by_id::find_one_by_id; use serde_json::json; @@ -45,73 +46,73 @@ async fn ws_handler( ) -> impl IntoResponse { let mut receiver = state.update.receiver.resubscribe(); ws.on_upgrade(|socket| async move { - let login_res = state.ws_login(socket).await; - if login_res.is_none() { + let login_res = state.ws_login(socket).await; + if login_res.is_none() { + return; + } + + let (socket, user) = login_res.unwrap(); + let (mut ws_sender, mut ws_reciever) = socket.split(); + let cancel = CancellationToken::new(); + let cancel_clone = cancel.clone(); + + tokio::spawn(async move { + loop { + let update = select! { + _ = cancel_clone.cancelled() => break, + update = receiver.recv() => {update.expect("failed to recv update msg")} + }; + let user = find_one_by_id(&state.db.users, &user.id).await; + let user = match user { + Err(e) => { + let _ = ws_sender + .send(Message::Text(json!({ "type": "INVALID_USER", "msg": format!("{e:#?}") }).to_string())) + .await; + let _ = ws_sender.close().await; return; + }, + Ok(None) => { + let _ = ws_sender + .send(Message::Text(json!({ "type": "INVALID_USER", "msg": "user not found" }).to_string())) + .await; + let _ = ws_sender.close().await; + return + }, + Ok(Some(user)) if !user.enabled => { + let _ = ws_sender + .send(Message::Text(json!({ "type": "INVALID_USER", "msg": "user not enabled" }).to_string())) + .await; + let _ = ws_sender.close().await; + return + } + Ok(Some(user)) => user + + }; + let res = state + .user_can_see_update(&user, &update.target) + .await; + if res.is_ok() { + let _ = ws_sender + .send(Message::Text(serde_json::to_string(&update).unwrap())) + .await; } + } + }); - let (socket, user) = login_res.unwrap(); - let (mut ws_sender, mut ws_reciever) = socket.split(); - let cancel = CancellationToken::new(); - let cancel_clone = cancel.clone(); - - tokio::spawn(async move { - loop { - let update = select! { - _ = cancel_clone.cancelled() => break, - update = receiver.recv() => {update.expect("failed to recv update msg")} - }; - let user = find_one_by_id(&state.db.users, &user.id).await; - let user = match user { - Err(e) => { - let _ = ws_sender - .send(Message::Text(json!({ "type": "INVALID_USER", "msg": format!("{e:#?}") }).to_string())) - .await; - let _ = ws_sender.close().await; - return; - }, - Ok(None) => { - let _ = ws_sender - .send(Message::Text(json!({ "type": "INVALID_USER", "msg": "user not found" }).to_string())) - .await; - let _ = ws_sender.close().await; - return - }, - Ok(Some(user)) if !user.enabled => { - let _ = ws_sender - .send(Message::Text(json!({ "type": "INVALID_USER", "msg": "user not enabled" }).to_string())) - .await; - let _ = ws_sender.close().await; - return - } - Ok(Some(user)) => user - - }; - let res = state - .user_can_see_update(&user, &update.target) - .await; - if res.is_ok() { - let _ = ws_sender - .send(Message::Text(serde_json::to_string(&update).unwrap())) - .await; - } - } - }); - - while let Some(msg) = ws_reciever.next().await { - match msg { - Ok(msg) => { - if let Message::Close(_) = msg { - cancel.cancel(); - return; - } - } - Err(_) => { - cancel.cancel(); - return; - } - } + while let Some(msg) = ws_reciever.next().await { + match msg { + Ok(msg) => { + if let Message::Close(_) = msg { + cancel.cancel(); + return; + } } + Err(_) => { + cancel.cancel(); + return; + } + } + } }) } @@ -120,10 +121,11 @@ impl State { &self, mut socket: WebSocket, ) -> Option<(WebSocket, RequestUser)> { - if let Some(jwt) = socket.recv().await { - match jwt { - Ok(jwt) => match jwt { - Message::Text(jwt) => { + match socket.recv().await { + Some(Ok(Message::Text(login_msg))) => { + // login + match WsLoginMessage::from_json_str(&login_msg) { + Ok(WsLoginMessage::Jwt { jwt }) => { match self.auth_jwt_check_enabled(&jwt).await { Ok(user) => { let _ = socket @@ -134,7 +136,7 @@ impl State { Err(e) => { let _ = socket .send(Message::Text(format!( - "failed to authenticate user | {e:#?}" + "failed to authenticate user using jwt | {e:#?}" ))) .await; let _ = socket.close().await; @@ -142,34 +144,64 @@ impl State { } } } - msg => { + Ok(WsLoginMessage::ApiKeys { key, secret }) => { + match self.auth_api_key_check_enabled(&key, &secret).await + { + Ok(user) => { + let _ = socket + .send(Message::Text("LOGGED_IN".to_string())) + .await; + Some((socket, user)) + } + Err(e) => { + let _ = socket + .send(Message::Text(format!( + "failed to authenticate user using api keys | {e:#?}" + ))) + .await; + let _ = socket.close().await; + None + } + } + } + Err(e) => { let _ = socket .send(Message::Text(format!( - "invalid login msg: {msg:#?}" + "failed to parse login message: {e:#?}" ))) .await; let _ = socket.close().await; None } - }, - Err(e) => { - let _ = socket - .send(Message::Text(format!( - "failed to get jwt message: {e:#?}" - ))) - .await; - let _ = socket.close().await; - None } } - } else { - let _ = socket - .send(Message::Text(String::from( - "failed to get jwt message", - ))) - .await; - let _ = socket.close().await; - None + Some(Ok(msg)) => { + let _ = socket + .send(Message::Text(format!( + "invalid login message: {msg:#?}" + ))) + .await; + let _ = socket.close().await; + None + } + Some(Err(e)) => { + let _ = socket + .send(Message::Text(format!( + "failed to get login message: {e:#?}" + ))) + .await; + let _ = socket.close().await; + None + } + None => { + let _ = socket + .send(Message::Text(String::from( + "failed to get login message", + ))) + .await; + let _ = socket.close().await; + None + } } } diff --git a/client/rs/src/lib.rs b/client/rs/src/lib.rs index 181e82eab..be940cd04 100644 --- a/client/rs/src/lib.rs +++ b/client/rs/src/lib.rs @@ -6,9 +6,9 @@ pub mod api; pub mod busy; pub mod entities; pub mod permissioned; +pub mod ws; mod request; -mod subscribe; #[derive(Deserialize)] struct MonitorEnv { diff --git a/client/rs/src/subscribe.rs b/client/rs/src/ws.rs similarity index 89% rename from client/rs/src/subscribe.rs rename to client/rs/src/ws.rs index d75ff7a3e..6003e6e5b 100644 --- a/client/rs/src/subscribe.rs +++ b/client/rs/src/ws.rs @@ -1,4 +1,28 @@ -use std::time::Duration; +use anyhow::Context; +use serde::{Deserialize, Serialize}; +use typeshare::typeshare; + +#[typeshare] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type", content = "params")] +pub enum WsLoginMessage { + Jwt { jwt: String }, + ApiKeys { key: String, secret: String }, +} + +impl WsLoginMessage { + pub fn from_json_str(json: &str) -> anyhow::Result { + serde_json::from_str(json) + .context("failed to parse json as WsLoginMessage") + } + + pub fn to_json_string(&self) -> anyhow::Result { + serde_json::to_string(self) + .context("failed to serialize WsLoginMessage to json string") + } +} + +// use std::time::Duration; // use anyhow::Context; // use futures::{SinkExt, TryStreamExt};