From 178f6ebbffdb78a65d42645d12f9780232c625a0 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Wed, 4 Nov 2026 22:03:39 +0800 Subject: [PATCH] =?UTF-8?q?fix(presenter):=20M114=20=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E5=88=86=E7=B1=BB=E5=8F=AA=E7=9C=8B=E7=BB=93=E6=9E=84=E5=8C=96?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=EF=BC=88status/kind/server=5Fmessage?= =?UTF-8?q?=EF=BC=89=EF=BC=8C=E4=B8=8D=E5=86=8D=20Debug=20=E6=95=B4?= =?UTF-8?q?=E6=9D=A1=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `AuthError::Response` 的判据从 `format!("{detail:?}")`(连中文 what/digest 一起扫) 改成读 `ResponseDetail{status, kind, server_message, digest}` - 5xx 与拦截页(Html/Text)归 `Unreachable`(「连不上」而不是「服务器拒绝了你」), AADSTS/consent/whitelist 标记仍优先判 `NotWhitelisted` - 补两条分类用例:AADSTS 优先于一般拒绝、5xx/拦截页归不可达 --- crates/presenter/src/host/accounts/failure.rs | 61 ++++++++++++++++++- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/crates/presenter/src/host/accounts/failure.rs b/crates/presenter/src/host/accounts/failure.rs index 541e071..b04833e 100644 --- a/crates/presenter/src/host/accounts/failure.rs +++ b/crates/presenter/src/host/accounts/failure.rs @@ -5,6 +5,8 @@ use unlucky_core::auth::AuthError; +use unlucky_core::auth::BodyKind; + use crate::dto::{AccountNoticeCode, LoginFailure}; /// 账户写操作的失败:**结构化码 + 原文**(原文只进日志/详细信息,不上屏)。 @@ -44,10 +46,17 @@ pub(super) fn classify(error: &AuthError) -> LoginFailure { LoginFailure::Denied } } + // 只看 `ResponseDetail` 的**结构化字段**(status / kind / server_message), + // 不把整条错误 Debug 成字符串再去扫——那会把中文 `what`/`digest` 也算进判据里。 AuthError::Response(detail) => { - let blob = format!("{detail:?}"); - if mentions_whitelist(&blob) { + let server = detail.server_message.as_deref().unwrap_or_default(); + let digest = detail.digest.as_str(); + if mentions_whitelist(server) || mentions_whitelist(digest) { LoginFailure::NotWhitelisted + } else if detail.status >= 500 || matches!(detail.kind, BodyKind::Html | BodyKind::Text) + { + // 5xx / 拦截页:服务器或链路出了问题,不是「它拒绝了你」。 + LoginFailure::Unreachable } else { LoginFailure::Denied } @@ -67,3 +76,51 @@ pub(super) fn mentions_whitelist(text: &str) -> bool { || lowered.contains("whitelist") || lowered.contains("not approved") } + +#[cfg(test)] +mod tests { + use super::*; + use unlucky_core::auth::body::ResponseDetail; + + fn response(status: u16, kind: BodyKind, server_message: Option<&str>) -> AuthError { + AuthError::Response(Box::new(ResponseDetail { + step: "xsts".to_string(), + what: "XSTS 授权".to_string(), + status, + content_type: None, + kind, + server_message: server_message.map(str::to_string), + digest: "digest".to_string(), + })) + } + + /// 403 带 AADSTS 码 → 白名单未批;403 不带 → 一般性拒绝。 + #[test] + fn aadsts_markers_win_over_the_generic_denial() { + assert_eq!( + classify(&response( + 403, + BodyKind::Json, + Some("AADSTS7000218: app not approved") + )), + LoginFailure::NotWhitelisted + ); + assert_eq!( + classify(&response(403, BodyKind::Json, Some("invalid_grant"))), + LoginFailure::Denied + ); + } + + /// 5xx 与「拦截页」(HTML/纯文本)归「连不上」,不是「服务器拒绝了你」。 + #[test] + fn upstream_failures_and_intercept_pages_are_unreachable() { + assert_eq!( + classify(&response(503, BodyKind::Json, None)), + LoginFailure::Unreachable + ); + assert_eq!( + classify(&response(200, BodyKind::Html, None)), + LoginFailure::Unreachable + ); + } +}