fix(notify): /ready JMAP 探针对齐真实客户端(Basic 认证 + 路径归一)
架构师复核指出两点会致全站不可用的缺陷(notify.rs): - 探针未带认证:真实 jmap_client 以 .credentials() 取 session,Stalwart session 端点需认证;无认证必 401 → /ready 永久 503 → ingress 停止路由。 改为从 business_runtime 快照取 jmap_username/jmap_password, basic_auth 随请求发送(零新增依赖,凭据仅入请求头,绝不日志)。 - session URL 未归一:配置若是裸 origin,GET 根路径必 404 → 误报。 改为复用 normalize_session_url(client.rs 改为 pub(crate))并请求 标准 /.well-known/jmap,与 JmapClientBackend::connect_with_runtime 同路径。 顺带:两探针 tokio::join! 并行,/ready 最坏耗时 3s→3s(原 6s)。 门禁 4/4 绿(45 passed, 0 failed, 1 ignored)。
This commit is contained in:
parent
c0aab5e966
commit
c5bc7b8d82
|
|
@ -384,7 +384,7 @@ fn role_name(role: mailbox::Role) -> Option<String> {
|
|||
}
|
||||
}
|
||||
|
||||
fn normalize_session_url(value: &str) -> Result<String, JmapError> {
|
||||
pub(crate) fn normalize_session_url(value: &str) -> Result<String, JmapError> {
|
||||
let parsed =
|
||||
url::Url::parse(value).map_err(|_| JmapError::InvalidRequest("JMAP_SESSION_URL"))?;
|
||||
if parsed.scheme() != "https" {
|
||||
|
|
|
|||
|
|
@ -70,12 +70,14 @@ const PROBE_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(3_00
|
|||
|
||||
/// Read the cached runtime configuration without holding the lock guard across an await.
|
||||
/// Callers refresh the cache first so this never reports a stale view.
|
||||
fn cached_jmap_session_url(app: &AppState) -> Option<String> {
|
||||
app.business_runtime
|
||||
.read()
|
||||
.ok()?
|
||||
.as_ref()
|
||||
.map(|config| config.jmap_session_url.clone())
|
||||
fn cached_jmap_session(app: &AppState) -> Option<(String, String, SecretString)> {
|
||||
app.business_runtime.read().ok()?.as_ref().map(|config| {
|
||||
(
|
||||
config.jmap_session_url.clone(),
|
||||
config.jmap_username.clone(),
|
||||
config.jmap_password.clone(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn cached_bot_token(app: &AppState) -> Option<secrecy::SecretString> {
|
||||
|
|
@ -86,13 +88,21 @@ fn cached_bot_token(app: &AppState) -> Option<secrecy::SecretString> {
|
|||
.map(|config| config.bot_token.clone())
|
||||
}
|
||||
|
||||
/// End-to-end JMAP session probe: the configured session URL must answer with a 2xx.
|
||||
/// Reusable by the remote debug surface so readiness and debugging share one implementation.
|
||||
/// End-to-end JMAP session probe: fetch `/.well-known/jmap` with the configured credentials and
|
||||
/// require a 2xx. Mirrors `JmapClientBackend::connect_with_runtime` (normalized origin + Basic
|
||||
/// auth): probing the raw URL unauthenticated would report not-ready forever and make ingress
|
||||
/// stop routing. Reusable by the remote debug surface so readiness and debugging share one
|
||||
/// implementation.
|
||||
async fn probe_jmap_session(app: &AppState) -> Result<(), &'static str> {
|
||||
let Some(session_url) = cached_jmap_session_url(app) else {
|
||||
let Some((session_url, username, password)) = cached_jmap_session(app) else {
|
||||
return Err("jmap session url not configured");
|
||||
};
|
||||
let request = reqwest::Client::new().get(session_url).send();
|
||||
let origin = crate::domain::jmap::client::normalize_session_url(&session_url)
|
||||
.map_err(|_| "jmap session url invalid")?;
|
||||
let request = reqwest::Client::new()
|
||||
.get(format!("{origin}/.well-known/jmap"))
|
||||
.basic_auth(&username, Some(password.expose_secret()))
|
||||
.send();
|
||||
match tokio::time::timeout(PROBE_TIMEOUT, request).await {
|
||||
Ok(Ok(response)) => response
|
||||
.error_for_status()
|
||||
|
|
@ -129,8 +139,10 @@ async fn ready(State(app): State<AppState>) -> Response {
|
|||
let configured = app.setup_missing.is_empty();
|
||||
let redis = app.state.is_enabled().await.is_ok();
|
||||
refresh_business_config(&app).await;
|
||||
let jmap = probe_jmap_session(&app).await.is_ok();
|
||||
let telegram = probe_telegram_get_me(&app).await.is_ok();
|
||||
// Probes run in parallel: worst case one PROBE_TIMEOUT instead of two.
|
||||
let (jmap, telegram) = tokio::join!(probe_jmap_session(&app), probe_telegram_get_me(&app));
|
||||
let jmap = jmap.is_ok();
|
||||
let telegram = telegram.is_ok();
|
||||
if configured && redis && jmap && telegram {
|
||||
axum::Json(serde_json::json!({
|
||||
"status": "ready",
|
||||
|
|
|
|||
Loading…
Reference in New Issue