fix(auth): M117 微软登录缺 client id 说「未配置」而不是「未支持」

用户实跑 debug 二进制看到「本期未支持」,误以为功能没做;实际是构建/启动时
没注入 Azure 公共客户端 id(登录三轨已实现、白名单已批、真机验签过)。

- core/auth/microsoft/client_id.rs(新):id 的来源判定(env > build,空白值不算配)、
  `ClientIdStatus`/`log_line()` 那行 `[auth] msa client_id=… source=…`、以及
  「未配置 + 怎么配(含可照抄命令)」的共用文案 `MISSING_CLIENT_ID_MESSAGE`。零网络。
- core/auth/error.rs:`AuthError::Unsupported` 不再统一拼「本期未支持:」前缀——该变体
  全仓唯一构造点就是缺 client id,硬拼前缀让「没配置」被读成「没实现」。`code()` 仍是
  `unsupported`,枚举形状与其它分支不动(tower 批的最小改动)。
- core/auth/microsoft/mod.rs:`from_env()` 走 `resolve_client_id()`,失败措辞取自上面那条
  文案,且在网络之前早退(连 HTTP 客户端都不建);导出 `client_id_status()`。
- cli:`map_auth_error` 的 Unsupported hint 给两条能照抄的命令;`accounts` 帮助说清
  「不是功能缺失」与两种注入方式;新增 `msa_client_id_log_line()` 给 GUI 侧出面。
- app-iced:`on_boot` 打一行 `[auth] msa client_id=… source=…`(判据来自 core,不重算)。
- i18n:`LaunchMsaNotConfigured` 中英各一条,golden 按 `Key::ALL` 顺序在
  `LaunchFailedWithCode` 之后追加一行。

本地便利路径(gitignored,随报告交付):`DevDocs/ops/ull-msa-run.sh` 读本地 id 文件
或 `azure-msa-app.md` 注入环境变量再起 GUI,脚本本身不含 id。
This commit is contained in:
AskaEth committed 2026-11-05 07:31:55 +08:00
1 parent 7ddcec4941
commit 8780f26308
8 files changed
+380 -43

No files matched your search

