fix(oauth): reqwest 启用 system-proxy;Google 换 token 诊断与 0.5.26
- 工作区 reqwest 增加 system-proxy,与系统代理一致 - google.rs:超时/非 2xx 体日志;OAuth 请求单独 45s 超时 - README / AGENTS / deploy/.env.example:OAuth 出站与 HTTPS_PROXY 说明
This commit is contained in:
@@ -1,5 +1,17 @@
|
||||
本文档在构建时嵌入 Web 的 `/changelog` 页面,并由服务端渲染为 HTML。
|
||||
|
||||
## [0.5.26] - 2026-04-11
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Google OAuth**:工作区 `reqwest` 此前关闭默认特性且未启用 **`system-proxy`**,进程不读取 macOS/Windows 系统代理,易出现与浏览器不一致(本机可上 Google 但换 token 超时)。已显式启用 `system-proxy`。
|
||||
|
||||
## [0.5.25] - 2026-04-11
|
||||
|
||||
### Changed
|
||||
|
||||
- Google OAuth:token / userinfo 请求单独 **45s** 超时(避免仅触达默认客户端 15s);失败时区分超时、连接错误,并在非 2xx 时记录/返回 Google 响应体片段(如 `invalid_grant`、`redirect_uri_mismatch`)。
|
||||
|
||||
## [0.5.24] - 2026-04-11
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "secrets-mcp"
|
||||
version = "0.5.24"
|
||||
version = "0.5.26"
|
||||
edition.workspace = true
|
||||
|
||||
[[bin]]
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::{OAuthConfig, OAuthUserInfo};
|
||||
|
||||
/// OAuth token / userinfo calls can be slow on poor routes; keep above client default if needed.
|
||||
const OAUTH_HTTP_TIMEOUT: Duration = Duration::from_secs(45);
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct TokenResponse {
|
||||
access_token: String,
|
||||
@@ -20,14 +25,28 @@ struct UserInfo {
|
||||
picture: Option<String>,
|
||||
}
|
||||
|
||||
fn map_reqwest_send_err(e: reqwest::Error) -> anyhow::Error {
|
||||
if e.is_timeout() {
|
||||
anyhow::anyhow!(
|
||||
"timeout reaching Google OAuth ({}s); ensure outbound HTTPS to oauth2.googleapis.com works (firewall/proxy/VPN if Google is unreachable)",
|
||||
OAUTH_HTTP_TIMEOUT.as_secs()
|
||||
)
|
||||
} else if e.is_connect() {
|
||||
anyhow::anyhow!("connection error to Google OAuth: {e}")
|
||||
} else {
|
||||
anyhow::Error::new(e)
|
||||
}
|
||||
}
|
||||
|
||||
/// Exchange authorization code for tokens and fetch user profile.
|
||||
pub async fn exchange_code(
|
||||
client: &reqwest::Client,
|
||||
config: &OAuthConfig,
|
||||
code: &str,
|
||||
) -> Result<OAuthUserInfo> {
|
||||
let token_resp: TokenResponse = client
|
||||
let token_http = client
|
||||
.post("https://oauth2.googleapis.com/token")
|
||||
.timeout(OAUTH_HTTP_TIMEOUT)
|
||||
.form(&[
|
||||
("code", code),
|
||||
("client_id", &config.client_id),
|
||||
@@ -37,24 +56,55 @@ pub async fn exchange_code(
|
||||
])
|
||||
.send()
|
||||
.await
|
||||
.context("failed to exchange Google code")?
|
||||
.error_for_status()
|
||||
.context("Google token endpoint error")?
|
||||
.json()
|
||||
.await
|
||||
.context("failed to parse Google token response")?;
|
||||
.map_err(map_reqwest_send_err)
|
||||
.context("Google token HTTP request failed")?;
|
||||
|
||||
let user: UserInfo = client
|
||||
let status = token_http.status();
|
||||
let body_bytes = token_http
|
||||
.bytes()
|
||||
.await
|
||||
.context("read Google token response body")?;
|
||||
|
||||
if !status.is_success() {
|
||||
let body_lossy = String::from_utf8_lossy(&body_bytes);
|
||||
tracing::warn!(%status, body = %body_lossy, "Google token endpoint error");
|
||||
anyhow::bail!(
|
||||
"Google token error {}: {}",
|
||||
status,
|
||||
body_lossy.chars().take(512).collect::<String>()
|
||||
);
|
||||
}
|
||||
|
||||
let token_resp: TokenResponse =
|
||||
serde_json::from_slice(&body_bytes).context("failed to parse Google token JSON")?;
|
||||
|
||||
let user_http = client
|
||||
.get("https://openidconnect.googleapis.com/v1/userinfo")
|
||||
.timeout(OAUTH_HTTP_TIMEOUT)
|
||||
.bearer_auth(&token_resp.access_token)
|
||||
.send()
|
||||
.await
|
||||
.context("failed to fetch Google userinfo")?
|
||||
.error_for_status()
|
||||
.context("Google userinfo endpoint error")?
|
||||
.json()
|
||||
.map_err(map_reqwest_send_err)
|
||||
.context("Google userinfo HTTP request failed")?;
|
||||
|
||||
let status = user_http.status();
|
||||
let body_bytes = user_http
|
||||
.bytes()
|
||||
.await
|
||||
.context("failed to parse Google userinfo")?;
|
||||
.context("read Google userinfo body")?;
|
||||
|
||||
if !status.is_success() {
|
||||
let body_lossy = String::from_utf8_lossy(&body_bytes);
|
||||
tracing::warn!(%status, body = %body_lossy, "Google userinfo endpoint error");
|
||||
anyhow::bail!(
|
||||
"Google userinfo error {}: {}",
|
||||
status,
|
||||
body_lossy.chars().take(512).collect::<String>()
|
||||
);
|
||||
}
|
||||
|
||||
let user: UserInfo =
|
||||
serde_json::from_slice(&body_bytes).context("failed to parse Google userinfo JSON")?;
|
||||
|
||||
Ok(OAuthUserInfo {
|
||||
provider: "google".to_string(),
|
||||
|
||||
Reference in New Issue
Block a user