Home mode: a signed-out page request carries return_to
The local plugin's RequireSession redirects a signed-out page request to /signin with no return_to, so after Home sign-in Calls landed on the root instead of the page the person asked for. In Home mode, requireParticipant now redirects GET/HEAD to /signin?return_to=<request URI> first; connect's Signin keeps a safe path through the exchange and lands on it. Guests hold a session and are unaffected; standalone is unchanged. TestHomeSigninReturnsToTheExactPage failed first (/settings went to "/signin") and passes now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 files changed,
+37
−2
internal/calls/guest.go+20 −2internal/calls/home_consumer_test.go+17 −0
CI passed — run details
CI log
=== vet === go vet ./... go: downloading amadan.net/oficina/home/connect v0.0.0-20260923190447-01166fcfc0a9 === fmt === === test === go test ./... ok amadan.net/oficina/calls/callscore 6.841s ? amadan.net/oficina/calls/cmd/calls [no test files] ok amadan.net/oficina/calls/internal/calls 1.668s ok amadan.net/oficina/calls/internal/calls/apns 0.007s ok amadan.net/oficina/calls/internal/calls/hub 0.394s ? amadan.net/oficina/calls/internal/calls/icons [no test files] ok amadan.net/oficina/calls/internal/calls/relay 0.004s ok amadan.net/oficina/calls/internal/calls/seal 0.003s ok amadan.net/oficina/calls/internal/callstest 8.924s ? amadan.net/oficina/calls/protocol [no test files] ? amadan.net/oficina/calls/scripts/writeestilo [no test files] === migration-check === go run amadan.net/rastrillo/rastrillo/cmd/rastrillo migration check rastrillo migration check: models and migrations agree === race === CGO_ENABLED=1 go test -race ./internal/... ok amadan.net/oficina/calls/internal/calls 11.029s ok amadan.net/oficina/calls/internal/calls/apns 1.026s ok amadan.net/oficina/calls/internal/calls/hub 3.561s ? amadan.net/oficina/calls/internal/calls/icons [no test files] ok amadan.net/oficina/calls/internal/calls/relay 1.019s ok amadan.net/oficina/calls/internal/calls/seal 1.012s ok amadan.net/oficina/calls/internal/callstest 27.008s
diff --git a/internal/calls/guest.go b/internal/calls/guest.go| index 3fc42b6..0fd355b 100644 |
| --- a/internal/calls/guest.go |
| +++ b/internal/calls/guest.go |
| @@ -2,6 +2,7 @@ package calls |
| import ( |
| "net/http" |
| + "net/url" |
| "strconv" |
| "strings" |
| "unicode/utf8" |
| @@ -13,7 +14,7 @@ import ( |
| // Guest sessions use the ordinary revocable session store, with a distinct |
| // identity method. They can never resolve to a verified address or team member. |
| func (a *app) requireParticipant(next http.Handler) http.Handler { |
| - return a.auth.RequireSession(a.requirePerson(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| + return a.homeReturnTo(a.auth.RequireSession(a.requirePerson(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| p, _ := currentPerson(r) |
| path := r.URL.Path |
| if p.Anonymous && !strings.HasPrefix(path, "/m/") && path != "/native/authorize" && path != "/native/complete" { |
| @@ -21,7 +22,24 @@ func (a *app) requireParticipant(next http.Handler) http.Handler { |
| return |
| } |
| next.ServeHTTP(w, r) |
| - }))) |
| + })))) |
| +} |
| + |
| +// homeReturnTo: in Home mode a signed-out page request carries the page to |
| +// sign-in, so Home sign-in comes back to it rather than to the root; |
| +// connect's Signin keeps a safe return_to through the exchange. The local |
| +// plugin's own refusal drops the path. Same cookie, same table: wireHome |
| +// checked the names agree. Standalone is unchanged. |
| +func (a *app) homeReturnTo(next http.Handler) http.Handler { |
| + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| + if a.home != nil && (r.Method == http.MethodGet || r.Method == http.MethodHead) { |
| + if _, ok := a.home.sessions.From(r); !ok { |
| + http.Redirect(w, r, signinPath+"?return_to="+url.QueryEscape(r.URL.RequestURI()), http.StatusSeeOther) |
| + return |
| + } |
| + } |
| + next.ServeHTTP(w, r) |
| + }) |
| } |
| func (a *app) guestPage(w http.ResponseWriter, r *http.Request, m Meeting, token, name, message string) { |
| nativeHeaders(w) |
diff --git a/internal/calls/home_consumer_test.go b/internal/calls/home_consumer_test.go| index 44a09ef..02575a9 100644 |
| --- a/internal/calls/home_consumer_test.go |
| +++ b/internal/calls/home_consumer_test.go |
| @@ -99,3 +99,20 @@ func homeSignout(t *testing.T, w *httptest.ResponseRecorder) { |
| t.Fatalf("POST /signout: status %d Location %q, want 307 to %s/signout", w.Code, w.Header().Get("Location"), homeTestIssuer) |
| } |
| } |
| + |
| +// A signed-out visit to a page in Home mode goes to sign-in carrying the |
| +// page, and Home sign-in comes back to that exact page, not the root. |
| +func TestHomeSigninReturnsToTheExactPage(t *testing.T) { |
| + f := newHomeConsumerFixture(t, "") |
| + f.link(t) |
| + homeSignout(t, f.do("POST", "/signout", "")) |
| + |
| + signin := homeRedirect(t, f.do("GET", "/settings", "")) |
| + if signin != "/signin?return_to=%2Fsettings" { |
| + t.Fatalf("signed-out /settings went to %q, want /signin?return_to=%%2Fsettings", signin) |
| + } |
| + start := homeRedirect(t, f.do("GET", signin, "")) |
| + if got := homeRedirect(t, f.exchange(t, start, "opaque-home-alice")); got != "/settings" { |
| + t.Fatalf("Home sign-in landed on %q, want /settings", got) |
| + } |
| +} |