mirror of
https://github.com/moghtech/komodo.git
synced 2026-03-11 17:44:19 -05:00
ws login support api keys
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -185,26 +185,8 @@ impl State {
|
||||
&self,
|
||||
jwt: &str,
|
||||
) -> anyhow::Result<RequestUser> {
|
||||
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<RequestUser> {
|
||||
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<RequestUser> {
|
||||
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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<WsLoginMessage> {
|
||||
serde_json::from_str(json)
|
||||
.context("failed to parse json as WsLoginMessage")
|
||||
}
|
||||
|
||||
pub fn to_json_string(&self) -> anyhow::Result<String> {
|
||||
serde_json::to_string(self)
|
||||
.context("failed to serialize WsLoginMessage to json string")
|
||||
}
|
||||
}
|
||||
|
||||
// use std::time::Duration;
|
||||
|
||||
// use anyhow::Context;
|
||||
// use futures::{SinkExt, TryStreamExt};
|
||||
Reference in New Issue
Block a user