This commit is contained in:
Mateusz Gruszczyński
2026-07-22 11:15:01 +02:00
parent 1be2023e36
commit 8bf45938ea
19 changed files with 189 additions and 33 deletions
+13 -7
View File
@@ -1,7 +1,7 @@
use axum::{extract::{ws::{Message, WebSocket}, Path, State, WebSocketUpgrade}, response::Response};
use futures_util::{SinkExt, StreamExt};
use serde::{Deserialize, Serialize};
use tracing::{debug, warn};
use tracing::{debug, info, warn};
use crate::{auth, db, state::{NoteUpdate, SharedState}};
#[derive(Debug, Deserialize)]
@@ -24,8 +24,9 @@ pub async fn upgrade(ws: WebSocketUpgrade, Path((workspace_slug, note_slug)): Pa
}
async fn handle_socket(mut socket: WebSocket, state: SharedState, workspace_slug: String, note_slug: String) {
let Some(workspace) = db::find_workspace(&state.db, &workspace_slug).await.ok().flatten() else { let _=send_error(&mut socket,"Workspace not found").await; return; };
let Some(note) = db::find_note(&state.db, workspace.id, &note_slug).await.ok().flatten() else { let _=send_error(&mut socket,"Note not found").await; return; };
info!(%workspace_slug, %note_slug, "note websocket connected");
let Some(workspace) = db::find_workspace(&state.db, &workspace_slug).await.ok().flatten() else { warn!(%workspace_slug, %note_slug, "note websocket rejected: workspace not found"); let _=send_error(&mut socket,"Workspace not found").await; return; };
let Some(note) = db::find_note(&state.db, workspace.id, &note_slug).await.ok().flatten() else { warn!(%workspace_slug, %note_slug, "note websocket rejected: note not found"); let _=send_error(&mut socket,"Note not found").await; return; };
let (password, nickname, session_token) = match socket.recv().await {
Some(Ok(Message::Text(text))) => match serde_json::from_str::<ClientMessage>(&text) {
Ok(ClientMessage::Authenticate { password, nickname, session_token }) => (password, clean_nickname(nickname), session_token),
@@ -33,7 +34,8 @@ async fn handle_socket(mut socket: WebSocket, state: SharedState, workspace_slug
}, _ => return
};
let nickname = match auth::authorize_nickname(&state, nickname, session_token).await { Ok(value) => value, Err(message) => { let _=send_error(&mut socket,&message).await; return; } };
if !db::verify_workspace_password(&workspace, password.as_deref()) { let _=send_error(&mut socket,"Invalid password").await; return; }
if !db::verify_workspace_password(&workspace, password.as_deref()) { warn!(workspace_id = workspace.id, note_id = note.id, "note websocket rejected: invalid workspace password"); let _=send_error(&mut socket,"Invalid password").await; return; }
info!(workspace_id = workspace.id, note_id = note.id, nickname = ?nickname, "note websocket authenticated");
if send(&mut socket,&ServerMessage::Authenticated { workspace_title:workspace.title.clone(), note_title:note.title.clone(), content:note.content.clone(), owner_map:note.owner_map.clone() }).await.is_err(){return;}
let channel=state.note_channel(&workspace_slug,&note_slug).await;
let mut updates=channel.subscribe();
@@ -46,7 +48,7 @@ async fn handle_socket(mut socket: WebSocket, state: SharedState, workspace_slug
let owner_map=owner_map.unwrap_or_else(||"[]".into());
match db::save_revision(&state.db,note.id,workspace.id,&content,nickname.as_deref(),&owner_map).await {
Ok((revision_id,updated_at))=>{let _=channel.send(NoteUpdate{content,revision_id,updated_at,author:nickname.clone(),owner_map});}
Err(error)=>warn!(%error,"failed to save revision"),
Err(error)=>warn!(%error, workspace_id = workspace.id, note_id = note.id, "failed to save revision"),
}
}
Ok(ClientMessage::Authenticate{..})=>{}, Err(error)=>warn!(%error,"invalid websocket message"),
@@ -59,6 +61,7 @@ async fn handle_socket(mut socket: WebSocket, state: SharedState, workspace_slug
Err(tokio::sync::broadcast::error::RecvError::Closed)=>break,
}
}}
info!(workspace_id = workspace.id, note_id = note.id, "note websocket disconnected");
}
fn clean_nickname(value: Option<String>)->Option<String>{value.map(|v|v.trim().chars().take(40).collect::<String>()).filter(|v|!v.is_empty())}
async fn send_error(socket:&mut WebSocket,message:&str)->Result<(),axum::Error>{send(socket,&ServerMessage::Error{message:message.into()}).await}
@@ -76,7 +79,8 @@ pub async fn upgrade_pad(ws:WebSocketUpgrade,Path(slug):Path<String>,State(state
ws.on_upgrade(move|socket|handle_pad_socket(socket,state,slug))
}
async fn handle_pad_socket(mut socket:WebSocket,state:SharedState,slug:String){
let Some(pad)=db::find_pad(&state.db,&slug).await.ok().flatten() else {let _=send_pad(&mut socket,&PadServerMessage::Error{message:"Note not found".into()}).await;return;};
info!(%slug, "pad websocket connected");
let Some(pad)=db::find_pad(&state.db,&slug).await.ok().flatten() else {warn!(%slug, "pad websocket rejected: pad not found");let _=send_pad(&mut socket,&PadServerMessage::Error{message:"Note not found".into()}).await;return;};
let (password,nickname,session_token)=match socket.recv().await{
Some(Ok(Message::Text(text)))=>match serde_json::from_str::<ClientMessage>(&text){
Ok(ClientMessage::Authenticate{password,nickname,session_token})=>(password,clean_nickname(nickname),session_token),
@@ -84,7 +88,8 @@ async fn handle_pad_socket(mut socket:WebSocket,state:SharedState,slug:String){
},_=>return
};
let nickname=match auth::authorize_nickname(&state,nickname,session_token).await{Ok(value)=>value,Err(message)=>{let _=send_pad(&mut socket,&PadServerMessage::Error{message}).await;return;}};
if !db::verify_pad_password(&pad,password.as_deref()){let _=send_pad(&mut socket,&PadServerMessage::Error{message:"Invalid password".into()}).await;return;}
if !db::verify_pad_password(&pad,password.as_deref()){warn!(pad_id = pad.id, "pad websocket rejected: invalid password");let _=send_pad(&mut socket,&PadServerMessage::Error{message:"Invalid password".into()}).await;return;}
info!(pad_id = pad.id, nickname = ?nickname, "pad websocket authenticated");
if send_pad(&mut socket,&PadServerMessage::Authenticated{title:pad.title.clone(),content:pad.content.clone(),owner_map:pad.owner_map.clone()}).await.is_err(){return;}
let channel=state.pad_channel(&slug).await;
let mut updates=channel.subscribe();
@@ -112,6 +117,7 @@ async fn handle_pad_socket(mut socket:WebSocket,state:SharedState,slug:String){
Err(tokio::sync::broadcast::error::RecvError::Closed)=>break,
}
}}
info!(pad_id = pad.id, "pad websocket disconnected");
}
async fn send_pad(socket:&mut WebSocket,message:&PadServerMessage)->Result<(),axum::Error>{socket.send(Message::Text(serde_json::to_string(message).unwrap().into())).await}
async fn send_pad_split(sender:&mut futures_util::stream::SplitSink<WebSocket,Message>,message:&PadServerMessage)->Result<(),axum::Error>{sender.send(Message::Text(serde_json::to_string(message).unwrap().into())).await}