ws login support api keys

This commit is contained in:
mbecker20
2024-01-12 23:40:47 -08:00
parent 1c7a159e40
commit 2ec5789d71
5 changed files with 178 additions and 112 deletions

View File

@@ -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

View File

@@ -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"))
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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 {

View File

@@ -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};