Commit Graph
3 Commits
Author SHA1 Message Date
kosmos d31dfeae27 fix(forge): a gitea 401 retries once as basic auth
A self-hosted Gitea answers 401 to `Authorization: token <password>` and
200 to the same value as `user:password`, and the account store's
`secret_kind` cannot tell the two apart — it is a label the human typed
at `identity_save`, so a login password saved as a token is
indistinguishable from a real PAT until the forge answers.

`GiteaForge` had one auth path: the token header, on every call. A
watch whose credential resolved to a password therefore 401'd on every
poll, forever, while the same secret worked for `git push` over https
(the credential helper sends it as a password). The watch looked quiet
instead of broken.

`GiteaForge::with_login` names the login that owns the secret, and
`send` tries the token header first, then — on a 401 only — the same
value as Basic auth. The retry is cloned before the token header lands,
so the fallback request carries one `Authorization` header and not two.
No login (a pasted watch token names none) or an unclonable body leaves
the 401 to surface exactly as it did before.

The login reaches the client in the next commit; this one is the adapter
half, tested against a mock that refuses the token header and accepts
the Basic one.
2026-11-08 17:54:15 +01:00
kosmos 6ae21568d0 fix(android): gate the wifi read on ACCESS_WIFI_STATE, and declare it
Reported from the phone's diag log:

  status: crashed: SecurityException: WifiService: Neither user 10519 nor
  current process has android.permission.ACCESS_WIFI_STATE.
  at android.os.Parcel.createExceptionOrNull

`PhoneSensors.wifiSsid` read `WifiManager.connectionInfo` behind nothing
but the location grant, but WifiService gates every WifiManager call on
ACCESS_WIFI_STATE — and it enforces that by throwing SecurityException,
not by answering null. The permission was never declared, so the read
always threw.

The cost is larger than the missing SSID: the throw travels out of
`readSnapshot` into the worker's catch, which logs "status: crashed" and
returns failure — so one undeclared permission took the battery, network,
DND and location topics down with it, and the fleet's rows went stale
while the app looked healthy.

Two halves, both needed:

- the manifest declares ACCESS_WIFI_STATE (a normal permission: declaring
  it is the whole grant, no runtime prompt). An install that predates the
  line keeps the grant set it was installed with, so this alone does not
  stop the crash on an existing phone;
- `wifiSsid` returns null unless the permission is actually held, and
  wraps the read in `runCatching`, so the status report degrades to "no
  wifi name" instead of dying whole. The check is a `hasWifiState` beside
  the existing `hasLocation`, same idiom.
2026-11-08 17:54:15 +01:00
kosmos 9e132fdf71 fix(claw): read a blank since as unset instead of failing the request
The phone's notification poll sent `since` unconditionally, and before
the first ack there is no cursor at all, so it went out as `?since=`.
Axum's `Query` hands a present-but-empty parameter through as an empty
string, and `Option<i64>` only forgives a *missing* key, so the whole
poll answered `400 Failed to deserialize query string: since: cannot
parse integer from empty string` and the phone showed an error instead
of an empty queue.

Both ends: the client omits `since` unless it has a positive id to send,
and a `blank_as_none` deserializer makes a blank value mean "not set"
for `NotificationQuery` and `EventStreamQuery` alike — an empty `since`
reads as "from the start" however it got there.
2026-11-06 20:39:53 +00:00