From f9160c0dbba7af7945510bd77a6c16a645a6d472 Mon Sep 17 00:00:00 2001 From: anonsaber Date: Tue, 13 Oct 2026 09:42:28 +0800 Subject: [PATCH] =?UTF-8?q?fix(push):=20RFC=208620=20=C2=A77.2=20verificat?= =?UTF-8?q?ion=20response=20+=20best-effort=20verify=20API=20call?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two issues preventing push verification from completing: 1. Response to Stalwart's verification request was 204 (empty body), but RFC 8620 §7.2 requires a JSON body containing exactly {"verificationCode": "..."}. Stalwart likely checks the body and rejects empty responses → subscription stays unverified → no event pushes ever sent. 2. verify_push_subscription (JMAP API PushSubscription/verify) was hard-fail (503 on error). This non-standard method may not be supported by Stalwart; a failure blocks verification entirely. Made best-effort: warn on failure, continue storing the key. Per RFC 8620, verification is done by the callback response, not by a separate API call. --- src/notify.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/notify.rs b/src/notify.rs index 3e98a9a..fd4ab31 100644 --- a/src/notify.rs +++ b/src/notify.rs @@ -1290,13 +1290,17 @@ async fn jmap_push(State(app): State, body: Bytes) -> Response { true, ); } - if app + // RFC 8620 §7.2: verification is done by responding to the + // callback with the verificationCode. The JMAP API call is + // best-effort — some servers (including Stalwart) may not support + // PushSubscription/verify; failing it must not block verification. + if let Err(e) = app .worker .verify_push_subscription(subscription, code) .await - .is_err() { - return error_response(StatusCode::SERVICE_UNAVAILABLE, "push_verify_failed", true); + tracing::warn!(error = ?e, subscription = %subscription, + "push verify API call failed; continuing (RFC 8620 §7.2 verification is via callback response)"); } if app .state @@ -1332,6 +1336,14 @@ async fn jmap_push(State(app): State, body: Bytes) -> Response { } } if push.account_id.is_none() && push.email_id.is_none() { + // RFC 8620 §7.2: verification request response MUST be a JSON body + // containing exactly the verificationCode property (not 204). + if let Some(code) = push.verification_code.as_deref() { + return axum::Json(serde_json::json!({ + "verificationCode": code + })) + .into_response(); + } return StatusCode::NO_CONTENT.into_response(); } let (Some(account), Some(email)) = (push.account_id, push.email_id) else {