From fccc25fb7172059d1ecb073081228df928e98884 Mon Sep 17 00:00:00 2001 From: mdecimus Date: Mon, 14 Jul 2025 15:30:09 +0200 Subject: [PATCH] Do not include WWW-Authenticate headers on API responses (fixes #1795) --- crates/http/src/management/mod.rs | 30 +++++++++++++++++++-------- crates/http/src/request.rs | 34 +++++++++++++++---------------- 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/crates/http/src/management/mod.rs b/crates/http/src/management/mod.rs index 63e8c662..58d44d0c 100644 --- a/crates/http/src/management/mod.rs +++ b/crates/http/src/management/mod.rs @@ -273,20 +273,32 @@ impl ToManageHttpResponse for &trc::Error { } } .into_http_response(), - trc::EventType::Auth(trc::AuthEvent::Failed) => { - HttpResponse::new(StatusCode::UNAUTHORIZED) - .with_header(header::WWW_AUTHENTICATE, "Bearer realm=\"Stalwart Server\"") - .with_header(header::WWW_AUTHENTICATE, "Basic realm=\"Stalwart Server\"") - .with_content_type("application/problem+json") - .with_text_body( - serde_json::to_string(&RequestError::unauthorized()).unwrap_or_default(), - ) - } + trc::EventType::Auth( + trc::AuthEvent::Failed | trc::AuthEvent::Error | trc::AuthEvent::TokenExpired, + ) => HttpResponse::unauthorized(true), _ => self.to_request_error().into_http_response(), } } } +pub trait UnauthorizedResponse { + fn unauthorized(include_realms: bool) -> Self; +} + +impl UnauthorizedResponse for HttpResponse { + fn unauthorized(include_realms: bool) -> Self { + (if include_realms { + HttpResponse::new(StatusCode::UNAUTHORIZED) + .with_header(header::WWW_AUTHENTICATE, "Bearer realm=\"Stalwart Server\"") + .with_header(header::WWW_AUTHENTICATE, "Basic realm=\"Stalwart Server\"") + } else { + HttpResponse::new(StatusCode::UNAUTHORIZED) + }) + .with_content_type("application/problem+json") + .with_text_body(serde_json::to_string(&RequestError::unauthorized()).unwrap_or_default()) + } +} + impl ManagementApiError<'_> { fn into_http_response(self) -> HttpResponse { JsonResponse::new(self).into_http_response() diff --git a/crates/http/src/request.rs b/crates/http/src/request.rs index a7b8a948..72968eec 100644 --- a/crates/http/src/request.rs +++ b/crates/http/src/request.rs @@ -4,8 +4,21 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{net::IpAddr, sync::Arc}; - +use crate::{ + HttpSessionManager, + auth::{ + authenticate::{Authenticator, HttpHeaders}, + oauth::{ + FormData, auth::OAuthApiHandler, openid::OpenIdHandler, + registration::ClientRegistrationHandler, token::TokenHandler, + }, + }, + autoconfig::Autoconfig, + form::FormHandler, + management::{ + ManagementApi, ToManageHttpResponse, UnauthorizedResponse, troubleshoot::TroubleshootApi, + }, +}; use common::{ Inner, KV_ACME, Server, auth::{AccessToken, oauth::GrantType}, @@ -40,24 +53,11 @@ use jmap_proto::{ request::{Request, capability::Session}, types::{blob::BlobId, id::Id}, }; +use std::{net::IpAddr, sync::Arc}; use store::dispatch::lookup::KeyValue; use trc::SecurityEvent; use utils::url_params::UrlParams; -use crate::{ - HttpSessionManager, - auth::{ - authenticate::{Authenticator, HttpHeaders}, - oauth::{ - FormData, auth::OAuthApiHandler, openid::OpenIdHandler, - registration::ClientRegistrationHandler, token::TokenHandler, - }, - }, - autoconfig::Autoconfig, - form::FormHandler, - management::{ManagementApi, ToManageHttpResponse, troubleshoot::TroubleshootApi}, -}; - pub trait ParseHttp: Sync + Send { fn parse_http_request( &self, @@ -427,7 +427,7 @@ impl ParseHttp for Server { (Some("troubleshoot"), _, Some(token)) => { (GrantType::Troubleshoot, token) } - _ => return Err(err), + _ => return Ok(HttpResponse::unauthorized(false)), }; let token_info = self.validate_access_token(grant_type.into(), token).await?;