diff --git a/STATUS.md b/STATUS.md index 3bf1335..35b6202 100644 --- a/STATUS.md +++ b/STATUS.md @@ -89,9 +89,9 @@ Implemented from [Client-Server API](https://matrix.org/docs/spec/client_server/ ### [10.2 Room aliases](https://matrix.org/docs/spec/client_server/latest#room-aliases) -- [ ] [10.2.1 PUT /_matrix/client/r0/directory/room/{roomAlias}](https://matrix.org/docs/spec/client_server/latest#put-matrix-client-r0-directory-room-roomalias) -- [ ] [10.2.2 GET /_matrix/client/r0/directory/room/{roomAlias}](https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-directory-room-roomalias) -- [ ] [10.2.3 DELETE /_matrix/client/r0/directory/room/{roomAlias}](https://matrix.org/docs/spec/client_server/latest#delete-matrix-client-r0-directory-room-roomalias) +- [x] [10.2.1 PUT /_matrix/client/r0/directory/room/{roomAlias}](https://matrix.org/docs/spec/client_server/latest#put-matrix-client-r0-directory-room-roomalias) +- [x] [10.2.2 GET /_matrix/client/r0/directory/room/{roomAlias}](https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-directory-room-roomalias) +- [x] [10.2.3 DELETE /_matrix/client/r0/directory/room/{roomAlias}](https://matrix.org/docs/spec/client_server/latest#delete-matrix-client-r0-directory-room-roomalias) ### [10.4 Room membership](https://matrix.org/docs/spec/client_server/latest#room-membership) diff --git a/internal/backend.go b/internal/backend.go index 3861e7f..e4e28a9 100644 --- a/internal/backend.go +++ b/internal/backend.go @@ -18,6 +18,7 @@ type Backend interface { Sync(token string, request sync.SyncRequest) (response *sync.SyncReply, err models.ApiError) PublicRooms(filter string) []Room ValidateUsernameFunc() func(string) error + GetRoomByAlias(string) Room } type Room interface { @@ -53,4 +54,6 @@ type User interface { Invite(Room, User) models.ApiError AddFilter(filterID string, filter common.Filter) GetFilterByID(filterID string) *common.Filter + AddRoomAlias(Room, string) models.ApiError + DeleteRoomAlias(string) models.ApiError } diff --git a/internal/backends/memory/backend.go b/internal/backends/memory/backend.go index fdc0600..373f5f6 100644 --- a/internal/backends/memory/backend.go +++ b/internal/backends/memory/backend.go @@ -17,6 +17,7 @@ import ( type Backend struct { data map[string]internal.User rooms map[string]internal.Room + roomAliases map[string]internal.Room hostname string validateUsernameFunc func(string) error // TODO: create ability to redefine validation func mutex sync.RWMutex @@ -31,6 +32,7 @@ func NewBackend(hostname string) *Backend { hostname: hostname, validateUsernameFunc: defaultValidationUsernameFunc, rooms: make(map[string]internal.Room), + roomAliases: make(map[string]internal.Room), data: make(map[string]internal.User)} } @@ -148,6 +150,20 @@ func (backend *Backend) PublicRooms(filter string) []internal.Room { return rooms } +func (backend *Backend) GetRoomByAlias(alias string) internal.Room { + backend.mutex.RLock() + defer backend.mutex.RUnlock() + + alias = strings.TrimPrefix(alias, "#") // TODO: create strip alias func + alias = strings.TrimSuffix(alias, ":"+backend.hostname) + + if room, exists := backend.roomAliases[alias]; exists { + return room + } + + return nil +} + func (backend *Backend) ValidateUsernameFunc() func(string) error { backend.mutex.RLock() defer backend.mutex.RUnlock() diff --git a/internal/backends/memory/user.go b/internal/backends/memory/user.go index a7ef3a5..3f34ecd 100644 --- a/internal/backends/memory/user.go +++ b/internal/backends/memory/user.go @@ -1,6 +1,7 @@ package memory import ( + "fmt" "sync" "time" @@ -278,6 +279,41 @@ func (user *User) JoinRoom(room internal.Room) models.ApiError { return nil } +func (user *User) AddRoomAlias(room internal.Room, alias string) models.ApiError { + user.backend.mutex.Lock() + defer user.backend.mutex.Unlock() + + if room.Creator().ID() != user.ID() { + return models.NewError(models.M_FORBIDDEN, "only room creator can add room alias") // TODO: make room admins can use this method + } + + if _, exists := user.backend.roomAliases[alias]; exists { + return models.NewError(models.M_UNKNOWN, fmt.Sprintf("room alias #%s:%s already exists", alias, user.backend.hostname)) + } + + user.backend.roomAliases[alias] = room + + return nil +} + +func (user *User) DeleteRoomAlias(alias string) models.ApiError { + user.backend.mutex.Lock() + defer user.backend.mutex.Unlock() + + room := user.backend.GetRoomByAlias(alias) + if room == nil { + return models.NewError(models.M_NOT_FOUND, "room not found") + } + + if room.Creator().ID() != user.ID() { + return models.NewError(models.M_FORBIDDEN, "only room creator can delete room alias") // TODO: make room admins can use this method + } + + delete(user.backend.roomAliases, alias) + + return nil +} + func (user *User) AddFilter(filterID string, filter common.Filter) { user.mutex.Lock() defer user.mutex.Unlock() diff --git a/internal/handlers.go b/internal/handlers.go index ef0f12c..ad46de7 100644 --- a/internal/handlers.go +++ b/internal/handlers.go @@ -22,6 +22,7 @@ import ( "github.com/signaller-matrix/signaller/internal/models/publicrooms" "github.com/signaller-matrix/signaller/internal/models/register" "github.com/signaller-matrix/signaller/internal/models/registeravailable" + "github.com/signaller-matrix/signaller/internal/models/roomalias" mSync "github.com/signaller-matrix/signaller/internal/models/sync" "github.com/signaller-matrix/signaller/internal/models/versions" "github.com/signaller-matrix/signaller/internal/models/whoami" @@ -538,6 +539,76 @@ func publicRoomsHandler(w http.ResponseWriter, r *http.Request) { sendJsonResponse(w, http.StatusOK, response) } +// https://matrix.org/docs/spec/client_server/latest#put-matrix-client-r0-directory-room-roomalias +// https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-directory-room-roomalias +// https://matrix.org/docs/spec/client_server/latest#delete-matrix-client-r0-directory-room-roomalias +func roomAliasHandler(w http.ResponseWriter, r *http.Request) { + roomAlias := mux.Vars(r)["roomAlias"] // TODO: add validation of room alias + + var user User + + if r.Method == http.MethodPut || r.Method == http.MethodDelete { + token := getTokenFromResponse(r) + if token == "" { + errorResponse(w, models.M_FORBIDDEN, http.StatusForbidden, "") + return + } + + user = currServer.Backend.GetUserByToken(token) + if user == nil { + errorResponse(w, models.M_UNKNOWN_TOKEN, http.StatusBadRequest, "") + return + } + } + + var request roomalias.Request + err := getRequest(r, &request) + if err != nil { + errorResponse(w, models.M_BAD_JSON, http.StatusBadRequest, err.Error()) + return + } + + var response interface{} + + switch r.Method { + case http.MethodGet: + room := currServer.Backend.GetRoomByAlias(roomAlias) + if room == nil { + errorResponse(w, models.M_NOT_FOUND, http.StatusNotFound, "room not found") + return + } + + response = roomalias.ResponseGet{ + RoomID: room.ID(), + Servers: []string{currServer.Address}} + + case http.MethodPut: + room := currServer.Backend.GetRoomByID(request.RoomID) + if room == nil { + errorResponse(w, models.M_NOT_FOUND, http.StatusNotFound, "room not found") + return + } + + err := user.AddRoomAlias(room, roomAlias) + if err != nil { + errorResponse(w, err, http.StatusConflict, "") // TODO: check http code + return + } + + response = struct{}{} + case http.MethodDelete: + err := user.DeleteRoomAlias(roomAlias) + if err != nil { + errorResponse(w, err, http.StatusConflict, "") // TODO: check http code + return + } + + response = struct{}{} + } + + sendJsonResponse(w, http.StatusOK, response) +} + func sendJsonResponse(w http.ResponseWriter, httpStatus int, data interface{}) error { b, err := json.Marshal(data) if err != nil { diff --git a/internal/models/roomalias/roomalias.go b/internal/models/roomalias/roomalias.go new file mode 100644 index 0000000..493711b --- /dev/null +++ b/internal/models/roomalias/roomalias.go @@ -0,0 +1,12 @@ +package roomalias + +// Merged PUT and GET requests +type Request struct { + RoomID string `json:"room_id"` // The room ID to set. + RoomAlias string `json:"roomAlias"` // The room alias. +} + +type ResponseGet struct { + RoomID string `json:"room_id"` // The room ID for this room alias. + Servers []string `json:"servers"` // A list of servers that are aware of this room alias. +} diff --git a/internal/server.go b/internal/server.go index 3d966d9..79726f6 100644 --- a/internal/server.go +++ b/internal/server.go @@ -42,6 +42,7 @@ func NewServer(port int) (*Server, error) { router.HandleFunc("/_matrix/client/r0/publicRooms", publicRoomsHandler) router.HandleFunc("/_matrix/client/r0/user/{userId}/filter/{filterID}", GetFilterHandler).Methods("GET") router.HandleFunc("/_matrix/client/r0/user/{userId}/filter", AddFilterHandler).Methods("POST") + router.HandleFunc("/_matrix/client/r0/directory/room/{roomAlias}", roomAliasHandler).Methods(http.MethodPut, http.MethodGet, http.MethodDelete) router.HandleFunc("/", RootHandler)