Last page: check the target, and let the server write the cookie #191
Labels
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Grandiras/Ponente#191
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Part of #190. Reopening where you left off (#186,
Services/LastPage.cs,/launch) works — checked on the dev stack — but it has two defects and has never been released: production is on v2.6.0, where/launchis a 404 and the manifest still saysstart_url: "/".1. The target is not checked.
/launchredirects to whatever path the cookie holds. If that is/board/7and board 7 has since been deleted or taken away from this member, every launch opens onboard.missing/board.denieduntil they navigate somewhere else — the one page in the app that is a dead end, as the first thing they see. For a pathShareLinkreads as board-scoped, the endpoint should build aBoardViewerfrom the cookie principal the way the.icsendpoint does, askBoardService.CanViewAsync, and fall back toReturnUrl.Default. A visitor who is not signed in passes through untouched: the page's own authorization asks them to sign in, carrying the destination, as now.2. The cookie is written by script, so on an iPhone the month is a week. WebKit caps the lifetime of anything set through
document.cookieat 7 days, whatevermax-agesays — and every browser on iOS, installed apps included, is WebKit. A cookie the server sets is not capped. So: a smallPOSTendpoint beside/launchthat takes the path, runs it throughLastPage.Remember(still the one rule in both directions) and answers 204 withSet-Cookie—HttpOnly,SameSite=Lax, the same 30 days.rememberPageinprefs-interop.jsbecomes afetchwithkeepalive: trueso a navigation that closes the page does not cancel it. The endpoint should refuse anything butSec-Fetch-Site: same-origin; the cookie is harmless, but nothing off-site has a reason to set it.3. The test does what no member does.
prefs.spec.jsprovesgoto('/launch')follows the cookie. Add: a launch onto a board that was deleted lands somewhere real, and the cookie a navigation leaves behind isHttpOnlywith roughly a month to run.Update Reopening where you left off in
docs/ui.md— the "a cookie, not localStorage" bullet is still right, but who writes it and why changes. Once merged this wants a release, so the feature exists on production before the home page builds on it.Not here:
/itself resuming. That rule only makes sense once/is the home page and stops being the default board's address — it belongs to the home-page step in #190.🤖 On it — the reply will link the full session transcript.
Both defects are fixed and verified, committed on
opencode/issue-191(fe3d09e).1. The target is checked.
/launchinProgram.csnow builds aBoardViewerfrom the cookie principal the way the.icsendpoint does and asksBoardService.CanViewAsyncabout any remembered pathShareLink.TryParsereads as board-scoped (/board/7,/calendar/board/7); a board that is gone or no longer this member's falls back toReturnUrl.Default. A visitor who is not signed in passes through untouched, so the page's own authorization still carries the destination. Verified with curl against a running app: signed-in launch with a cookie for a nonexistent board → 302 to/; live board → kept; anonymous → kept.2. The server writes the cookie. New
POST /launch/remember(LastPage.RememberRoute) takes the path, runs it throughLastPage.Remember— still the one rule in both directions — and answers 204 withSet-Cookie:HttpOnly,SameSite=Lax, 30 days (LastPage.CookieLifetime). It refuses anything butSec-Fetch-Site: same-origin; in practice the antiforgery middleware already 400s a cross-site POST before the endpoint is reached, so the header check is the second gate.rememberPageinprefs-interop.jsis now afetchwithkeepalive: trueand no longer knows the cookie's name, flags or lifetime. Verified: same-origin POST → 204 +Set-Cookie: ponente-last-page=%2Fboard%2F3; max-age=2592000; path=/; samesite=lax; httponly; a door path (/login) → 204 with no cookie.3. Tests.
prefs.spec.jsgained two tests: the cookie a navigation leaves behind isHttpOnlywith roughly a month to run, and a launch onto a board that was deleted lands on the default board rather thanboard.missing(the stale cookie is restored by hand after the deletion goes through/admin/boards, which is itself a remembered page). All 6 tests in the file pass, plus the full unit suite (477 passed, includingDocumentationTests).Also updated: the Reopening where you left off section in
docs/ui.md(who writes the cookie and why, the target check, the test list), and theChangelog.Unreleasedbullet in both locales — amended rather than added to, since the feature has never shipped.On verification setup: no container engine exists in this environment, so instead of the compose stack I ran the app directly (
dotnet run, Development, port 8090) against a local Postgres 15 with the same connection string, and ran Playwright against it. The e2e run's cleanup helper is disabled without podman/docker, so I removed the boards the failed intermediate runs left behind directly in the database; the final passing run cleaned up after itself.One note for after merge: this wants a release (production is on v2.6.0, where
/launchis a 404) — that's the tag flow in the release skill, deliberately not done here.→ Opened PR: #200
⏱ 18m 10s · 82 steps · $0.1988 · tokens: 499.7k in / 22.3k out (+3757.6k cache-read) · Go: 5h 27% · wk 35% · mo 20%
📜 Full session transcript