From 8fbf90d87d162ae90719b7b1a34fc232b62173d0 Mon Sep 17 00:00:00 2001 From: Fredrik Ahlgren Date: Sat, 17 Oct 2026 07:40:31 +0200 Subject: [PATCH] fix(oauth): the consent page names the box's own page, and its lookup refusals can be read there An owner with no FTW app approves and removes an assistant on the box's own page, under Settings, then Access. The consent page said removal happens "in the FTW app" only, and named an Access page without saying where. The box page asks the code lookup from the owner's browser. Only a found code carried Access-Control-Allow-Origin, so the browser hid a wrong code and a rate limit behind the same network error. Every answer of the lookup now carries it; a refusal says nothing a code does not. Paired with srcfl/ftw's Access tab. Co-Authored-By: Claude Opus 5.5 --- internal/oauth/authorize.go | 8 +++++--- internal/oauth/oauth_test.go | 12 +++++++++++- internal/oauth/page.go | 4 ++-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/internal/oauth/authorize.go b/internal/oauth/authorize.go index b780dbe..2f06948 100644 --- a/internal/oauth/authorize.go +++ b/internal/oauth/authorize.go @@ -303,6 +303,11 @@ func (s *Server) serveRequest(w http.ResponseWriter, r *http.Request) { // about 39 bits, and finding one shows only an offer the owner must still // approve. func (s *Server) serveRequestByCode(w http.ResponseWriter, r *http.Request) { + // The box's page fetches it from the owner's browser, on whatever + // address the box has at home. Every answer is readable there: a + // browser hides a refusal it may not read, and a wrong code would look + // the same as no network. A refusal says nothing a code does not. + w.Header().Set("Access-Control-Allow-Origin", "*") if s.limited(&s.lookupLimit, s.lookupByIP, r) { w.Header().Set("Retry-After", "1") http.Error(w, "too many lookups", http.StatusTooManyRequests) @@ -318,9 +323,6 @@ func (s *Server) serveRequestByCode(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) return } - // The box's page fetches it from the owner's browser, on whatever - // address the box has at home. - w.Header().Set("Access-Control-Allow-Origin", "*") writeJSON(w, http.StatusOK, s.view(c), false) } diff --git a/internal/oauth/oauth_test.go b/internal/oauth/oauth_test.go index fb14ce5..afabddb 100644 --- a/internal/oauth/oauth_test.go +++ b/internal/oauth/oauth_test.go @@ -896,11 +896,19 @@ func TestTheBoxPageFindsARequestByItsCode(t *testing.T) { if resp.StatusCode != http.StatusNotFound { t.Fatalf("an unknown code: %d", resp.StatusCode) } + // The box's page reads a refusal too, so it can tell a wrong code from + // no network. + if got := resp.Header.Get("Access-Control-Allow-Origin"); got != "*" { + t.Fatalf("an unknown code's answer is not readable from the box's page: %q", got) + } // Lookups are rate limited for everyone together. limited := false for range 60 { resp, _ := http.Get(g.srv.URL + "/oauth/requests?code=ZZZZZZ") if resp.StatusCode == http.StatusTooManyRequests { + if resp.Header.Get("Access-Control-Allow-Origin") != "*" { + t.Fatal("a limited lookup's answer is not readable from the box's page") + } limited = true break } @@ -920,7 +928,9 @@ func TestTheConsentPage(t *testing.T) { body, _ := io.ReadAll(resp.Body) page := string(body) for _, want := range []string{"Let Claude into your home?", "From example.com. FTW Cloud does not know this client", "127.0.0.1:8123", - "a program on this computer", "

After you approve, this window goes back to {{.RedirectHost}}.{{if .Local}} That is a program on this computer. Approve only if you started it.{{end}}

It asks to

    {{range .Scopes}}
  • {{.}}
  • {{end}}
-

{{if .Control}}Control lasts minutes at a time, within limits your box sets. {{end}}It can change nothing lasting by itself. You can remove it at any time in the FTW app.

+

{{if .Control}}Control lasts minutes at a time, within limits your box sets. {{end}}It can change nothing lasting by itself. You can remove it at any time, in the FTW app or on your box's own page.

{{.QR}}

Approve in the FTW app: scan the code with your phone, or

Open the FTW app

-

Or, on your box's own page, open Access and type

+

No app? On your box's own page, open Settings, then Access, and type

{{.Code}}