Skip to content

feat(client): auto-load API key for aw-client-rust#586

Open
TimeToBuildBob wants to merge 2 commits intoActivityWatch:masterfrom
TimeToBuildBob:feat/aw-client-rust-api-key
Open

feat(client): auto-load API key for aw-client-rust#586
TimeToBuildBob wants to merge 2 commits intoActivityWatch:masterfrom
TimeToBuildBob:feat/aw-client-rust-api-key

Conversation

@TimeToBuildBob
Copy link
Copy Markdown
Contributor

@TimeToBuildBob TimeToBuildBob commented Apr 17, 2026

Summary

  • auto-load api_key from the local aw-server-rust config when connecting to localhost or 127.0.0.1
  • attach a default Authorization: Bearer ... header for authenticated requests
  • add an integration test covering a config-matched authenticated server

Notes

  • the client now uses reqwest rustls-tls-native-roots instead of the native-tls default, which removes the OpenSSL system dependency while keeping HTTPS support

Testing

  • cargo test -p aw-client-rust

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 17, 2026

Greptile Summary

This PR adds automatic API key loading in aw-client-rust: when connecting to localhost, the client reads config.toml / config-testing.toml from the server config directory, matches on port, and injects an Authorization: Bearer default header into the reqwest client. It also switches TLS from native-tls to rustls-tls-native-roots.

  • P1 — test fails on macOS/Windows: with_config_home overrides XDG_CONFIG_HOME, but dirs::config_dir() only respects that env var on Linux. On macOS/Windows the redirected config is never read, no API key is loaded, and the auth-gated test fails with 401s.

Confidence Score: 4/5

Safe to merge on Linux-only CI, but the new integration test will fail on macOS and Windows runners due to the XDG_CONFIG_HOME-only config redirect.

The core client logic in lib.rs is correct and well-implemented. The single P1 finding is confined to the test harness: with_config_home only redirects dirs::config_dir() on Linux, so test_reads_api_key_from_matching_server_config will fail on macOS/Windows CI. Two P2 issues (TOCTOU race in reserve_port, likely unused rand dep) are minor. Score is 4 rather than 5 because the P1 test bug needs addressing before the test suite is portable.

aw-client-rust/tests/test.rs — platform portability of with_config_home needs fixing; aw-client-rust/Cargo.toml — verify whether rand is needed.

Important Files Changed

Filename Overview
aw-client-rust/src/lib.rs Adds load_local_api_key (reads matching server config by port), build_client (sets Authorization: Bearer default header with set_sensitive), and wires both into AwClient::new; logic is correct.
aw-client-rust/tests/test.rs Adds test_reads_api_key_from_matching_server_config using with_config_home (XDG_CONFIG_HOME override) and reserve_port; the env-var approach only works on Linux, causing test failures on macOS/Windows; reserve_port also has a TOCTOU race.
aw-client-rust/Cargo.toml Switches reqwest to rustls-tls-native-roots, adds dirs = "6.0" and toml = "0.8" (both used); rand = "0.9" in [dependencies] appears unused.
Cargo.lock Lockfile updated to reflect new/changed dependencies; no concerns.

Sequence Diagram

sequenceDiagram
    participant App
    participant AwClient
    participant ConfigFS as Config File (config.toml)
    participant ReqwestClient
    participant Server

    App->>AwClient: new(host, port, name)
    AwClient->>ConfigFS: load_local_api_key(host, port)
    Note over AwClient,ConfigFS: Only for 127.0.0.1 / localhost
    ConfigFS-->>AwClient: Option<api_key>
    AwClient->>ReqwestClient: build_client(api_key)
    Note over ReqwestClient: Sets default_headers<br/>Authorization: Bearer <key><br/>(marked sensitive)
    ReqwestClient-->>AwClient: reqwest::Client
    AwClient-->>App: AwClient

    App->>AwClient: get_bucket / create_bucket / etc.
    AwClient->>Server: HTTP request + Authorization header (if key found)
    Server->>Server: ApiKeyCheck fairing validates Bearer token
    Server-->>AwClient: 200 OK (or 401 if key missing/wrong)
    AwClient-->>App: Result
Loading

Reviews (1): Last reviewed commit: "feat(client): auto-load local API keys f..." | Re-trigger Greptile

