feat(client): auto-load local API keys#106
feat(client): auto-load local API keys#106TimeToBuildBob wants to merge 4 commits intoActivityWatch:masterfrom
Conversation
Greptile SummaryAuto-loads the Confidence Score: 5/5Safe to merge — all findings are non-blocking P2 style suggestions. The core logic is correct and well-tested: localhost check, port matching, bearer header injection, and the remote-server skip path all work as intended. The three flagged issues (missing IPv6
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[ActivityWatchClient.__init__] --> B[load_local_server_api_key]
B --> C{host is localhost?}
C -- No --> D[return None]
C -- Yes --> E[get_config_dir aw-server-rust]
E --> F[Iterate candidates:\nconfig.toml, config-testing.toml]
F --> G{File exists?}
G -- No --> F
G -- Yes --> H[Parse TOML with tomlkit]
H --> I{port matches?}
I -- No --> F
I -- Yes --> J{auth.api_key present?}
J -- No --> F
J -- Yes --> K[return key string]
F -- exhausted --> D
K --> L[store as server_api_key]
L --> M[_headers on every request]
M --> N{server_api_key set?}
N -- Yes --> O[Inject Authorization Bearer header]
N -- No --> P[Return headers unchanged]
O --> Q[HTTP request sent]
P --> Q
|
|
|
||
|
|
||
| def load_local_server_api_key(host: str, port: object) -> Optional[str]: | ||
| if host not in {"127.0.0.1", "localhost"}: |
There was a problem hiding this comment.
IPv6 localhost (
::1) not covered
The guard only recognises "127.0.0.1" and "localhost" as local addresses, so a client initialised with host="::1" (IPv6 loopback) will never load the API key. On systems where the server binds to ::1 this silently skips auth.
| if host not in {"127.0.0.1", "localhost"}: | |
| if host not in {"127.0.0.1", "localhost", "::1"}: |
| continue | ||
|
|
||
| try: | ||
| with open(config_path) as f: |
There was a problem hiding this comment.
Missing explicit
encoding on open()
Without an explicit encoding, the file is decoded using the platform default (e.g. CP1252 on Windows). TOML files are UTF-8 by spec, so the encoding should be stated explicitly to avoid subtle failures on non-UTF-8 locales.
| with open(config_path) as f: | |
| with open(config_path, encoding="utf-8") as f: |
|
Fixed the black formatting CI failure and addressed Greptile's P2 suggestions:
All tests pass in CI (lint was the only failure). Ready for review. |
|
What if aw-server-rust has api_key configured but aw-server-python is running? |
|
I checked the Python server path before answering this. If has an configured but is actually the process listening on that localhost port, the current client behavior is still OK:\n\n- may send when the host is localhost and the port matches the local config\n- does not implement Bearer auth, and it does not reject unknown headers\n- so the practical outcome is just one extra header on the request, not a broken connection\n\nSo I don’t think this blocks the PR. The only downside is that we may send a redundant header in the mixed-server case.\n\nIf we want to make that distinction explicit later, the clean follow-up would be exposing auth/server implementation info in and keying off that instead of inferring from local config alone. But I’d keep this PR as-is. |
|
I checked the Python server path before answering this. If
So I don't think this blocks the PR. The only downside is that we may send a redundant header in the mixed-server case. If we want to make that distinction explicit later, the clean follow-up would be exposing auth/server implementation info in |
Summary
[auth].api_keyfrom localaw-server-rustconfigs when connecting to127.0.0.1orlocalhostAuthorization: Bearer ...when the configured server port matches the requested portCloses the Python client part of ActivityWatch/activitywatch#1199.
Testing
poetry run pytest -q tests/test_auth.py tests/test_requestqueue.pypoetry run mypy aw_client tests/test_auth.py tests/test_requestqueue.pypoetry run ruff check aw_client/config.py aw_client/client.py tests/test_auth.py