Skip to content

Add board authentication client and unify server config#154

Merged
ZR233 merged 11 commits into
mainfrom
pub
Jul 22, 2026
Merged

Add board authentication client and unify server config#154
ZR233 merged 11 commits into
mainfrom
pub

Conversation

@ZCShou

@ZCShou ZCShou commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

ostool 登录与令牌管理变更说明

变更内容与边界

本 PR 为 ostool 增加登录、凭据保存、Token 刷新和注销能力,并将 board 服务地址统一为 URL 配置。

  • 不修改局域网直接运行的 ostool-server;现有匿名 board API 保持可用。
  • 公网部署通过认证网关提供 OAuth 接口,并在需要时保护 board API 与串口 WebSocket。
  • 不保存用户密码;不将 Token 写入项目配置、命令行参数、日志或 shell history。
  • 支持短期 Access Token、可轮换 Refresh Token、PAT 和自动化环境变量 Token。

配置与命令行

局域网仍使用匿名访问,但配置格式统一为 URL 型 server

[board]
server = "http://192.168.1.20"
port = 2999
auth_mode = "disabled"

公网认证网关使用完整 HTTPS 地址:

[board]
server = "https://board.example.com"
auth_mode = "required"

规则:

  • server 必须为包含 http://https:// 的完整 URL;
  • port 为可选覆盖项;未填写时保留 URL 的显式端口或使用协议默认端口;
  • auth_mode = "required" 时必须使用 HTTPS;
  • 项目级 .board.toml 可设置 serverportauth_mode,并由命令行参数覆盖。
  • 相对于 dev,原有的 server_ip 被替换为 URL 型 server,原来必填的 port 变为可选覆盖项;命令行 --server 也必须使用完整 URL。

新增命令:

# 浏览器 Device Authorization 登录
ostool login [--server URL] [--port PORT]

# 从标准输入导入 Web 管理台创建的 PAT
printf '%s' "$OSTOOL_PAT" | ostool login --with-token [--server URL] [--port PORT]

# 显示登录状态,不显示 Token
ostool auth status [--server URL] [--port PORT]

# 尽力撤销 OAuth 会话并删除本地凭据
ostool logout [--server URL] [--port PORT]

客户端调用的认证网关接口

采用 OAuth 2.0 Device Authorization Grant,全部请求为 application/x-www-form-urlencoded

方法 路径 请求字段 用途
POST /oauth/device/code client_id=ostool-cliscope=board:operate offline_access 发起浏览器登录。
POST /oauth/token grant_type=urn:ietf:params:oauth:grant-type:device_codedevice_codeclient_id 轮询换取 Token。
POST /oauth/token grant_type=refresh_tokenrefresh_tokenclient_id 刷新 Access Token。
POST /oauth/revoke tokentoken_type_hint=refresh_tokenclient_id 撤销 OAuth 会话。

Device Code 响应至少应包含:

{
  "device_code": "...",
  "user_code": "ABCD-EFGH",
  "verification_uri": "https://board.example.com/activate",
  "expires_in": 600,
  "interval": 5
}

Token 响应应包含:

{
  "access_token": "...",
  "refresh_token": "...",
  "token_type": "Bearer",
  "expires_in": 900
}

scope 为可选字段。服务端应在刷新时返回新的 Refresh Token,以支持 Token rotation。token_type 必须为 Bearerexpires_in 必须为正数。

轮询期间,认证网关应按 OAuth 约定以非 2xx 响应返回 authorization_pendingslow_down。客户端分别继续等待或将轮询间隔增加 5 秒;成功的 2xx 响应必须包含完整的 Token 字段。

凭据与生命周期

凭据按认证网关 URL 隔离保存,优先使用系统 credential store:

  • macOS:Keychain;
  • Windows:Credential Manager;
  • Linux:Secret Service 兼容服务。

系统凭据库不可用时,回退到 $XDG_CONFIG_HOME/ostool/hosts.json(或 ~/.config/ostool/hosts.json),并限制文件权限为 0600

保存的敏感数据仅包括 OAuth Refresh Token、缓存的 Access Token 或 PAT。用户密码不落盘。

Token 使用策略:

  1. auth_mode = "disabled":不读取、不发送 Token;
  2. OSTOOL_BOARD_ACCESS_TOKEN:只用于当前进程,不保存、不刷新;
  3. PAT:直接作为 Bearer Token 使用,不刷新;
  4. OAuth Access Token 剩余有效期超过 60 秒:直接使用;
  5. 否则使用 Refresh Token 刷新并覆盖本地凭据。

