Let an anonymous reader open the report card's track in the analysis map (#666) - #667
Merged
Conversation
…map (#666) The report card is public and server-rendered, and under its map it offers "Open full track in the analysis map" — the one place a reader gets the per-track tooling (Events, Glides, Climbs, Sinks) the card itself has no room for. That button was drawn for signed-in visitors only, because the analysis viewer redirected everyone else to sign in. So the reader most likely to follow it — the pilot who was sent the link, their club, a meet director on somebody else's laptop — was shown a page that explains the score and then no way to look at the flight. The gate itself dates from March, when the viewer WAS the product and gating it was the sign-up funnel. Neither that nor the personal library it grew into covers the case that exists now. So `?compId=…&taskId=…&pilotId=…` — and only that shape — now opens with no session. `isPublicCompLink()` is a whitelist rather than a blocklist, so a param it has never heard of cannot quietly widen it; the bare page, the personal library, the bundled samples and the share target all still redirect. The deep link moves no byte a curl could not already fetch: its four endpoints are `optionalAuth` and public, and the report card already downloads the very same IGC anonymously to draw its own map. Nothing here relaxes the server — no route moved off `requireAuth`, no endpoint was added, and no worker file is touched. A hidden `test` comp still 404s on all four routes, and the viewer now answers that with "Competition task not found", built before any breadcrumb that could have named the competition. The sample loaders reload the page with `?track=` / `?sampleComp=`, which the gate refuses, so they are marked `data-requires-account` and hidden from an anonymous reader rather than left to bounce them out of the page. Dropping in a file of one's own is unchanged and needs no account; only the account library is out of reach, and it was already gated by `storage.isAvailable()`. Coverage: `isPublicCompLink` unit tests, and a "signed out" block in e2e/report-card.spec.ts — the button is offered and its link loads the pilot's track with all six tabs; twelve other URLs still ask for a sign-in; a deep link into a hidden test comp finds nothing and names nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Preview Deployment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #666.
Preview: https://claude-github-issue-666-74c2.glidecomp.pages.dev
The gap
The report card is public and server-rendered, and under its map it offers "Open full track in the analysis map" — the one place a reader gets the per-track tooling (Events, Glides, Climbs, Sinks) the card itself has no room for. That button was drawn for signed-in visitors only, because the analysis viewer redirected everyone else to
/u/me/. So the reader most likely to follow it — the pilot who was sent the link, their club, a meet director on someone else's laptop — was shown a page that explains the score and then no way to look at the flight.What changed
?compId=…&taskId=…&pilotId=…— and only that shape — now opens with no session. The gate is one function,isPublicCompLink()inweb/frontend/src/analysis/public-deep-link.ts, and it is a whitelist rather than a blocklist, so a param it has never heard of cannot quietly widen it. The bare page, the personal library (storedTrack,storedTask,u), the bundled samples (track,task,sampleComp) and the share target all still redirect.The
user != nullwrapper is gone from the report card, so the button is now static markup — identical on the server and after hydration, rather than appearing once/api/auth/meanswers.Security — each of the issue's points, confirmed
optionalAuthand public, and the report card already downloads the very same IGC anonymously to draw its own map.testcomps stay hidden. All four routes 404 for a non-admin. The viewer now answers a 404 with "Competition task not found", and that branch returns beforeappendCompBreadcrumbs(), so nothing names the comp or the task. Asserted, includingdocument.title.analysis/storage.ts, gated bystorage.isAvailable()(session?.signedIn) on the client andrequireAuthon the server.web/workers/is untouched — no route moved offrequireAuth, no new endpoint.needsOnboardingstill fires for a session that has one; "no session" and "not onboarded" stay different answers.getCurrentUserOnce(), which retries a dropped request or a 5xx before answering, so a blip cannot read as "anonymous".One deviation from the issue, on the owner's call
The issue proposed hiding the file/import tooling from anonymous readers. The owner's direction was the opposite: dropping in an IGC file of your own is fine — the only thing an anonymous visitor can't reach is the account library, which was already gated. So
menu-open-igc, the drop zone, and the XContest/AirScore importers are all left alone (they're in-memory or external-fetch, and mutate nothing).What is hidden is the sample loaders: they reload the page with
?track=/?sampleComp=, which the gate refuses, so an anonymous reader clicking one would be thrown out to sign-in. They're markeddata-requires-accountin the markup and hidden in one sweep, so a new sample loader is covered by the attribute it's written with.Coverage
public-deep-link.test.ts— 7 unit tests over the whitelist.signed outblock ine2e/report-card.spec.ts:data-requires-accountcontrol is hidden);storedTrack/track— all still land on/u/me/;testcomp says not-found and names nothing.Run green:
bun run test(1580),web/frontendvitest (724),test:e2eonreport-card,analysis-map(signed-in, unchanged),lazy-map-in-view,explain-affordance,non-route-links, and the fulltest:e2e:ssr(38, including a clean hydration check on:pilot).🤖 Generated with Claude Code