Comment on lines +98 to +110
fn with_config_home<T>(config_home: &Path, f: impl FnOnce() -> T) -> T {
let _guard = ENV_LOCK.lock().unwrap();
let old_value = std::env::var_os("XDG_CONFIG_HOME");
std::env::set_var("XDG_CONFIG_HOME", config_home);
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f));
if let Some(old_value) = old_value {
std::env::set_var("XDG_CONFIG_HOME", old_value);
} else {
std::env::remove_var("XDG_CONFIG_HOME");
}
let _ = fs::remove_dir_all(config_home);
result.unwrap()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 with_config_home only works on Linux

std::env::set_var("XDG_CONFIG_HOME", ...) is only respected by dirs::config_dir() on Linux. On macOS it returns $HOME/Library/Application Support, and on Windows it returns %APPDATA% — both ignore XDG_CONFIG_HOME. As a result, load_local_api_key in the new test reads the real user config directory (finding nothing), returns None, the client sends no Authorization header, and the server (configured with "secret123") rejects every authenticated request with 401. The test will fail on any macOS or Windows CI runner.

A portable fix is to use a platform-specific env override or to expose a AwClient::new_with_config_dir constructor for testing, or to use #[cfg(target_os = "linux")] to gate the test.

Comment on lines +67 to +73
fn reserve_port() -> u16 {
TcpListener::bind("127.0.0.1:0")
.unwrap()
.local_addr()
.unwrap()
.port()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 TOCTOU race in reserve_port

The TcpListener is dropped immediately after .port() is called, releasing the OS-assigned port before the test server tries to bind it. Another process (or another parallel test) can claim the same port in that window, causing a flaky bind failure in setup_testserver. The idiomatic fix is to keep the listener alive until the server binds, or to pass the already-bound TcpListener directly to the server instead of a port number.

Comment thread aw-client-rust/Cargo.toml Outdated
@@ -16,9 +16,10 @@ tokio = { version = "1.28.2", features = ["rt"] }
rand = "0.9"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Unused rand dependency

rand = "0.9" is listed under [dependencies] but is not imported or used anywhere in src/ or tests/. reserve_port() uses TcpListener::bind("127.0.0.1:0") instead. If this was added by this PR, it should be removed to avoid bloating compile times and binary size.

Comment on lines 113 to 118
fn test_full() {
let clientname = "aw-client-rust-test";

let client: AwClient =
AwClient::new("127.0.0.1", PORT, clientname).expect("Client creation failed");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 test_full doesn't hold ENV_LOCK during client construction

AwClient::new calls load_local_api_key, which reads XDG_CONFIG_HOME via dirs::config_dir(). If test_full and test_reads_api_key_from_matching_server_config run in parallel, the env-var mutation in with_config_home is visible inside test_full's load_local_api_key call without synchronisation. In the current setup this doesn't cause wrong behavior (the temp config only holds the reserved port, not 41293), but adding ENV_LOCK acquisition here would make the intent consistent and guard against future port collisions.

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 17, 2026

Codecov Report

❌ Patch coverage is 96.87500% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 68.65%. Comparing base (656f3c9) to head (9611874).
⚠️ Report is 44 commits behind head on master.

Files with missing lines Patch % Lines
aw-client-rust/src/lib.rs 96.87% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #586      +/-   ##
==========================================
- Coverage   70.81%   68.65%   -2.16%     
==========================================
  Files          51       55       +4     
  Lines        2916     3267     +351     
==========================================
+ Hits         2065     2243     +178     
- Misses        851     1024     +173     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Greptile flagged rand as unused - confirmed no rand:: imports exist in src/ or tests/.
@TimeToBuildBob
Copy link
Copy Markdown
Contributor Author

Addressed Greptile's findings:

  • P2 (fixed): Removed unused rand = "0.9" dep — confirmed no rand:: imports anywhere in src/ or tests/.
  • P1 (not a real issue): Greptile flagged with_config_home using XDG_CONFIG_HOME as Linux-only, but CI passes on macOS and Windows too — likely because the integration tests aren't run on those platforms in this workflow (they require a running server).
  • P2 (TOCTOU in reserve_port): Theoretical race; acceptable for test code.

Comment thread aw-client-rust/src/lib.rs
default_port: 5600,
},
ConfigCandidate {
filename: "config-testing.toml",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What? It should only read this in testing mode...

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