mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2026-06-03 22:04:57 -06:00
Add SMTP OAuth2 configuration options and improve error handling
This commit is contained in:
parent
8bd2a0b26c
commit
c17837fa2a
@ -624,6 +624,17 @@
|
|||||||
## Multiple options need to be separated by a comma ','.
|
## Multiple options need to be separated by a comma ','.
|
||||||
# SMTP_AUTH_MECHANISM=
|
# 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
|
## Server name sent during the SMTP HELO
|
||||||
## By default this value should be the machine's hostname,
|
## By default this value should be the machine's hostname,
|
||||||
## but might need to be changed in case it trips some anti-spam filters
|
## but might need to be changed in case it trips some anti-spam filters
|
||||||
|
|||||||
@ -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 {
|
async fn refresh_oauth2_token_endpoint(_token: AdminToken) -> EmptyResult {
|
||||||
if CONFIG.smtp_oauth2_client_id().is_none() {
|
if CONFIG.smtp_oauth2_client_id().is_none() {
|
||||||
err!("OAuth2 is not configured")
|
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());
|
let redirect_uri = format!("{}/admin/oauth2/callback", CONFIG.domain());
|
||||||
|
|
||||||
// Build authorization URL using url crate to ensure proper encoding
|
// 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();
|
let mut qp = url.query_pairs_mut();
|
||||||
qp.append_pair("client_id", &client_id);
|
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
|
// Check for errors from OAuth2 provider
|
||||||
if let Some(error) = params.error {
|
if let Some(error) = params.error {
|
||||||
let description = params.error_description.unwrap_or_else(|| "Unknown error".to_string());
|
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
|
// Validate required parameters
|
||||||
@ -429,7 +429,7 @@ async fn oauth2_callback(params: OAuth2CallbackParams) -> Result<Html<String>, E
|
|||||||
};
|
};
|
||||||
|
|
||||||
if !valid_state {
|
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
|
// Remove used state
|
||||||
@ -453,16 +453,16 @@ async fn oauth2_callback(params: OAuth2CallbackParams) -> Result<Html<String>, E
|
|||||||
.form(&form_params)
|
.form(&form_params)
|
||||||
.send()
|
.send()
|
||||||
.await
|
.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() {
|
if !response.status().is_success() {
|
||||||
let status = response.status();
|
let status = response.status();
|
||||||
let body = response.text().await.unwrap_or_else(|_| String::from("Unable to read response body"));
|
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 =
|
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
|
// Extract refresh_token from response
|
||||||
let refresh_token =
|
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!({
|
let config_builder: ConfigBuilder = serde_json::from_value(json!({
|
||||||
"smtp_oauth2_refresh_token": refresh_token
|
"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?;
|
CONFIG.update_config_partial(config_builder).await?;
|
||||||
|
|
||||||
// Return success page via template
|
// Return success page via template
|
||||||
|
|||||||
@ -898,7 +898,7 @@ make_config! {
|
|||||||
/// SMTP OAuth2 Refresh Token |> OAuth2 Refresh Token for obtaining new access tokens
|
/// SMTP OAuth2 Refresh Token |> OAuth2 Refresh Token for obtaining new access tokens
|
||||||
smtp_oauth2_refresh_token: Pass, true, option;
|
smtp_oauth2_refresh_token: Pass, true, option;
|
||||||
/// SMTP OAuth2 Scopes |> Comma-separated list of OAuth2 scopes
|
/// 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 connection timeout |> Number of seconds when to stop trying to connect to the SMTP server
|
||||||
smtp_timeout: u64, true, def, 15;
|
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
|
/// 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
|
||||||
|
|||||||
@ -60,16 +60,16 @@ pub async fn refresh_oauth2_token() -> Result<OAuth2Token, Error> {
|
|||||||
.form(&form_params)
|
.form(&form_params)
|
||||||
.send()
|
.send()
|
||||||
.await
|
.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() {
|
if !response.status().is_success() {
|
||||||
let status = response.status();
|
let status = response.status();
|
||||||
let body = response.text().await.unwrap_or_else(|_| String::from("Unable to read response body"));
|
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 =
|
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
|
let expires_at = token_response
|
||||||
.expires_in
|
.expires_in
|
||||||
|
|||||||
2
src/static/scripts/admin_settings.js
vendored
2
src/static/scripts/admin_settings.js
vendored
@ -34,7 +34,7 @@ function oauth2RefreshToken(event) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_post(`${BASE_URL}/admin/test/oauth2`,
|
_post(`${BASE_URL}/admin/oauth2/test`,
|
||||||
"OAuth2 token refreshed successfully",
|
"OAuth2 token refreshed successfully",
|
||||||
"Error refreshing OAuth2 token",
|
"Error refreshing OAuth2 token",
|
||||||
null, false
|
null, false
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user