jlcodes99/cockpit-tools · error
Token 解析失败: {}
Error message
Token 解析失败: {} What it means
Returned by oauth::exchange_code when the token endpoint answered with a success HTTP status but the response body could not be deserialized into TokenResponse (response.json::<TokenResponse>() failed). The serde error is wrapped as "Token 解析失败: {}" and propagated, so the authorization code exchange yields no tokens.
Source
Thrown at crates/cockpit-core/src/modules/oauth.rs:136
let response = client
.post(TOKEN_URL)
.form(¶ms)
.send()
.await
.map_err(|e| {
let msg = format!("Token 交换请求失败: {}", e);
crate::modules::logger::log_error(&msg);
msg
})?;
let status = response.status();
crate::modules::logger::log_info(&format!("Token 交换响应状态: {}", status));
if status.is_success() {
let mut token_res = response.json::<TokenResponse>().await.map_err(|e| {
let msg = format!("Token 解析失败: {}", e);
crate::modules::logger::log_error(&msg);
msg
})?;
token_res.oauth_client_key = Some(client_key);
if token_res.refresh_token.is_some() {
crate::modules::logger::log_info("Token 交换成功, 获取到 refresh_token");
} else {
crate::modules::logger::log_warn(
"警告: Google 未返回 refresh_token, 可能之前已授权过此应用",
);
}
Ok(token_res)
} else {
let error_text = response.text().await.unwrap_or_default();
let msg = format!("Token 交换失败 ({}),body_len={}", status, error_text.len());
crate::modules::logger::log_error(&msg);
Err(msg)View on GitHub (pinned to 1ed8b77992)
Solutions
- Inspect the wrapped serde message to see which expected field/type mismatched, then capture the raw body for comparison.
- Check whether a proxy or captive portal returned HTML instead of JSON (curl the endpoint from the same machine).
- Update Cockpit Tools if the provider changed the token response schema; report a schema mismatch otherwise.
- Retry the login from a network without TLS/HTTP interception.
- If a required field (e.g. access_token/refresh_token) is missing, verify the client_id/scopes sent during authorization.
Example fix
// before: assuming 2xx means valid token JSON
let tokens = exchange_code(&code, &verifier).await?;
// after: distinguish parse failure and log the raw body
match exchange_code(&code, &verifier).await {
Err(e) if e.contains("Token 解析失败") => {
log_raw_token_response_for_debug(); // inspect actual payload
ui.show("登录响应格式异常,请检查网络代理后重试");
}
r => handle(r),
} Defensive patterns
Strategy: try-catch
Validate before calling
// Detect non-JSON (HTML/proxy) 200 responses by shape before trusting tokens
fn looks_like_html(body: &str) -> bool {
let b = body.trim_start();
b.starts_with('<') || b.to_ascii_lowercase().contains("<html")
} Type guard
fn is_token_parse_failure(err: &str) -> bool {
err.starts_with("Token 解析失败: ")
} Try / catch
match exchange_code(&code, &verifier).await {
Err(e) if is_token_parse_failure(&e) => {
log::error!("token response not parseable: {} — check proxy/captive portal", e);
ui.show("登录响应异常,请关闭代理/验证网络后重试");
}
other => handle(other),
} Prevention
- Avoid networks with captive portals or HTTP-intercepting proxies during login.
- Keep the app updated so TokenResponse matches the provider's current schema.
- When this fires, capture the raw body (curl the endpoint) to compare with the expected fields.
- Check for CDN/WAF challenge pages returning 200 with HTML.
- Verify required fields (access_token/refresh_token) exist in provider responses after provider updates.
When it happens
Trigger: Calling exchange_code when the server returns 2xx with a body that is not the expected TokenResponse JSON: an HTML error/captive-portal page with status 200, missing required fields, changed API schema, or a proxy returning a 200 interstitial.
Common situations: Captive portal or proxy injecting HTML with 200 status; provider changed the token response shape (removed/renamed fields) after a client/server version mismatch; CDN/WAF challenge page; truncated response body.
Related errors
- invalidJsonMessage
- messages.invalidJson
- messages.empty
- errorCode (parseJsonOrThrow caller-supplied)
- invalid_bundle_root
AI-assisted analysis of jlcodes99/cockpit-tools@1ed8b77992 (2026-09-05).
Data as JSON: /api/errors/a75a92826743cabf.
Report an issue: GitHub.