Browse Source

Add SMTP OAuth2 configuration options and improve error handling

pull/6388/head
Henning 3 weeks ago
parent
commit
c17837fa2a
  1. 11
      .env.template
  2. 16
      src/api/admin.rs
  3. 2
      src/config.rs
  4. 6
      src/mail.rs
  5. 2
      src/static/scripts/admin_settings.js

11
.env.template

@ -624,6 +624,17 @@
## Multiple options need to be separated by a comma ','.
# SMTP_AUTH_MECHANISM=
## SMTP OAuth2 settings
## These are required if SMTP_AUTH_MECHANISM includes "Xoauth2".
## After configuring these, you'll need to use the admin panel to complete the authorization flow.
# SMTP_OAUTH2_CLIENT_ID=
# SMTP_OAUTH2_CLIENT_SECRET=
# SMTP_OAUTH2_AUTH_URL=
# SMTP_OAUTH2_TOKEN_URL=
# SMTP_OAUTH2_SCOPES=
## The refresh token is typically obtained automatically during the authorization flow in the admin panel.
# SMTP_OAUTH2_REFRESH_TOKEN=
## Server name sent during the SMTP HELO
## By default this value should be the machine's hostname,
## but might need to be changed in case it trips some anti-spam filters

16
src/api/admin.rs

@ -352,7 +352,7 @@ async fn test_smtp(data: Json<InviteData>, _token: AdminToken) -> EmptyResult {
}
}
#[post("/test/oauth2")]
#[post("/oauth2/test")]
async fn refresh_oauth2_token_endpoint(_token: AdminToken) -> EmptyResult {
if CONFIG.smtp_oauth2_client_id().is_none() {
err!("OAuth2 is not configured")
@ -384,7 +384,7 @@ fn oauth2_authorize(_token: AdminToken) -> Result<Redirect, Error> {
let redirect_uri = format!("{}/admin/oauth2/callback", CONFIG.domain());
// Build authorization URL using url crate to ensure proper encoding
let mut url = Url::parse(&auth_url).map_err(|e| Error::new("Invalid OAuth2 Authorization URL", e.to_string()))?;
let mut url = Url::parse(&auth_url).map_err(|e| err!(format!("Invalid OAuth2 Authorization URL: {e}")))?;
{
let mut qp = url.query_pairs_mut();
qp.append_pair("client_id", &client_id);
@ -414,7 +414,7 @@ async fn oauth2_callback(params: OAuth2CallbackParams) -> Result<Html<String>, E
// Check for errors from OAuth2 provider
if let Some(error) = params.error {
let description = params.error_description.unwrap_or_else(|| "Unknown error".to_string());
return Err(Error::new("OAuth2 Authorization Failed", format!("{}: {}", error, description)));
err!("OAuth2 Authorization Failed", format!("{error}: {description}"));
}
// Validate required parameters
@ -429,7 +429,7 @@ async fn oauth2_callback(params: OAuth2CallbackParams) -> Result<Html<String>, E
};
if !valid_state {
return Err(Error::new("OAuth2 State Validation Failed", "Invalid or expired state token"));
err!("OAuth2 State Validation Failed", "Invalid or expired state token");
}
// Remove used state
@ -453,16 +453,16 @@ async fn oauth2_callback(params: OAuth2CallbackParams) -> Result<Html<String>, E
.form(&form_params)
.send()
.await
.map_err(|e| Error::new("OAuth2 Token Exchange Error", e.to_string()))?;
.map_err(|e| err!(format!("OAuth2 Token Exchange Error: {e}")))?;
if !response.status().is_success() {
let status = response.status();
let body = response.text().await.unwrap_or_else(|_| String::from("Unable to read response body"));
return Err(Error::new("OAuth2 Token Exchange Failed", format!("HTTP {}: {}", status, body)));
err!("OAuth2 Token Exchange Failed", format!("HTTP {status}: {body}"));
}
let token_response: Value =
response.json().await.map_err(|e| Error::new("OAuth2 Token Parse Error", e.to_string()))?;
response.json().await.map_err(|e| err!(format!("OAuth2 Token Parse Error: {e}")))?;
// Extract refresh_token from response
let refresh_token =
@ -472,7 +472,7 @@ async fn oauth2_callback(params: OAuth2CallbackParams) -> Result<Html<String>, E
let config_builder: ConfigBuilder = serde_json::from_value(json!({
"smtp_oauth2_refresh_token": refresh_token
}))
.map_err(|e| Error::new("ConfigBuilder serialization error", e.to_string()))?;
.map_err(|e| err!(format!("ConfigBuilder serialization error: {e}")))?;
CONFIG.update_config_partial(config_builder).await?;
// Return success page via template

2
src/config.rs

@ -898,7 +898,7 @@ make_config! {
/// SMTP OAuth2 Refresh Token |> OAuth2 Refresh Token for obtaining new access tokens
smtp_oauth2_refresh_token: Pass, true, option;
/// SMTP OAuth2 Scopes |> Comma-separated list of OAuth2 scopes
smtp_oauth2_scopes: String, true, def, "https://mail.google.com/".to_string();
smtp_oauth2_scopes: String, true, def, "".to_string();
/// SMTP connection timeout |> Number of seconds when to stop trying to connect to the SMTP server
smtp_timeout: u64, true, def, 15;
/// Server name sent during HELO |> By default this value should be the machine's hostname, but might need to be changed in case it trips some anti-spam filters

6
src/mail.rs

@ -60,16 +60,16 @@ pub async fn refresh_oauth2_token() -> Result<OAuth2Token, Error> {
.form(&form_params)
.send()
.await
.map_err(|e| Error::new("OAuth2 Token Refresh Error", e.to_string()))?;
.map_err(|e| err!(format!("OAuth2 Token Refresh Error: {e}")))?;
if !response.status().is_success() {
let status = response.status();
let body = response.text().await.unwrap_or_else(|_| String::from("Unable to read response body"));
return Err(Error::new("OAuth2 Token Refresh Failed", format!("HTTP {status}: {body}")));
err!("OAuth2 Token Refresh Failed", format!("HTTP {status}: {body}"));
}
let token_response: TokenRefreshResponse =
response.json().await.map_err(|e| Error::new("OAuth2 Token Parse Error", e.to_string()))?;
response.json().await.map_err(|e| err!(format!("OAuth2 Token Parse Error: {e}")))?;
let expires_at = token_response
.expires_in

2
src/static/scripts/admin_settings.js

@ -34,7 +34,7 @@ function oauth2RefreshToken(event) {
return false;
}
_post(`${BASE_URL}/admin/test/oauth2`,
_post(`${BASE_URL}/admin/oauth2/test`,
"OAuth2 token refreshed successfully",
"Error refreshing OAuth2 token",
null, false

Loading…
Cancel
Save