Harden auth/vault path sanitization and clean WeKnora docs
This commit is contained in:
@@ -53,6 +53,21 @@ async fn events_with_stream_delta(
|
||||
let state_for_stream = state.clone();
|
||||
let context_for_stream = context.clone();
|
||||
let query_for_stream = query.clone();
|
||||
let subscription_workspace = query
|
||||
.workspace_id
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(str::to_string)
|
||||
.or_else(|| {
|
||||
context
|
||||
.workspace
|
||||
.workspace_id
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(str::to_string)
|
||||
});
|
||||
let stream = stream::unfold(
|
||||
Some(StreamPollState {
|
||||
app_state: state_for_stream,
|
||||
@@ -64,6 +79,7 @@ async fn events_with_stream_delta(
|
||||
initial_emitted: false,
|
||||
block_delta_rx,
|
||||
stream_delta_rx,
|
||||
subscription_workspace,
|
||||
}),
|
||||
move |state| async move {
|
||||
let mut state = state?;
|
||||
@@ -76,11 +92,17 @@ async fn events_with_stream_delta(
|
||||
));
|
||||
}
|
||||
|
||||
// Check block.delta broadcast first
|
||||
// Check block.delta broadcast first(按订阅 workspace 过滤,防跨工作区泄露)
|
||||
if let Some(ref mut rx) = state.block_delta_rx {
|
||||
match rx.try_recv() {
|
||||
Ok(payload) => {
|
||||
return Some((Ok(stream_event("block.delta", &payload)), Some(state)));
|
||||
if delta_matches_workspace(
|
||||
&payload,
|
||||
state.subscription_workspace.as_deref(),
|
||||
) {
|
||||
return Some((Ok(stream_event("block.delta", &payload)), Some(state)));
|
||||
}
|
||||
// 非本工作区:丢弃并继续同一 tick 的后续检查
|
||||
}
|
||||
Err(tokio::sync::broadcast::error::TryRecvError::Empty) => {}
|
||||
Err(tokio::sync::broadcast::error::TryRecvError::Closed) => {
|
||||
@@ -94,12 +116,17 @@ async fn events_with_stream_delta(
|
||||
if let Some(ref mut rx) = state.stream_delta_rx {
|
||||
match rx.try_recv() {
|
||||
Ok(payload) => {
|
||||
let hint = build_stream_push_delta_hint(
|
||||
if delta_matches_workspace(
|
||||
&payload,
|
||||
&state.context.trace.request_id,
|
||||
&state.context.trace.trace_id,
|
||||
);
|
||||
return Some((Ok(stream_event("delta", &hint)), Some(state)));
|
||||
state.subscription_workspace.as_deref(),
|
||||
) {
|
||||
let hint = build_stream_push_delta_hint(
|
||||
&payload,
|
||||
&state.context.trace.request_id,
|
||||
&state.context.trace.trace_id,
|
||||
);
|
||||
return Some((Ok(stream_event("delta", &hint)), Some(state)));
|
||||
}
|
||||
}
|
||||
Err(tokio::sync::broadcast::error::TryRecvError::Empty) => {}
|
||||
Err(tokio::sync::broadcast::error::TryRecvError::Closed) => {
|
||||
@@ -184,11 +211,19 @@ async fn events_with_stream_delta(
|
||||
state.polls += 1;
|
||||
sleep(Duration::from_millis(poll_ms)).await;
|
||||
|
||||
// Check block.delta after poll sleep
|
||||
// Check block.delta after poll sleep(同样按 workspace 过滤)
|
||||
if let Some(ref mut rx) = state.block_delta_rx {
|
||||
match rx.try_recv() {
|
||||
Ok(payload) => {
|
||||
return Some((Ok(stream_event("block.delta", &payload)), Some(state)));
|
||||
if delta_matches_workspace(
|
||||
&payload,
|
||||
state.subscription_workspace.as_deref(),
|
||||
) {
|
||||
return Some((
|
||||
Ok(stream_event("block.delta", &payload)),
|
||||
Some(state),
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(tokio::sync::broadcast::error::TryRecvError::Empty) => {}
|
||||
Err(tokio::sync::broadcast::error::TryRecvError::Closed) => {
|
||||
@@ -305,6 +340,31 @@ struct StreamPollState {
|
||||
#[allow(dead_code)]
|
||||
block_delta_rx: Option<tokio::sync::broadcast::Receiver<Value>>,
|
||||
stream_delta_rx: Option<tokio::sync::broadcast::Receiver<Value>>,
|
||||
/// 订阅工作区;broadcast 推送仅转发匹配项,避免跨工作区泄露。
|
||||
subscription_workspace: Option<String>,
|
||||
}
|
||||
|
||||
/// 仅转发与当前订阅 workspace 一致的 delta(与 ws.rs 同策略)。
|
||||
fn delta_matches_workspace(delta: &Value, subscription_workspace: Option<&str>) -> bool {
|
||||
let Some(expected) = subscription_workspace else {
|
||||
// 无订阅 workspace 时不推送带 workspace 的全局 delta(保守)
|
||||
return delta
|
||||
.get("workspaceId")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(str::trim)
|
||||
.filter(|v| !v.is_empty())
|
||||
.is_none();
|
||||
};
|
||||
match delta
|
||||
.get("workspaceId")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(str::trim)
|
||||
.filter(|v| !v.is_empty())
|
||||
{
|
||||
Some(delta_ws) => delta_ws == expected,
|
||||
// 无 workspace 标记的 delta 不转发(避免跨租户噪声)
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn live_poll_query(query: &StreamSnapshotQuery) -> StreamSnapshotQuery {
|
||||
@@ -368,6 +428,7 @@ mod tests {
|
||||
dev_user_id: "dev-user".into(),
|
||||
dev_user_name: "开发用户".into(),
|
||||
dev_user_email: "dev@mnote.local".into(),
|
||||
environment: "dev".into(),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user