oficina / meet Public

Pin home/connect to Home main (7a0f19c): in-app sign-out chains to id.*

connect's Signout now hands the POST on to {Issuer}/signout as a 307
(shared-chrome spec §9.5), and Signin skips the exchange for a live
signed-in session. The Home router tests asserted the old 303; they now
assert the chain itself.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev pushed by paul@keymail.dev dfe5fe0d93683c9929ad0ada610022dc1f42b40c parent 84ed5dc
5 files changed, +18 −7
  • go.mod +1 −1
  • go.sum +2 −0
  • internal/calls/home_release_test.go +1 −1
  • internal/calls/home_router_test.go +13 −4
  • internal/calls/native_auth_test.go +1 −1
diff --git a/go.mod b/go.mod
index 7fbf2ec..3ae112d 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ go 1.26.0
require (
amadan.net/oficina/estilo.git v0.0.0-20260929231226-cce11010ece0
- amadan.net/oficina/home/connect v0.0.0-20260910000424-5b8d8410775f
+ amadan.net/oficina/home/connect v0.0.0-20260930200947-7a0f19c3a8a7
amadan.net/oficina/home/handoff v0.0.0-20260908102137-481d2d57f489
amadan.net/rastrillo/rastrillo v0.26.1-0.20260908075707-29e4491631bd
github.com/go-chi/chi/v5 v5.3.2
diff --git a/go.sum b/go.sum
index c6810c4..5b1e238 100644
--- a/go.sum
+++ b/go.sum
@@ -2,6 +2,8 @@ amadan.net/oficina/estilo.git v0.0.0-20260929231226-cce11010ece0 h1:Qww+xGIYJaj+
amadan.net/oficina/estilo.git v0.0.0-20260929231226-cce11010ece0/go.mod h1:4u0jNBtIdMLOsJyrScfqoEuYDJF7BBM3qM9YWbzt6aw=
amadan.net/oficina/home/connect v0.0.0-20260910000424-5b8d8410775f h1:lkw3tjzHfZxe74pNfo+tGDz4mkZLg+D6R+FTkmsym6M=
amadan.net/oficina/home/connect v0.0.0-20260910000424-5b8d8410775f/go.mod h1:YroavXe43Wietec+po8mtSk4a9Bir2h31Q3iAf4MWxI=
+amadan.net/oficina/home/connect v0.0.0-20260930200947-7a0f19c3a8a7 h1:FVzfiViGRUtl/enMtf34LL2ECLCLJx5V2JJmsOxuOEI=
+amadan.net/oficina/home/connect v0.0.0-20260930200947-7a0f19c3a8a7/go.mod h1:YroavXe43Wietec+po8mtSk4a9Bir2h31Q3iAf4MWxI=
amadan.net/oficina/home/handoff v0.0.0-20260908102137-481d2d57f489 h1:RPKtyry/71i06o8L+gjAaUyexbmG56kO1YPs601URAM=
amadan.net/oficina/home/handoff v0.0.0-20260908102137-481d2d57f489/go.mod h1:YISpzFJ+iMgrM0rUL032OSzsAQbP4/nozvXLCCOcn1k=
amadan.net/rastrillo/rastrillo v0.26.1-0.20260908075707-29e4491631bd h1:jlFSt95M1ontOg+VePU+kIJpChtWqDQAbnmlxqCnbgc=
diff --git a/internal/calls/home_release_test.go b/internal/calls/home_release_test.go
index dd9632e..2ec6357 100644
--- a/internal/calls/home_release_test.go
+++ b/internal/calls/home_release_test.go
@@ -136,7 +136,7 @@ func TestHomeConnectionKeepsCurrentCallAndAgentRoutes(t *testing.T) {
if w.Code != 403 {
t.Fatalf("browser mutation lost CSRF protection: %d", w.Code)
}
- homeRedirect(t, f.do("POST", "/signout", ""))
+ homeSignout(t, f.do("POST", "/signout", ""))
if w := f.do("GET", target, ""); w.Code != 303 {
t.Fatalf("room survives signout: %d", w.Code)
}
diff --git a/internal/calls/home_router_test.go b/internal/calls/home_router_test.go
index 758ebc0..f306ac1 100644
--- a/internal/calls/home_router_test.go
+++ b/internal/calls/home_router_test.go
@@ -95,6 +95,15 @@ func homeRedirect(t *testing.T, w *httptest.ResponseRecorder) string {
}
return w.Header().Get("Location")
}
+// homeSignout is POST /signout with Home configured: the app session
+// ends and the same POST is handed on, as a 307, to id.*'s /signout
+// (spec shared-chrome §9.5), which is what ends the id.* session too.
+func homeSignout(t *testing.T, w *httptest.ResponseRecorder) {
+ t.Helper()
+ if w.Code != 307 || w.Header().Get("Location") != homeTestIssuer+"/signout" {
+ t.Fatalf("sign-out did not chain to id.*: %d %q", w.Code, w.Header().Get("Location"))
+ }
+}
func (f *homeRouterFixture) local(t *testing.T) {
t.Helper()
homeRedirect(t, f.do("POST", "/signin", "address=alice%40example.test&force=1"))
@@ -144,7 +153,7 @@ func (f *homeRouterFixture) link(t *testing.T) {
t.Fatal("confirmation", next)
}
homeRedirect(t, f.do("POST", "/account/home/confirm", ""))
- homeRedirect(t, f.do("POST", "/signout", ""))
+ homeSignout(t, f.do("POST", "/signout", ""))
start = homeRedirect(t, f.do("GET", "/signin", ""))
homeRedirect(t, f.exchange(t, start, "opaque-home-alice"))
}
@@ -159,7 +168,7 @@ func TestHomeLinksExistingMemberAndKeepsMeetings(t *testing.T) {
if err := f.app.db.Writer().QueryRow("SELECT created_by FROM meetings LIMIT 1").Scan(&before); err != nil {
t.Fatal(err)
}
- homeRedirect(t, f.do("POST", "/signout", ""))
+ homeSignout(t, f.do("POST", "/signout", ""))
f.link(t)
w := f.do("GET", "/", "")
if w.Code != 200 || !strings.Contains(w.Body.String(), "Existing private meeting") {
@@ -211,7 +220,7 @@ func TestHomeEnabledPreservesInvitedGuestFlow(t *testing.T) {
if token == nil || sqid == nil {
t.Fatal("missing meeting links")
}
- homeRedirect(t, f.do("POST", "/signout", ""))
+ homeSignout(t, f.do("POST", "/signout", ""))
path := homeRedirect(t, f.do("GET", "/j/"+token[1], ""))
if path != "/signin" {
t.Fatal(path)
@@ -274,7 +283,7 @@ func TestHomePublicJoinResolvesExistingMember(t *testing.T) {
func TestHomeRemovedMemberSigninIsRefusedWithoutServiceError(t *testing.T) {
f := newHomeRouterFixture(t)
f.link(t)
- homeRedirect(t, f.do("POST", "/signout", ""))
+ homeSignout(t, f.do("POST", "/signout", ""))
f.app.cfg.allow = allowlist{}
start := homeRedirect(t, f.do("GET", "/signin", ""))
if w := f.exchange(t, start, "opaque-home-alice"); w.Code != 403 {
diff --git a/internal/calls/native_auth_test.go b/internal/calls/native_auth_test.go
index 211204c..f8d61ed 100644
--- a/internal/calls/native_auth_test.go
+++ b/internal/calls/native_auth_test.go
@@ -366,7 +366,7 @@ func TestNativeSessionReadsWhatTheAppReads(t *testing.T) {
if dest := homeRedirect(t, f.do("GET", "/j/"+token, "")); dest != "/m/"+m.Sqid+"/join" {
t.Fatal("invitation did not lead to its meeting", dest)
}
- homeRedirect(t, f.do("POST", "/signout", ""))
+ homeSignout(t, f.do("POST", "/signout", ""))
// f.do dropped the cleared cookie; the app still holds its copy.
f.cookies = map[string]*http.Cookie{name: {Name: name, Value: value}}
if dest := homeRedirect(t, f.do("GET", "/settings", "")); !strings.HasPrefix(dest, signinPath) {