diff options
| author | soryu <soryu@soryu.co> | 2026-02-06 02:08:37 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-02-06 02:08:37 +0000 |
| commit | 25e1275af1b742cc7866fba91152d9a4734a6f94 (patch) | |
| tree | e92c7e168f4e73c302fb63217ea20bf8cfa2ba7e /makima/src/server/handlers/directives.rs | |
| parent | 8f725a7c64fbeb85ebeb59b54d2f774e9a0a59d6 (diff) | |
| download | soryu-25e1275af1b742cc7866fba91152d9a4734a6f94.tar.gz soryu-25e1275af1b742cc7866fba91152d9a4734a6f94.zip | |
Fix: Directives API
Diffstat (limited to 'makima/src/server/handlers/directives.rs')
| -rw-r--r-- | makima/src/server/handlers/directives.rs | 85 |
1 files changed, 83 insertions, 2 deletions
diff --git a/makima/src/server/handlers/directives.rs b/makima/src/server/handlers/directives.rs index 6f6c3f1..4a78ab5 100644 --- a/makima/src/server/handlers/directives.rs +++ b/makima/src/server/handlers/directives.rs @@ -39,6 +39,14 @@ pub struct ListEventsQuery { pub limit: Option<i64>, } +/// Query parameters for SSE stream authentication +/// EventSource API cannot set custom headers, so auth is passed via query params +#[derive(Debug, Deserialize)] +pub struct StreamAuthQuery { + pub token: Option<String>, + pub api_key: Option<String>, +} + /// Query parameters for listing evaluations #[derive(Debug, Deserialize)] pub struct ListEvaluationsQuery { @@ -117,7 +125,14 @@ pub async fn list_directives( match repository::list_directives_for_owner(pool, auth.owner_id, params.status.as_deref()).await { - Ok(directives) => Json(directives).into_response(), + Ok(directives) => { + let total = directives.len() as i64; + Json(serde_json::json!({ + "directives": directives, + "total": total, + })) + .into_response() + } Err(e) => { tracing::error!("Failed to list directives: {}", e); ( @@ -1052,10 +1067,13 @@ pub async fn list_events( /// SSE stream of events for a directive /// GET /api/v1/directives/:id/events/stream +/// +/// EventSource API cannot set custom headers, so authentication is accepted +/// via query parameters: ?token=<jwt> or ?api_key=<key> pub async fn stream_events( State(state): State<SharedState>, - Authenticated(auth): Authenticated, Path(id): Path<Uuid>, + Query(auth_params): Query<StreamAuthQuery>, ) -> impl IntoResponse { let Some(ref pool) = state.db_pool else { return ( @@ -1065,6 +1083,69 @@ pub async fn stream_events( .into_response(); }; + // Authenticate via query params (EventSource cannot set headers) + let auth = if let Some(ref token) = auth_params.token { + // JWT token + let verifier = match state.jwt_verifier.as_ref() { + Some(v) => v, + None => { + return ( + StatusCode::UNAUTHORIZED, + Json(ApiError::new("AUTH_NOT_CONFIGURED", "Authentication not configured")), + ) + .into_response() + } + }; + let claims = match verifier.verify(token) { + Ok(c) => c, + Err(_) => { + return ( + StatusCode::UNAUTHORIZED, + Json(ApiError::new("INVALID_TOKEN", "Invalid authentication token")), + ) + .into_response() + } + }; + match crate::server::auth::resolve_owner_id_public(pool, claims.sub, claims.email.as_deref()).await { + Ok(owner_id) => crate::server::auth::AuthenticatedUser { + user_id: claims.sub, + owner_id, + auth_source: crate::server::auth::AuthSource::Jwt, + email: claims.email, + }, + Err(_) => { + return ( + StatusCode::UNAUTHORIZED, + Json(ApiError::new("USER_NOT_FOUND", "User not found")), + ) + .into_response() + } + } + } else if let Some(ref api_key) = auth_params.api_key { + // API key + match crate::server::auth::validate_api_key_public(pool, api_key).await { + Ok((user_id, owner_id)) => crate::server::auth::AuthenticatedUser { + user_id, + owner_id, + auth_source: crate::server::auth::AuthSource::ApiKey, + email: None, + }, + Err(_) => { + return ( + StatusCode::UNAUTHORIZED, + Json(ApiError::new("INVALID_API_KEY", "Invalid or revoked API key")), + ) + .into_response() + } + } + } else { + return ( + StatusCode::UNAUTHORIZED, + Json(ApiError::new("MISSING_TOKEN", "Authentication required via ?token= or ?api_key= query parameter")), + ) + .into_response(); + }; + // Verify ownership match repository::get_directive_for_owner(pool, id, auth.owner_id).await { Ok(Some(_)) => {} |
