Skip to content

Commit ebb468f

Browse files
committed
fix: add events/broadcast route to platform bin + secure with BROADCAST_SECRET
1 parent c09cd79 commit ebb468f

File tree

2 files changed

+36
-2
lines changed

2 files changed

+36
-2
lines changed

bins/platform/src/server.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,11 @@ pub async fn run(args: ServerArgs) -> Result<()> {
189189
"/api/v1/bridge/:challenge/*path",
190190
any(api::bridge::bridge_to_challenge),
191191
)
192+
// Events API - Broadcast to validators (requires BROADCAST_SECRET)
193+
.route(
194+
"/api/v1/events/broadcast",
195+
post(api::events::broadcast_event),
196+
)
192197
// Submissions API
193198
.route(
194199
"/api/v1/submissions",

crates/platform-server/src/api/events.rs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
11
//! Challenge Events API
22
//!
33
//! Allows challenges to broadcast custom events to validators via WebSocket.
4+
//! Secured with shared secret to prevent unauthorized broadcasts.
45
56
use crate::models::{ChallengeCustomEvent, WsEvent};
67
use crate::state::AppState;
7-
use axum::{extract::State, http::StatusCode, Json};
8+
use axum::{
9+
extract::State,
10+
http::{HeaderMap, StatusCode},
11+
Json,
12+
};
813
use serde::{Deserialize, Serialize};
914
use std::sync::Arc;
10-
use tracing::info;
15+
use tracing::{info, warn};
16+
17+
/// Shared secret for challenge event broadcasts (set via BROADCAST_SECRET env var)
18+
/// Challenges must include this in X-Broadcast-Secret header
19+
fn get_broadcast_secret() -> Option<String> {
20+
std::env::var("BROADCAST_SECRET").ok()
21+
}
1122

1223
#[derive(Debug, Deserialize)]
1324
pub struct BroadcastEventRequest {
@@ -30,10 +41,28 @@ pub struct BroadcastEventResponse {
3041
///
3142
/// Called by challenge containers to notify validators of events.
3243
/// Validators filter events by challenge_id to receive only relevant ones.
44+
///
45+
/// Requires X-Broadcast-Secret header matching BROADCAST_SECRET env var.
3346
pub async fn broadcast_event(
3447
State(state): State<Arc<AppState>>,
48+
headers: HeaderMap,
3549
Json(req): Json<BroadcastEventRequest>,
3650
) -> Result<Json<BroadcastEventResponse>, (StatusCode, String)> {
51+
// Verify broadcast secret
52+
if let Some(expected_secret) = get_broadcast_secret() {
53+
let provided_secret = headers
54+
.get("X-Broadcast-Secret")
55+
.and_then(|v| v.to_str().ok())
56+
.unwrap_or("");
57+
58+
if provided_secret != expected_secret {
59+
warn!(
60+
"Unauthorized broadcast attempt for challenge: {}",
61+
req.challenge_id
62+
);
63+
return Err((StatusCode::UNAUTHORIZED, "Invalid broadcast secret".into()));
64+
}
65+
}
3766
// Create the custom event
3867
let event = ChallengeCustomEvent {
3968
challenge_id: req.challenge_id.clone(),

0 commit comments

Comments
 (0)