diff --git a/src/api/admin.rs b/src/api/admin.rs index 7546dd7d..5ca075c2 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -1,6 +1,6 @@ use data_encoding::BASE64URL_NOPAD; use once_cell::sync::Lazy; -use percent_encoding::{percent_encode, NON_ALPHANUMERIC}; +use url::Url; use reqwest::Method; use serde::de::DeserializeOwned; use serde_json::Value; @@ -371,15 +371,20 @@ fn oauth2_authorize(_token: AdminToken) -> Result { // Construct redirect URI let redirect_uri = format!("{}/admin/oauth2/callback", CONFIG.domain()); - // Build authorization URL - let auth_url = format!( - "{}?client_id={}&redirect_uri={}&response_type=code&scope={}&state={}&access_type=offline&prompt=consent", - auth_url, - percent_encode(client_id.as_bytes(), NON_ALPHANUMERIC), - percent_encode(redirect_uri.as_bytes(), NON_ALPHANUMERIC), - percent_encode(scopes.as_bytes(), NON_ALPHANUMERIC), - percent_encode(state.as_bytes(), NON_ALPHANUMERIC) - ); + // 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 qp = url.query_pairs_mut(); + qp.append_pair("client_id", &client_id); + qp.append_pair("redirect_uri", &redirect_uri); + qp.append_pair("response_type", "code"); + qp.append_pair("scope", &scopes); + qp.append_pair("state", &state); + qp.append_pair("access_type", "offline"); + qp.append_pair("prompt", "consent"); + } + + let auth_url = url.to_string(); Ok(Redirect::to(auth_url)) } @@ -432,9 +437,7 @@ async fn oauth2_callback(params: OAuth2CallbackParams) -> Result, E ("client_secret", &client_secret), ]; - let client = reqwest::Client::new(); - let response = client - .post(&token_url) + let response = make_http_request(Method::POST, &token_url)? .form(&form_params) .send() .await @@ -460,29 +463,14 @@ async fn oauth2_callback(params: OAuth2CallbackParams) -> Result, E .map_err(|e| Error::new("ConfigBuilder serialization error", e.to_string()))?; CONFIG.update_config_partial(config_builder).await?; - // Return success page - let success_html = format!( - r#" - - - OAuth2 Authorization Successful - - - -

✓ OAuth2 Authorization Successful!

-

The refresh token has been saved to your configuration.

-

You can now close this window and return to the admin panel.

-

Return to Admin Settings

- -"#, - admin_url() - ); - - Ok(Html(success_html)) + // Return success page via template + let json = json!({ + "page_content": "admin/oauth2_success", + "admin_url": admin_url(), + "urlpath": CONFIG.domain_path(), + }); + let text = CONFIG.render_template(BASE_TEMPLATE, &json)?; + Ok(Html(text)) } #[get("/logout")] diff --git a/src/config.rs b/src/config.rs index b7b89bad..34497f18 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1732,6 +1732,7 @@ where reg!("admin/users"); reg!("admin/organizations"); reg!("admin/diagnostics"); + reg!("admin/oauth2_success"); reg!("404"); diff --git a/src/mail.rs b/src/mail.rs index 422b61d9..a4ffecf8 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -24,6 +24,8 @@ use crate::{ CONFIG, }; +use crate::http_client::make_http_request; + // OAuth2 Token structures #[derive(Debug, Clone, Serialize, Deserialize)] pub struct OAuth2Token { @@ -54,9 +56,7 @@ pub async fn refresh_oauth2_token() -> Result { ("client_secret", &client_secret), ]; - let client = reqwest::Client::new(); - let response = client - .post(&token_url) + let response = make_http_request(reqwest::Method::POST, &token_url)? .form(&form_params) .send() .await diff --git a/src/static/templates/admin/oauth2_success.hbs b/src/static/templates/admin/oauth2_success.hbs new file mode 100644 index 00000000..e3ef6750 --- /dev/null +++ b/src/static/templates/admin/oauth2_success.hbs @@ -0,0 +1,10 @@ +
+ +