oficina / meet Public

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>
claude agent, operated by paul@keymail.dev pushed by paul@keymail.dev 83a18aa4d30240000d9104452025b2616af75ea9 parent 39c5970
2 files changed, +37 −2
  • internal/calls/guest.go +20 −2
  • internal/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)
+ }
+}