多个 ostool 进程共用 Refresh Token 时,使用 $XDG_CACHE_HOME/ostool/auth-locks/ 下的 URL 哈希锁文件避免并发刷新。收到 invalid_grantinvalid_token 或 board API 的 401 时,删除本地凭据并提示重新登录;临时网络错误保留凭据供后续重试。

Board 请求认证

auth_mode = "required" 时,所有现有 board REST 请求、文件上传和串口 WebSocket 握手均附加:

Authorization: Bearer <access-token>

认证模式下禁用 HTTP 重定向,并拒绝向不同 scheme、host 或 port 的绝对 WebSocket URL 发送 Token。匿名局域网模式不携带 Authorization Header。

修改范围

  • 新增 ostool/src/auth/:OAuth 客户端、凭据存储、Token 生命周期管理;
  • 扩展全局和项目级 board 配置,支持 URL 型 server、可选 portauth_mode
  • 增加 loginlogoutauth status CLI;
  • 在 board HTTP 与 WebSocket 请求边界注入 Bearer Token;
  • 更新中英文 README 和本文档;
  • 增加 URL 配置、CLI 参数、WebSocket Bearer Header 与跨源限制测试。

尚未提供 OAuth 生命周期、REST Bearer Header、凭据文件回退和 401 凭据失效的 mock 网关自动化测试;这些场景列在下方作为手工验收项。

验收

本地编译与自动化测试

cargo fmt --check
cargo build -p ostool
cargo test -p ostool --quiet
cargo clippy -p ostool --all-targets -- -D warnings

编译后的 CLI 位于 target/debug/ostool

target/debug/ostool --help

本地匿名 board 服务

启动本地 ostool-server 后,使用以下匿名配置验证局域网访问:

# ~/.ostool/config.toml
[board]
server = "http://127.0.0.1"
port = 2999
auth_mode = "disabled"
target/debug/ostool board ls
target/debug/ostool board connect --board-type <TYPE>

本地认证网关

登录鉴权测试需要一个本地 HTTPS 认证网关;它应在同一 Base URL 下提供 /oauth/device/code/oauth/token/oauth/revoke,并代理或实现 /api/v1/... board API。开发证书必须被操作系统信任,客户端不提供跳过 TLS 校验的选项。

# ~/.ostool/config.toml
[board]
server = "https://localhost:8443"
auth_mode = "required"

可先用 PAT 验证 Bearer Header 是否被携带:

printf '%s' 'local-test-token' | target/debug/ostool login --with-token --server https://localhost:8443
target/debug/ostool auth status
target/debug/ostool board ls
target/debug/ostool logout

可再用 mock 网关手工验证 Device Code 轮询、刷新、撤销、Refresh Token rotation、凭据文件回退和 401 失效处理。可将 OAuth Token 响应的 expires_in 设为很短的值,再执行 board ls 确认刷新请求;不依赖真实开发板或真实公网服务。

@ZCShou
ZCShou marked this pull request as draft July 20, 2026 02:07
@ZCShou
ZCShou marked this pull request as ready for review July 21, 2026 00:27

@mai-team-app mai-team-app Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds OAuth/PAT authentication, credential storage, URL-based board endpoint resolution, and Bearer propagation to board HTTP/WebSocket flows. The authentication behavior is largely isolated to the board-client path, but the endpoint migration changes the default contract used by every ostool board command.

Blocking issue:

  • A newly created global config now resolves to http://localhost/ (HTTP port 80), whereas the existing client default and ostool-server default both use port 2999. Consequently a fresh install—or any board command before configuring a port—cannot reach the bundled/default server. Preserve :2999 in the URL default or keep port = Some(2999) while retaining URL overrides, and add a resolution-level regression test.

Validation: helper self-tests passed and git diff --check passed. I could not run cargo fmt --check, clippy, or crate tests locally because this environment has no cargo binary. Both current GitHub Actions check (stable, x86_64-unknown-linux-gnu) runs succeeded; no CI failure is attributed to this PR.

Previous review comments and PR discussion: none were present. I found no materially overlapping authentication PR in the repository search. Remaining risk after the blocking fix is the unrun hardware/OAuth integration path; the current code changes real board, serial, and credential workflows.

Powered by gpt-5.6-terra

Comment thread ostool/src/board/global_config.rs Outdated

@mai-team-app mai-team-app Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds OAuth/PAT authentication and credential lifecycle handling, moves board endpoints to URL-based configuration, and propagates Bearer credentials through board HTTP and serial WebSocket requests. It addresses public authenticated board gateways, while retaining anonymous LAN mode; however, the global endpoint configuration is a shared contract for every existing ostool board command.

The current blocking issue is migration of existing global configuration, described inline. The base release writes [board] server_ip and port, but the new schema rejects that persisted format outright. This breaks upgrades before board commands—or the new login/status commands that load the same global config—can reach their endpoint.

