feat(ios,android): P2 wave, git panel, token UX, per-host WS token, docs
App layer, four sequential slices (a shared .xcodeproj means adding files regenerates it, so these could not run in parallel): - token UX end to end: pairing prompts for a token when a host 401s, POST /auth validates it, and 204-without-Set-Cookie is correctly read as "this server has auth disabled" rather than "authenticated". A host paired before the token was turned on recovers by re-pairing in place. Remove-host now exists and finally gives PushRegistrar.handleHostRemoved a caller. - project git panel + worktree lifecycle (T-iOS-32) + claude --resume history — the parity gap with Android and the web front end. - terminal search (T-iOS-33) and voice PTT (T-iOS-31) with an epoch guard so a session switch between dictation and confirm cannot inject into the wrong session. - theme + Dynamic Type (T-iOS-34) and web ?join= interop (T-iOS-35). RootView no longer hard-locks .preferredColorScheme(.dark). Also unpins SwiftTerm to 1.15.0 by dropping the local hasActiveSelection that collided with the upstream one, verified green from a fresh derivedDataPath. Includes the two HIGH fixes the security review found: - iOS resolved the WS token host-independently, so a token-gated host sitting next to an open one could never open a terminal and no on-screen remedy could fix it. Now one transport per host; cross-host leakage is structurally impossible since both read paths return only that host's own value. - Android reported the host's own git-credential 401 (git-ops.ts:108, "Push authentication required on the host.") as "your access token is wrong", because a blanket 401 mapping ran ahead of the per-route one. Git-write routes are now ROUTE_DEFINED and keep the server's message. And the doc sync: README/ios README no longer claim the client is unmerged on feat/ios-client, the Clients section finally lists Android, and the plan checkboxes reflect what is actually built. iOS 534 app tests + 452 package tests; Android 687 tests.
This commit is contained in:
@@ -78,14 +78,30 @@ struct DeepLinkRouterTests {
|
||||
|
||||
// MARK: - URL 解析:白名单拒绝路径(任一非法 → .ignore)
|
||||
|
||||
@Test("scheme 非 webterminal → .ignore")
|
||||
func wrongSchemeIgnored() throws {
|
||||
/// T-iOS-35 **有意改写**:本例原名"scheme 非 webterminal → .ignore",断言
|
||||
/// http(s) 一律被拒。web 分享 QR 互通落地后 http(s) 有了自己的白名单形状
|
||||
/// (`<origin>/?join=<uuid>`,见 `DeepLinkJoinTests`),所以那条断言的**理由**
|
||||
/// 变了:这里的 URL 仍 `.ignore`,但原因是它带了 `host=` 这个多余 query 键
|
||||
/// (web 形状精确只允许单一 `join` 键),而不是"scheme 不对"。
|
||||
/// scheme 白名单本身改由下一例(非 http/https/webterminal)继续钉住。
|
||||
@Test("https 但不是 web 分享形状(多余 query 键)→ .ignore")
|
||||
func httpsWithExtraQueryKeysIgnored() throws {
|
||||
let route = DeepLinkRouter.route(
|
||||
url: try Self.url("https://open?host=\(Self.validHostId)&join=\(Self.validSessionId)")
|
||||
)
|
||||
#expect(route == .ignore)
|
||||
}
|
||||
|
||||
@Test("白名单外的 scheme(ftp/file/javascript)→ .ignore")
|
||||
func nonWhitelistedSchemeIgnored() throws {
|
||||
for scheme in ["ftp", "file", "javascript"] {
|
||||
let route = DeepLinkRouter.route(
|
||||
url: try Self.url("\(scheme)://open?join=\(Self.validSessionId)")
|
||||
)
|
||||
#expect(route == .ignore, "\(scheme) 不该被接受")
|
||||
}
|
||||
}
|
||||
|
||||
@Test("action 非 open → .ignore")
|
||||
func wrongActionIgnored() throws {
|
||||
let route = DeepLinkRouter.route(
|
||||
|
||||
Reference in New Issue
Block a user