+5 -2
View File
@@ -125,8 +125,11 @@ accessToken 一律脱敏(输出里出现的是 <redacted:token>)。",
账户存在 <数据根>/.ULL/data/auth/:accounts.json(可公开元数据)与 credentials.dat(凭据)
**分文件**存放,先写凭据后写元数据、原子写、凭据文件 POSIX 侧 0600。
凭据永不打印:list 只输出名称/轨道/UUID。
add msa 走微软 device-code(用户码打在 stderr,去 https://www.microsoft.com/link 输码),
需要先配置 ULL_MSA_CLIENT_ID(Azure 公共客户端 id)。
add msa 走微软 device-code(用户码打在 stderr,去 https://www.microsoft.com/link 输码)。
它需要一个 Azure 公共客户端 id —— 登录链路是完整的,缺的只是这一项配置;id 不进仓库:
ULL_MSA_CLIENT_ID=<id> %prog% accounts add msa 每次运行时给(环境变量优先)
ULL_MSA_CLIENT_ID=<id> cargo build 构建时注入,二进制自带
两处都没有时命令立刻报「微软登录未配置」(含怎么配),**不会去联网**。
authlib-injector 账户的密码登录留给 GUI(密码进 argv 会泄露,CLI 不开这条通道)。",
),
"sessions" => Some("%prog% sessions list [--limit N] 列出会话索引(按开始时间倒序,缺省 20 条)"),
+50 -3
View File
@@ -82,9 +82,14 @@ pub(crate) fn map_auth_error(error: unlucky_core::auth::AuthError) -> CliError {
Some("重新执行 `%prog% accounts add msa` 拿新的用户码".to_string())
}
AuthError::CredentialExpired => Some("重新登录:%prog% accounts add msa".to_string()),
AuthError::Unsupported(_) => {
Some("微软登录需要设置 ULL_MSA_CLIENT_ID(Azure 公共客户端 id)".to_string())
}
// M117:id 缺失是「没配置」,不是「没实现」。hint 里给**能照抄的命令**(两条路:
// 运行期一次性的、构建期烘进二进制的),别只丢一个变量名让用户自己猜。
AuthError::Unsupported(_) => Some(format!(
"先配 Azure 公共客户端 id(id 不进仓库):\
`{env}=<id> %prog% accounts add msa`;\
想让二进制自带就构建时注入 `{env}=<id> cargo build`。环境变量优先于构建期注入。",
env = unlucky_core::auth::microsoft::CLIENT_ID_ENV
)),
AuthError::Conflict { .. } => Some("用 `%prog% accounts list` 查看已有账户".to_string()),
AuthError::NotFound { .. } => Some("用 `%prog% accounts list` 查看账户名".to_string()),
AuthError::Denied { code, .. } if code == "no_profile" || code == "no_license" => {
@@ -114,6 +119,18 @@ pub(crate) fn map_auth_error(error: unlucky_core::auth::AuthError) -> CliError {
}
}
/// GUI / CLI 共用的**启动日志一行**:`[auth] msa client_id=configured|missing source=env|build|none`。
///
/// M117:判据(配没配 + 从哪来)在 `core::auth::microsoft::client_id_status()`,这里只做出面——
/// `app-iced` 不直接依赖 `unlucky-core`,而 `unlucky-cli` 已经是它的依赖(同一个二进制既跑
/// GUI 也跑 `ull` 子命令)。所以「谁打印」不在 UI 侧重算一遍判据,免得两份口径漂。
///
/// 用途:日后看启动日志能一眼分辨「没配置」与「配了但登录失败」,不用再猜。
#[must_use]
pub fn msa_client_id_log_line() -> String {
unlucky_core::auth::microsoft::client_id_status().log_line()
}
/// 需要数据根的命令:解析上下文 → D19 支持级别闸门 → 执行 → 挂环境提示。
fn with_ctx<F>(globals: &Globals, action: F) -> Result<Outcome, CliError>
where
@@ -174,4 +191,34 @@ mod tests {
assert!(!hint.contains("代理"), "{hint}");
assert!(hint.contains("login_with_xbox_403"), "{hint}");
}
#[test]
fn startup_log_line_is_one_machine_readable_line() {
// M117:这行是「没配置 vs 配了但登录失败」的唯一机读判据,形状不能漂。
let line = super::msa_client_id_log_line();
assert!(line.starts_with("[auth] msa client_id="), "{line}");
assert!(line.contains(" source="), "{line}");
assert!(!line.contains('\n'), "必须正好一行:{line}");
let source = line.rsplit("source=").next().unwrap_or_default();
assert!(matches!(source, "env" | "build" | "none"), "{line}");
}
#[test]
fn missing_client_id_hint_carries_a_runnable_command() {
// M117:用户实跑看到的「本期未支持」是错的表述,而且 hint 只丢了变量名。
// 判据:文案说「未配置」、不说「未支持/未实现」,并给出能照抄的命令(含变量名)。
let cli = map_auth_error(AuthError::Unsupported(
unlucky_core::auth::microsoft::MISSING_CLIENT_ID_MESSAGE.to_string(),
));
let message = cli.message();
assert_eq!(cli.code(), "unsupported", "机读码保持兼容");
assert!(message.contains("客户端 id"), "{message}");
assert!(!message.contains("未支持"), "{message}");
assert!(!message.contains("未实现"), "{message}");
let hint = cli.hint().expect("缺配置要有 hint").to_string();
assert!(hint.contains("ULL_MSA_CLIENT_ID=<id>"), "{hint}");
assert!(hint.contains("cargo build"), "{hint}");
assert!(hint.contains("accounts add msa"), "{hint}");
}
}