package clientapi import ( "net/http" "strconv" "github.com/dariuszjaniak/warp/server/internal/httputil" "github.com/dariuszjaniak/warp/server/internal/roomserver" "github.com/dariuszjaniak/warp/server/internal/userapi" ) // aliasResponse is the GET /directory/room/{roomAlias} body. type aliasResponse struct { RoomID string `json:"room_id"` Servers []string `json:"servers"` } // handleGetAlias implements GET /_matrix/client/v3/directory/room/{roomAlias}. // // The endpoint needs no token: a client that has been handed a link has to // be able to turn it into a room ID before it can decide whether to log in, // and the spec makes it unauthenticated for that reason. func (c *ClientAPI) handleGetAlias(w http.ResponseWriter, r *http.Request) error { alias, err := aliasFromPath(r) if err != nil { return err } resolved, err := c.rooms.ResolveAlias(r.Context(), alias) if err != nil { return roomError(err) } httputil.WriteJSON(w, http.StatusOK, aliasResponse{ RoomID: resolved.RoomID, Servers: resolved.Servers, }) return nil } // putAliasRequest is the PUT /directory/room/{roomAlias} body. type putAliasRequest struct { RoomID string `json:"room_id"` } // handlePutAlias implements PUT /_matrix/client/v3/directory/room/{roomAlias}. func (c *ClientAPI) handlePutAlias(w http.ResponseWriter, r *http.Request, auth userapi.Authenticated) error { alias, err := aliasFromPath(r) if err != nil { return err } var req putAliasRequest if err := httputil.DecodeJSON(w, r, &req); err != nil { return err } if req.RoomID == "" { return httputil.MissingParam("A room ID is required") } if err := c.rooms.CreateAlias(r.Context(), alias, req.RoomID, auth.UserID); err != nil { return roomError(err) } httputil.WriteJSON(w, http.StatusOK, struct{}{}) return nil } // handleDeleteAlias implements // DELETE /_matrix/client/v3/directory/room/{roomAlias}. func (c *ClientAPI) handleDeleteAlias(w http.ResponseWriter, r *http.Request, auth userapi.Authenticated) error { alias, err := aliasFromPath(r) if err != nil { return err } if err := c.rooms.DeleteAlias(r.Context(), alias, auth.UserID, auth.Admin); err != nil { return roomError(err) } httputil.WriteJSON(w, http.StatusOK, struct{}{}) return nil } // roomAliasesResponse is the GET /rooms/{roomID}/aliases body. type roomAliasesResponse struct { Aliases []string `json:"aliases"` } // handleGetRoomAliases implements // GET /_matrix/client/v3/rooms/{roomID}/aliases. func (c *ClientAPI) handleGetRoomAliases(w http.ResponseWriter, r *http.Request, auth userapi.Authenticated) error { roomID, err := pathRoomID(r) if err != nil { return err } aliases, err := c.rooms.RoomAliases(r.Context(), roomID, auth.UserID) if err != nil { return roomError(err) } httputil.WriteJSON(w, http.StatusOK, roomAliasesResponse{Aliases: aliases}) return nil } // visibilityResponse is the GET /directory/list/room/{roomID} body. type visibilityResponse struct { Visibility string `json:"visibility"` } // handleGetRoomVisibility implements // GET /_matrix/client/v3/directory/list/room/{roomID}. // // Whether a room is in the public directory is itself public: the directory // can be read without a token, so hiding the flag behind one would protect // nothing. func (c *ClientAPI) handleGetRoomVisibility(w http.ResponseWriter, r *http.Request) error { roomID, err := pathRoomID(r) if err != nil { return err } visibility, err := c.rooms.RoomVisibility(r.Context(), roomID) if err != nil { return roomError(err) } httputil.WriteJSON(w, http.StatusOK, visibilityResponse{Visibility: visibility}) return nil } // handlePutRoomVisibility implements // PUT /_matrix/client/v3/directory/list/room/{roomID}. func (c *ClientAPI) handlePutRoomVisibility(w http.ResponseWriter, r *http.Request, auth userapi.Authenticated) error { roomID, err := pathRoomID(r) if err != nil { return err } var req visibilityResponse if err := httputil.DecodeJSON(w, r, &req); err != nil { return err } if err := c.rooms.SetRoomVisibility(r.Context(), roomID, auth.UserID, req.Visibility, auth.Admin); err != nil { return roomError(err) } httputil.WriteJSON(w, http.StatusOK, struct{}{}) return nil } // publicRoomsFilter is the filter in a POST /publicRooms body. type publicRoomsFilter struct { GenericSearchTerm string `json:"generic_search_term"` RoomTypes []string `json:"room_types"` } // publicRoomsRequest is the POST /publicRooms body. type publicRoomsRequest struct { Limit int `json:"limit"` Since string `json:"since"` Filter publicRoomsFilter `json:"filter"` // IncludeAllNetworks and ThirdPartyInstanceID select an application // service's own directory, which arrives with application service // support in Phase 9. IncludeAllNetworks bool `json:"include_all_networks"` ThirdPartyInstanceID string `json:"third_party_instance_id"` } // handleGetPublicRooms implements GET /_matrix/client/v3/publicRooms. func (c *ClientAPI) handleGetPublicRooms(w http.ResponseWriter, r *http.Request) error { query := r.URL.Query() limit := 0 if raw := query.Get("limit"); raw != "" { parsed, err := strconv.Atoi(raw) if err != nil || parsed < 0 { return httputil.InvalidParam("limit must be a non-negative integer") } limit = parsed } return c.publicRooms(w, r, roomserver.PublicRoomsRequest{ Limit: limit, Since: query.Get("since"), Server: query.Get("server"), }) } // handlePostPublicRooms implements POST /_matrix/client/v3/publicRooms, // which is the same listing with a filter. func (c *ClientAPI) handlePostPublicRooms(w http.ResponseWriter, r *http.Request, _ userapi.Authenticated) error { var req publicRoomsRequest if _, err := httputil.DecodeOptionalJSON(w, r, &req); err != nil { return err } if req.ThirdPartyInstanceID != "" { return httputil.Unknown(http.StatusBadRequest, "Application service room directories are not supported by this server") } if req.Limit < 0 { return httputil.InvalidParam("limit must be a non-negative integer") } return c.publicRooms(w, r, roomserver.PublicRoomsRequest{ Limit: req.Limit, Since: req.Since, Search: req.Filter.GenericSearchTerm, Server: r.URL.Query().Get("server"), }) } // publicRooms is the body of both directory endpoints. func (c *ClientAPI) publicRooms(w http.ResponseWriter, r *http.Request, req roomserver.PublicRoomsRequest) error { resp, err := c.rooms.PublicRooms(r.Context(), req) if err != nil { return roomError(err) } httputil.WriteJSON(w, http.StatusOK, resp) return nil }