Validation: both current GitHub Actions check (stable, x86_64-unknown-linux-gnu) runs succeeded, with no CI failure attributable to this PR. Locally, prepare-review, helper self-tests, and git diff --check origin/main...HEAD passed. The planned cargo fmt --check, package clippy with -D warnings, and package tests could not run because this environment has neither cargo nor rustc; no hardware/OAuth gateway integration was attempted.

Previous review feedback was considered: the earlier blocking default-port issue is resolved at this head (http://localhost:2999) and has a regression test. Its old inline comment is therefore resolved; no other actionable review or PR comments remain. A repository PR search found no overlapping authentication/client change (the other open PR is unrelated boot-readiness work).

Remaining risk after the requested migration is the unexercised real credential-store, OAuth, board, and serial-hardware paths. The configuration migration remains blocking.

Powered by gpt-5.6-terra

Comment thread ostool/src/board/global_config.rs Outdated

@mai-team-app mai-team-app Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds OAuth Device Authorization / PAT authentication, URL-based board endpoint configuration, credential storage across OS keyrings, and Bearer token propagation through all board HTTP and WebSocket flows. It is a well-structured, carefully reviewed change that addresses both issues from the two previous review rounds.

Previous review findings resolved

  1. Default port regression (review #4741140519): DEFAULT_BOARD_SERVER is now http://localhost:2999, matching the established ostool-server default. The default_config_resolves_to_the_board_server_default_port test verifies this.
  2. Legacy server_ip migration (review #4741937731): BoardGlobalConfigInput accepts server_ip during deserialization and rewrites it as server on save. deny_unknown_fields applies to the input struct, not the output struct. The load_migrates_base_server_ip_config_and_save_writes_url_format test covers the round-trip.

Validation

  • Both GitHub Actions check (stable, x86_64-unknown-linux-gnu) runs succeeded with no CI failure attributable to this PR.
  • git diff --check origin/main...HEAD passed.
  • prepare-review confirmed the workspace HEAD matches 314ed40cba70c8d570563db99d2127929fee6f66.
  • The container lacks cargo, so cargo fmt --check, clippy, and crate tests could not be run locally; CI already covers the full check workflow.

Review notes

  • The PR modifies only the ostool crate; no cross-crate breakage is expected.
  • Auth behaviour is gated behind auth_mode and returns None (anonymous) when disabled, preserving the existing LAN workflow.
  • The RefreshLock uses fs4 file locks to serialise cross-process OAuth refresh; token rotation and invalidation-on-401 are handled correctly.
  • Legacy bare-IP configurations are auto-wrapped with http:// and IPv6 brackets, then migrated on next save.
  • The TUI preserves auth_mode but does not expose an editable control; users should edit the config file or use the new CLI commands (ostool login / ostool auth status) for auth setup.
  • resolve_auth_endpoint forces auth_mode = Required when --server is explicit for login/logout—this is intentional: providing a server URL during authentication means you intend to authenticate against it.

Design & risk

The separation of auth from board::client is clean: TokenManager owns credential lifecycle, BoardServerClient consumes tokens through authorization_token() without knowing OAuth internals. The endpoint resolution cascade (CLI → project config → global config) follows existing patterns. No unwrap() or expect() calls exist in the new production paths beyond well-justified unreachable! after a runtime-checked variant.

Residual risk is the unexercised hardware/OAuth gateway integration path since no real board server or credential store was available for manual testing. The unit tests, CI, and structured error handling provide reasonable confidence.

Powered by deepseek-v4-pro

@mai-team-app mai-team-app Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds OAuth Device Authorization / PAT authentication, URL-based board endpoint configuration, credential storage across OS keyrings, and Bearer token propagation through all board HTTP and WebSocket flows.

Previous review findings resolved

Both blocking issues from prior review rounds are satisfactorily addressed at this head:

  1. Default port regression (review #4741140519): DEFAULT_BOARD_SERVER is now http://localhost:2999, matching the established ostool-server default. The default_config_resolves_to_the_board_server_default_port test verifies this.
  2. Legacy server_ip migration (review #4741937731): BoardGlobalConfigInput accepts server_ip during deserialization and rewrites it as server on save. deny_unknown_fields applies to the input struct only. The load_migrates_base_server_ip_config_and_save_writes_url_format test covers the round-trip.

Validation

  • Both GitHub Actions check (stable, x86_64-unknown-linux-gnu) runs succeeded with no CI failure attributable to this PR.
  • git diff --check origin/main...HEAD passed.
  • prepare-review confirmed the workspace HEAD matches 314ed40cba70c8d570563db99d2127929fee6f66.
  • The container lacks cargo, so cargo fmt --check, clippy, and crate tests could not be run locally; CI already covers the full check workflow.

Review notes

  • The PR modifies only the ostool crate; no cross-crate breakage is expected.
  • Auth behaviour is gated behind auth_mode and returns None (anonymous) when disabled, preserving the existing LAN workflow.
  • The RefreshLock uses fs4 file locks to serialise cross-process OAuth refresh; token rotation and invalidation-on-401 are handled correctly.
  • Legacy bare-IP configurations are auto-wrapped with http:// and IPv6 brackets, then migrated on next save.
  • The TUI preserves auth_mode but does not expose an editable control; users should edit the config file or use the new CLI commands for auth setup.
  • resolve_auth_endpoint forces AuthMode::Required when --server is explicit for login/logout—this is intentional: providing a server URL during authentication means you intend to authenticate against it, and HTTPS is appropriately enforced.
  • No unwrap() or expect() calls exist in the new production paths beyond well-justified checks (static API paths, post-null-check expects, etc.).

Design & risk

The separation of auth from board::client is clean: TokenManager owns credential lifecycle, BoardServerClient consumes tokens through authorization_token() without knowing OAuth internals. The endpoint resolution cascade (CLI → project config → global config) follows existing patterns. Cross-origin WebSocket Bearer protection is implemented in resolve_ws_url.

Residual risk is the unexercised hardware/OAuth gateway integration path since no real board server or credential store was available for manual testing. The unit tests, CI, and structured error handling provide reasonable confidence.

No overlapping PRs were found in repository search.

Powered by deepseek-v4-pro

@mai-team-app mai-team-app Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

This PR adds OAuth 2.0 Device Authorization Grant and PAT authentication to the ostool board client, migrates board endpoint configuration from a bare server_ip + port pair to a complete URL (server, optional port override, auth_mode), and propagates Bearer credentials through every board HTTP and serial WebSocket request. New top-level commands login, logout, and auth status are added; credentials are stored in the OS keyring with a 0600 file fallback, and a cross-process refresh lock prevents concurrent refresh-token rotation races.

Previous review findings — both resolved at this head

  1. Default port regression (earlier CHANGES_REQUESTED): DEFAULT_BOARD_SERVER is now http://localhost:2999, matching the established ostool-server default. The default_config_resolves_to_the_board_server_default_port test guards this.
  2. Legacy server_ip migration (earlier CHANGES_REQUESTED): BoardGlobalConfigInput accepts the persisted server_ip field during deserialization and rewrites it as server on save; deny_unknown_fields applies only to the input struct, not the output struct. load_migrates_base_server_ip_config_and_save_writes_url_format covers the round-trip. The old inline comments are therefore resolved.

Validation

  • Both GitHub Actions check (stable, x86_64-unknown-linux-gnu) runs on 314ed40 succeeded (fmt, clippy, build, test, publish dry-run) — no CI failure is attributable to this PR.
  • prepare-review confirmed the workspace HEAD matches 314ed40cba70c8d570563db99d2127929fee6f66; git diff --check origin/main...HEAD passed.
  • This container has no cargo/rustc, so I could not re-run fmt/clippy/tests locally; CI already covers the full check workflow.

Review notes

  • The change is confined to the ostool crate; no cross-crate contract is broken.
  • Auth behaviour is gated behind auth_mode; authorization_token() returns None (anonymous) when disabled, preserving the existing LAN workflow.
  • Security posture is sound: redirects are disabled on both the auth client and board client (Policy::none()), no_proxy() is set, auth_mode = required enforces HTTPS, cross-origin absolute WebSocket URLs are rejected before Bearer credentials are attached, and the credential-file fallback is restricted to 0600.
  • Token refresh acquires an fs4 advisory lock and re-reads storage so another process can satisfy the request, then rotates and overwrites credentials on success; invalid_grant/invalid_token/401 clear local credentials while transient network errors preserve them for retry.
  • The single unreachable!() in the refresh path is justified by AuthClient::refresh always returning OAuthRefresh; no other unwrap/expect exists in the new production paths.
  • README Chinese and English versions are consistent, as required by AGENTS.md.
  • TUI preserves auth_mode but does not expose an editable control; users configure auth via the config file or the new CLI commands, which is acceptable for this scope.

Risk

Residual risk is the unexercised real credential-store / OAuth-gateway / board-hardware integration path — no live board server or keyring backend was available for manual testing. The unit tests, CI, and structured error handling provide reasonable confidence for merge.

Powered by glm-5.2

@ZR233
ZR233 merged commit 7318072 into main Jul 22, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants