fix(push): RFC 8620 §7.2 verification response + best-effort verify API call

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.
This commit is contained in:
anonsaber 2026-10-13 09:42:28 +08:00
parent 7331cb7d31
commit f9160c0dbb
1 changed files with 15 additions and 3 deletions

View File

@ -1290,13 +1290,17 @@ async fn jmap_push(State(app): State<AppState>, 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<AppState>, 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 {