Repository navigation
Keep browser logins for 30 days across Core restarts - #1727
Conversation
The car ops were cmd frames only, so the box page, which speaks HTTP,
could list cars but not keep them or answer which car is on a charger.
Each op now has a route that hands the same arguments to the same
runtime and answers with the same code and reason:
- POST /api/cars is car.add, PATCH /api/cars/{id} car.update and
DELETE /api/cars/{id} car.remove. Configure, no step-up, and only a
caller with ftw.cars.write.
- POST /api/loadpoints/{id}/car is session.car.set, the tap, with
{session, car_id}. Actuate, Via the op, so a phone is told to send it.
appproto.CarRefusal is the refusal table both use.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The overview gets a card per car, plugged in or not: its level with where it came from and how old it is, an estimate marked with ≈, its charge limit, its goal and the charger it is on, with how the box knows. The level ages between reads, and a list that stopped updating says since when. While a charger asks which car it is, its notice offers one button per car and None of these, and the cars it may be say "On garage?" rather than "Not plugged in". A car the box named itself can be corrected behind "Not this car?". The buttons are rebuilt only when what they show changes, so a poll never takes one from under a tap. Settings gets a Cars tab: each car with its VIN, level sources and charger tags; rename, battery size, add and remove through the car routes. Adding a VIN a car already has is stopped on the page: the box would take that as the app adopting the car it made, and quietly rename it. The EV modal stays as it was; a car on a charger opens it with "Check charging". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…home From the review of #1694: - GET /api/cars gives VINs, charge tags and sources only to a request that has proved itself as a protected one must. The LAN listener names every caller an owner while the house password is off, so a box published under a public name without FTW_API_TOKEN handed them to anyone. Others get the viewer shape and redacted: true. - POST/PATCH/DELETE /api/cars are Local: a phone keeps sending the ops as cmd frames, with their id and expiry. - The routes read their bodies with the ops' own argument checks (appproto.CarAddArgs, CarUpdateArgs): a negative size is refused, and a null identifiers clears the list as car.update does. - The card dates the chargers' read apart from the cars': an old read, or a charger gone quiet, says "Charger status unknown" rather than the last place. The charge limit shows only beside the car's own reading. - A tap gives up after 30 s, and a 401 says the house password is needed. - The Cars tab says hidden, not "Not set", when the box left a field out. - docs/cars.md names the routes, the page and who sees the VIN. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f1bf6e1a28
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| caller, ok := apiauth.FromRequest(r) | ||
| full := ok && caller.Scopes.Has(protocol.ScopeCarsRead) && | ||
| (caller.Kind != apiauth.KindLAN || remoteProved(r, s.deps.MutationPolicy)) |
There was a problem hiding this comment.
Honor the API token when LAN auth is enabled
When api.lan_auth is enabled and a client connects directly from a non-local address through a public hostname, even a valid FTW_API_TOKEN receives the redacted list. resolveLANSecret skips non-local clients, so decideLANCaller assigns the viewer scopes; this scope check then fails before remoteProved can recognize the valid token. This contradicts the documented public-token flow and prevents authenticated remote owners from seeing VINs, sources, and charge tags.
Useful? React with 👍 / 👎.
Each Core update or restart currently drops the browser’s house-password login because the server keeps its sessions only in RAM. Keep the login for 30 days and restore valid sessions from
state.dbwhen Core starts.Store a SHA-256 digest of the opaque token, its expiry and a binding to the password version. Commit the session before sending its cookie. Logout revokes the durable row; a password change clears sessions, and the password binding also rejects old rows after an interrupted cleanup. Expired rows cannot restore and new logins prune expired entries. The cookie keeps HttpOnly, SameSite=Strict and its existing LAN HTTP behavior. A review of the stacked car-page base also found that enabling the house password gave a correctly authenticated public API-token caller viewer scopes. Keep that explicit token caller an owner; invalid tokens still get viewer scopes. Its regression fails before and passes after the fix.
Depends on #1694. This branch preserves its exact
db790a0acar-page work because it owns the API constructor file. Current master, including #1723, is merged into this branch. Merge #1694 first; its commits appear in this diff until then. This PR adds the session change in eight files and does not install anything on the box.Validation:
make verifypass locally. Full verification also passes after integrating current master.Fredrik explicitly requested durable browser login with a default of about a month. No beta is published by this PR; the next box beta is being coordinated with the MCP work.
A stalled unauthenticated login body no longer holds the shared Settings write lock. A regression runs an owner settings write alongside that body; it fails before the fix and passes after. Login admission and password/session persistence remain serialized. Full
make verifyand targeted auth/state race checks pass.