From b1d1f64c16dd1326fc6f866d1db0033208b5f67b Mon Sep 17 00:00:00 2001 From: agent Date: Wed, 3 Jun 2026 21:33:42 +0200 Subject: [PATCH] fix: surface clear error when joining encrypted room without invitation - membership server returns 403 + human-readable message on missing sealed key (was leaking 'sql: no rows in result set') - client doJSON unwraps the server's {"error"} field instead of pasting the raw HTTP envelope --- pkg/client/client.go | 8 ++++++++ pkg/membership/server.go | 9 ++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/pkg/client/client.go b/pkg/client/client.go index 467d1b7..aa93308 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -126,6 +126,14 @@ func (c *Client) doJSON(method, path string, body, out any) error { defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) if resp.StatusCode >= 300 { + // Surface the server's structured {"error": "..."} message when present, + // instead of leaking the raw HTTP envelope (method, path, status, JSON body). + var er struct { + Error string `json:"error"` + } + if json.Unmarshal(respBody, &er) == nil && er.Error != "" { + return fmt.Errorf("%s (HTTP %d)", er.Error, resp.StatusCode) + } return fmt.Errorf("client: %s %s -> %d: %s", method, path, resp.StatusCode, string(respBody)) } if out != nil { diff --git a/pkg/membership/server.go b/pkg/membership/server.go index 7bff786..330c151 100644 --- a/pkg/membership/server.go +++ b/pkg/membership/server.go @@ -1,7 +1,9 @@ package membership import ( + "database/sql" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -235,7 +237,12 @@ func (s *Server) handleGetKey(w http.ResponseWriter, r *http.Request) { } ep, sealed, err := s.store.GetSealedKey(roomID, endpoint, epoch) if err != nil { - writeErr(w, http.StatusNotFound, err.Error()) + if errors.Is(err, sql.ErrNoRows) { + writeErr(w, http.StatusForbidden, + "not invited to this encrypted room: no key has been sealed for your identity. Ask the room owner to invite you before joining.") + return + } + writeErr(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, keyResp{Epoch: ep, SealedKey: sealed})