From ecb27e78ffc28531cd15578f9e9329e647774a30 Mon Sep 17 00:00:00 2001 From: jedarden Date: Sun, 24 May 2026 18:13:16 -0400 Subject: [PATCH] feat(ui): implement scoped key creation on search UI enable (P5.21.a) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements plan §13.21 auth model layer 1 - when search UI is first enabled for an index, the orchestrator now creates a scoped search-only key on every Meilisearch node via POST /keys with actions: [search], indexes scoped. The key is stored in Redis hash with metadata (primary_uid, rotated_at, generation) for retrieval at request time. Changes: - Add imports for MeilisearchClient and mint_scoped_key - Implement get_or_create_scoped_key to create keys when needed - Store new keys in Redis via set_search_ui_scoped_key - Return the scoped key for use in JWT session minting The scoped key has a hard expiration of scoped_key_max_age_days (60d default) and will be auto-rotated by the background rotation loop at scoped_key_rotate_before_expiry_days (30d default) - see P10.5 for the rotation coordination implementation. Closes: miroir-uhj.21.1 --- crates/miroir-proxy/src/routes/search_ui.rs | 42 +++++++++++++++++---- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/crates/miroir-proxy/src/routes/search_ui.rs b/crates/miroir-proxy/src/routes/search_ui.rs index 45a34e8..79a0382 100644 --- a/crates/miroir-proxy/src/routes/search_ui.rs +++ b/crates/miroir-proxy/src/routes/search_ui.rs @@ -21,9 +21,11 @@ use crate::error_response::ErrorResponse; use rust_embed::RustEmbed as Embed; use serde::{Deserialize, Serialize}; use std::sync::Arc; -use tracing::{debug, info}; +use tracing::{debug, info, warn}; use crate::auth::{build_csp_header, jwt_encode, JwtClaims, JwtHeader, KID_PRIMARY}; +use crate::routes::indexes::MeilisearchClient; +use crate::scoped_key_rotation::mint_scoped_key; use super::admin_endpoints::AppState; @@ -331,12 +333,38 @@ async fn get_or_create_scoped_key( ); } - // TODO: Create new scoped key via Meilisearch API - // For now, return an error indicating the key needs to be created - Err(ErrorResponse::internal_error(format!( - "scoped key for index '{}' needs to be created via background rotation", - index_uid - ))) + // Create new scoped key via Meilisearch API (plan §13.21) + info!(index = %index_uid, "creating new scoped search-only key"); + + let client = MeilisearchClient::new(state.config.node_master_key.clone()); + let (new_key, new_uid) = mint_scoped_key(&client, &state.config, index_uid) + .await + .map_err(|e| ErrorResponse::internal_error(format!("failed to mint scoped key: {}", e)))?; + + let now_ms = chrono::Utc::now().timestamp_millis(); + let scoped_key = SearchUiScopedKey { + index_uid: index_uid.to_string(), + primary_key: new_key.clone(), + primary_uid: new_uid.clone(), + previous_key: None, + previous_uid: None, + rotated_at: now_ms, + generation: 1, + }; + + // Store in Redis + redis_store + .set_search_ui_scoped_key(&scoped_key) + .map_err(|e| ErrorResponse::internal_error(format!("failed to store scoped key: {}", e)))?; + + info!( + index = %index_uid, + uid = %new_uid, + generation = 1, + "created new scoped search-only key" + ); + + Ok(scoped_key) } /// Get per-index search UI configuration (plan §13.21).