HTTP: Add redirectRoot option to Http object
This commit is contained in:
@@ -7,8 +7,10 @@ All notable changes to this project will be documented in this file. This projec
|
||||
If you are upgrading from v0.16.x, replace the binary (or run `docker pull`). If you are upgrading from v0.15.x and below, please read the [upgrading documentation](https://github.com/stalwartlabs/stalwart/blob/main/UPGRADING/v0_16.md) for more information on how to upgrade from previous versions.
|
||||
|
||||
## Added
|
||||
- OAuth Profile for Open Public Clients ([draft-ietf-mailmaint-oauth-public](https://datatracker.ietf.org/doc/draft-ietf-mailmaint-oauth-public/))
|
||||
- OAuth client secret verification for confidential clients.
|
||||
- OAuth:
|
||||
- OAuth Profile for Open Public Clients ([draft-ietf-mailmaint-oauth-public](https://datatracker.ietf.org/doc/draft-ietf-mailmaint-oauth-public/))
|
||||
- Client secret verification for confidential clients.
|
||||
- HTTP: Add `redirectRoot` option to `Http` object to allow redirecting requests to the root path to a different path (e.g. `/account`).
|
||||
|
||||
## Changed
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ pub struct Http {
|
||||
pub allowed_endpoint: IfBlock,
|
||||
pub response_headers: Vec<(hyper::header::HeaderName, hyper::header::HeaderValue)>,
|
||||
pub use_forwarded: bool,
|
||||
pub redirect_root: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -448,6 +449,7 @@ impl Http {
|
||||
rate_anonymous: http.rate_limit_anonymous,
|
||||
response_headers: http_headers,
|
||||
use_forwarded: http.use_x_forwarded,
|
||||
redirect_root: http.redirect_root,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,8 +233,7 @@ impl OAuthApiHandler for Server {
|
||||
|
||||
// Parse and validate PKCE challenge (RFC 7636).
|
||||
let pkce_challenge = match code_challenge {
|
||||
Some(challenge) => {
|
||||
match code_challenge_method.as_deref().unwrap_or("plain") {
|
||||
Some(challenge) => match code_challenge_method.as_deref().unwrap_or("plain") {
|
||||
"S256" => PkceCodeChallenge::S256(challenge),
|
||||
"plain" if stateless_client.is_none() => {
|
||||
PkceCodeChallenge::Plain(challenge)
|
||||
@@ -244,13 +243,12 @@ impl OAuthApiHandler for Server {
|
||||
.into_err()
|
||||
.details("Unsupported PKCE code_challenge_method."));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
None => {
|
||||
if stateless_client.is_some() {
|
||||
return Err(trc::AuthEvent::Error
|
||||
.into_err()
|
||||
.details("A PKCE code_challenge with the S256 method is required."));
|
||||
return Err(trc::AuthEvent::Error.into_err().details(
|
||||
"A PKCE code_challenge with the S256 method is required.",
|
||||
));
|
||||
}
|
||||
PkceCodeChallenge::None
|
||||
}
|
||||
|
||||
@@ -611,6 +611,8 @@ impl ParseHttp for Server {
|
||||
if path.next().is_none() {
|
||||
if !external.is_empty() {
|
||||
return Ok(HttpResponse::redirect(format!("/{external}/")));
|
||||
} else if let Some(url) = &self.core.network.http.redirect_root {
|
||||
return Ok(HttpResponse::redirect(url.clone()));
|
||||
}
|
||||
} else if let Some(resource) = self
|
||||
.inner
|
||||
|
||||
@@ -948,6 +948,7 @@ pub enum Property {
|
||||
Recipients = 484,
|
||||
Records = 256,
|
||||
RecurrenceId = 805,
|
||||
RedirectRoot = 911,
|
||||
RedirectUris = 605,
|
||||
Refresh = 419,
|
||||
RefreshTokenExpiry = 617,
|
||||
|
||||
@@ -1101,6 +1101,7 @@ impl EnumImpl for Property {
|
||||
b"recipients" => Property::Recipients,
|
||||
b"records" => Property::Records,
|
||||
b"recurrenceId" => Property::RecurrenceId,
|
||||
b"redirectRoot" => Property::RedirectRoot,
|
||||
b"redirectUris" => Property::RedirectUris,
|
||||
b"refresh" => Property::Refresh,
|
||||
b"refreshTokenExpiry" => Property::RefreshTokenExpiry,
|
||||
@@ -2017,6 +2018,7 @@ impl EnumImpl for Property {
|
||||
Property::Recipients => "recipients",
|
||||
Property::Records => "records",
|
||||
Property::RecurrenceId => "recurrenceId",
|
||||
Property::RedirectRoot => "redirectRoot",
|
||||
Property::RedirectUris => "redirectUris",
|
||||
Property::Refresh => "refresh",
|
||||
Property::RefreshTokenExpiry => "refreshTokenExpiry",
|
||||
@@ -2937,6 +2939,7 @@ impl EnumImpl for Property {
|
||||
484 => Some(Property::Recipients),
|
||||
256 => Some(Property::Records),
|
||||
805 => Some(Property::RecurrenceId),
|
||||
911 => Some(Property::RedirectRoot),
|
||||
605 => Some(Property::RedirectUris),
|
||||
419 => Some(Property::Refresh),
|
||||
617 => Some(Property::RefreshTokenExpiry),
|
||||
@@ -3158,7 +3161,7 @@ impl EnumImpl for Property {
|
||||
}
|
||||
}
|
||||
|
||||
const COUNT: usize = 911;
|
||||
const COUNT: usize = 912;
|
||||
}
|
||||
|
||||
impl serde::Serialize for Property {
|
||||
|
||||
@@ -2930,6 +2930,8 @@ pub struct Http {
|
||||
pub response_headers: VecMap<String, String>,
|
||||
#[serde(rename = "useXForwarded")]
|
||||
pub use_x_forwarded: bool,
|
||||
#[serde(rename = "redirectRoot")]
|
||||
pub redirect_root: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
||||
@@ -21203,7 +21203,7 @@ impl RegistryJsonPropertyPatch for GroupAccount {
|
||||
|
||||
impl ObjectImpl for Http {
|
||||
const FLAGS: u64 = OBJ_SINGLETON;
|
||||
const VERSION: u8 = 0;
|
||||
const VERSION: u8 = 1;
|
||||
const OBJECT: ObjectType = ObjectType::Http;
|
||||
|
||||
fn validate(&self, errors: &mut Vec<ValidationError>) -> bool {
|
||||
@@ -21222,6 +21222,11 @@ impl ObjectImpl for Http {
|
||||
errors.push(ValidationError::required(Property::ResponseHeaders));
|
||||
}
|
||||
}
|
||||
if let Some(value) = &self.redirect_root {
|
||||
if value.is_empty() {
|
||||
errors.push(ValidationError::required(Property::RedirectRoot));
|
||||
}
|
||||
}
|
||||
errors.len() == neb
|
||||
}
|
||||
|
||||
@@ -21256,6 +21261,7 @@ impl Pickle for Http {
|
||||
self.use_permissive_cors.pickle(out);
|
||||
self.response_headers.pickle(out);
|
||||
self.use_x_forwarded.pickle(out);
|
||||
self.redirect_root.pickle(out);
|
||||
}
|
||||
|
||||
fn unpickle(stream: &mut crate::pickle::PickledStream<'_>) -> Option<Self> {
|
||||
@@ -21267,6 +21273,9 @@ impl Pickle for Http {
|
||||
this.use_permissive_cors = Pickle::unpickle(stream)?;
|
||||
this.response_headers = Pickle::unpickle(stream)?;
|
||||
this.use_x_forwarded = Pickle::unpickle(stream)?;
|
||||
if stream.version() >= 1 {
|
||||
this.redirect_root = Pickle::unpickle(stream)?;
|
||||
}
|
||||
Some(this)
|
||||
}
|
||||
}
|
||||
@@ -21290,13 +21299,14 @@ impl Default for Http {
|
||||
use_permissive_cors: false,
|
||||
response_headers: Default::default(),
|
||||
use_x_forwarded: false,
|
||||
redirect_root: Some("/account".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoValue for Http {
|
||||
fn into_value(self) -> JmapValue<'static> {
|
||||
let mut map = jmap_tools::Map::with_capacity(9);
|
||||
let mut map = jmap_tools::Map::with_capacity(10);
|
||||
map.insert_unchecked(
|
||||
Property::RateLimitAuthenticated,
|
||||
self.rate_limit_authenticated.into_value(),
|
||||
@@ -21319,6 +21329,7 @@ impl IntoValue for Http {
|
||||
self.response_headers.into_value(),
|
||||
);
|
||||
map.insert_unchecked(Property::UseXForwarded, self.use_x_forwarded.into_value());
|
||||
map.insert_unchecked(Property::RedirectRoot, self.redirect_root.into_value());
|
||||
JmapValue::Object(map)
|
||||
}
|
||||
}
|
||||
@@ -21341,6 +21352,9 @@ impl RegistryJsonPropertyPatch for Http {
|
||||
.response_headers
|
||||
.patch(pointer.with_validators(&[StringValidator::Trim]), value),
|
||||
Some(Property::UseXForwarded) => self.use_x_forwarded.patch(pointer, value),
|
||||
Some(Property::RedirectRoot) => self
|
||||
.redirect_root
|
||||
.patch(pointer.with_validators(&[StringValidator::Trim]), value),
|
||||
Some(Property::Type) => Ok(MaybeUnpatched::Unpatched {
|
||||
property: Property::Type,
|
||||
value,
|
||||
|
||||
Binary file not shown.
@@ -1 +1 @@
|
||||
fleWj8pl8amEMbzM8N8ku4pLHlm4yYln_xEcXSCADBw
|
||||
qtv0KUvdELIzxfAvonxvf3PW10RnWMB9iADw0FtHMF4
|
||||
Reference in New Issue
Block a user