package room import ( "bytes" "encoding/json" "fmt" "log" "net/http" "strings" "time" "git.nutfactory.org/hoernschen/Matrix/config" "git.nutfactory.org/hoernschen/Matrix/entities/event" "git.nutfactory.org/hoernschen/Matrix/entities/user" "git.nutfactory.org/hoernschen/Matrix/utils" "github.com/cenkalti/backoff/v4" "github.com/gorilla/mux" ) func New( version string, name string, topic string, visibility string, isDirect bool, federated bool, creatorId string, ) (err error, newRoom *Room) { err, roomId := utils.CreateUUID() if err != nil { return } id := generateRoomId(roomId) newRoom = &Room{ Id: id, Version: version, Name: name, Topic: topic, Members: []string{creatorId}, Events: make(map[string]*event.Event), Visibility: visibility, IsDirect: isDirect, Federated: federated, } return } func CreateRoomHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") request := CreateRoomRequest{} errResponse := utils.CheckRequest(r) if errResponse != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(errResponse); err != nil { panic(err) } return } token, errResponse := utils.GetAccessToken(r) if errResponse != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(errResponse); err != nil { panic(err) } return } foundUser, err := user.ReadUserFromAccessToken(token) if err != nil || foundUser == nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorCode: "M_UNKNOWN_TOKEN", ErrorMessage: fmt.Sprintf("%s", err)}); err != nil { panic(err) } return } decoder := json.NewDecoder(r.Body) err = decoder.Decode(&request) if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("Could not parse JSON: %s", err)}); err != nil { panic(err) } return } err, newRoom := New( request.RoomVersion, request.Name, request.Topic, request.Visibility, request.IsDirect, request.CreationContent.Federated, foundUser.Id, ) err = CreateRoom(newRoom) if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("Database Error: %s", err)}); err != nil { panic(err) } return } createEventContent := event.CreateEventContent{ Creator: foundUser.Id, Federated: request.CreationContent.Federated, RoomVersion: newRoom.Version, } createEventContentBytes, _ := json.Marshal(createEventContent) err, createEvent := event.New( newRoom.Id, foundUser.Id, config.Homeserver, time.Now().Unix(), "m.room.create", "", string(createEventContentBytes), "", ) if err == nil { err = event.CreateEvent(createEvent, "") } if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("Error Event-Creation: %s", err)}); err != nil { panic(err) } return } memberEventContent := event.MemberEventContent{ DisplayName: foundUser.Name, IsDirect: request.IsDirect, Membership: "join", } memberEventContentBytes, _ := json.Marshal(memberEventContent) err, memberEvent := event.New( newRoom.Id, foundUser.Id, config.Homeserver, time.Now().Unix(), "m.room.member", foundUser.Id, string(memberEventContentBytes), "", ) if err == nil { err = event.CreateEvent(memberEvent, "") } if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("Error Event-Creation: %s", err)}); err != nil { panic(err) } return } userPowerLevel := make(map[string]int) userPowerLevel[foundUser.Id] = 100 powerLevelEventContent := event.PowerLevelsEventContent{ Ban: 50, EventsDefault: 0, Invite: 50, Kick: 50, Redact: 50, StateDefault: 50, Users: userPowerLevel, UsersDefault: 0, Notifications: event.Notifications{ Room: 50, }, } powerLevelEventContentBytes, _ := json.Marshal(powerLevelEventContent) err, powerLevelEvent := event.New( newRoom.Id, foundUser.Id, config.Homeserver, time.Now().Unix(), "m.room.power_levels", "", string(powerLevelEventContentBytes), "", ) if err == nil { err = event.CreateEvent(powerLevelEvent, "") } if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("Error Event-Creation: %s", err)}); err != nil { panic(err) } return } joinRule := "invite" historyVisibilty := "shared" guestAccess := "can_join" if request.Preset == "public_chat" { joinRule = "public" guestAccess = "forbidden" } joinRuleEventContent := event.JoinRuleEventContent{ JoinRule: joinRule, } joinRuleEventContentBytes, _ := json.Marshal(joinRuleEventContent) err, joinRulesEvent := event.New( newRoom.Id, foundUser.Id, config.Homeserver, time.Now().Unix(), "m.room.join_rules", "", string(joinRuleEventContentBytes), "", ) if err == nil { err = event.CreateEvent(joinRulesEvent, "") } if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("Error Event-Creation: %s", err)}); err != nil { panic(err) } return } historyVisiblilityEventContent := event.HistoryVisibilityEventContent{ HistoryVisibility: historyVisibilty, } historyVisiblilityContentBytes, _ := json.Marshal(historyVisiblilityEventContent) err, historyVisibilityEvent := event.New( newRoom.Id, foundUser.Id, config.Homeserver, time.Now().Unix(), "m.room.history_visibility", "", string(historyVisiblilityContentBytes), "", ) if err == nil { err = event.CreateEvent(historyVisibilityEvent, "") } if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("Error Event-Creation: %s", err)}); err != nil { panic(err) } return } guestAccessEventContent := event.GuestAccessEventContent{ GuestAccess: guestAccess, } guestAccessContentBytes, _ := json.Marshal(guestAccessEventContent) err, guestAccessEvent := event.New( newRoom.Id, foundUser.Id, config.Homeserver, time.Now().Unix(), "m.room.guest_access", "", string(guestAccessContentBytes), "", ) if err == nil { err = event.CreateEvent(guestAccessEvent, "") } if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("Error Event-Creation: %s", err)}); err != nil { panic(err) } return } nameEventContent := event.NameEventContent{ Name: newRoom.Name, } nameContentBytes, _ := json.Marshal(nameEventContent) err, nameEvent := event.New( newRoom.Id, foundUser.Id, config.Homeserver, time.Now().Unix(), "m.room.name", "", string(nameContentBytes), "", ) if err == nil { err = event.CreateEvent(nameEvent, "") } if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("Error Event-Creation: %s", err)}); err != nil { panic(err) } return } topicEventContent := event.TopicEventContent{ Topic: newRoom.Topic, } topicContentBytes, _ := json.Marshal(topicEventContent) err, topicEvent := event.New( newRoom.Id, foundUser.Id, config.Homeserver, time.Now().Unix(), "m.room.topic", "", string(topicContentBytes), "", ) if err == nil { err = event.CreateEvent(topicEvent, "") } if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("Error Event-Creation: %s", err)}); err != nil { panic(err) } return } response := CreateRoomResponse{RoomId: newRoom.Id} w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(response); err != nil { panic(err) } } func GetRoomMemberHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") errResponse := utils.CheckRequest(r) if errResponse != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(errResponse); err != nil { panic(err) } return } token, errResponse := utils.GetAccessToken(r) if errResponse != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(errResponse); err != nil { panic(err) } return } foundUser, err := user.ReadUserFromAccessToken(token) if err != nil || foundUser == nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorCode: "M_UNKNOWN_TOKEN", ErrorMessage: fmt.Sprintf("%s", err)}); err != nil { panic(err) } return } vars := mux.Vars(r) roomId := vars["roomId"] if roomId == "" { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: "Missing Params"}); err != nil { panic(err) } return } event.ReadStateEvents(roomId, "m.room.member") w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode("Not Implemented"); err != nil { panic(err) } } func JoinRoomUserHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") request := JoinRoomUserRequest{} errResponse := utils.CheckRequest(r) if errResponse != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(errResponse); err != nil { panic(err) } return } token, errResponse := utils.GetAccessToken(r) if errResponse != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(errResponse); err != nil { panic(err) } return } foundUser, err := user.ReadUserFromAccessToken(token) if err != nil || foundUser == nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorCode: "M_UNKNOWN_TOKEN", ErrorMessage: fmt.Sprintf("%s", err)}); err != nil { panic(err) } return } decoder := json.NewDecoder(r.Body) err = decoder.Decode(&request) if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("JoinRoomUserHandler Could not parse JSON Request: %s", err)}); err != nil { panic(err) } return } vars := mux.Vars(r) roomId := vars["roomId"] if roomId == "" { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: "JoinRoomUserHandler Missing Parameter"}); err != nil { panic(err) } return } foundRoom, err := ReadRoom(roomId) if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("JoinRoomUserHandler Database Error ReadRoom: %s", err)}); err != nil { panic(err) } return } var joinEvent *event.Event if foundRoom == nil { server := strings.Split(roomId, ":")[1] requestUrl := fmt.Sprintf("%s://%s/_matrix/federation/v1/make_join/%s/%s", config.HttpString, server, roomId, foundUser.Id) client := &http.Client{Timeout: 2 * time.Second} var req *http.Request req, err = http.NewRequest(http.MethodGet, requestUrl, bytes.NewBuffer(nil)) if err != nil { return } req.Header["Content-Type"] = []string{"application/json"} res, err := client.Do(req) if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("JoinRoomUserHandler Error Getting Response Make JSON: %s", err)}); err != nil { panic(err) } return } if res.StatusCode != http.StatusOK { errResponse = utils.HandleHTTPError(res) if errResponse != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(errResponse); err != nil { panic(err) } return } } makeJoinRes := makeJoinResponse{} decoder = json.NewDecoder(res.Body) err = decoder.Decode(&makeJoinRes) if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("JoinRoomUserHandler Could not parse JSON makeJoinResponse: %s", err)}); err != nil { panic(err) } return } err = CreateRoom(&Room{Id: roomId, Version: makeJoinRes.RoomVersion}) if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("JoinRoomUserHandler Database Error CreateRoom: %s", err)}); err != nil { panic(err) } return } err, joinEvent = event.New( roomId, makeJoinRes.Event.Sender, makeJoinRes.Event.Origin, makeJoinRes.Event.Timestamp, makeJoinRes.Event.EventType, makeJoinRes.Event.StateKey, makeJoinRes.Event.Content, "", ) requestUrl = fmt.Sprintf("%s://%s/_matrix/federation/v2/send_join/%s/%s", config.HttpString, server, roomId, joinEvent.Id) reqBody, err := json.Marshal(joinEvent) if err != nil { return } client = &http.Client{Timeout: 2 * time.Second} req, err = http.NewRequest(http.MethodPut, requestUrl, bytes.NewBuffer(reqBody)) if err != nil { return } req.Header["Content-Type"] = []string{"application/json"} res, err = client.Do(req) if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("JoinRoomUserHandler Error Getting Response Send Join: %s", err)}); err != nil { panic(err) } return } if res.StatusCode != http.StatusOK { errResponse = utils.HandleHTTPError(res) if errResponse != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(errResponse); err != nil { panic(err) } return } } joinRes := joinRoomServerResponse{} decoder = json.NewDecoder(res.Body) err = decoder.Decode(&joinRes) if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("JoinRoomUserHandler Could not parse JSON joinRoomServerResponse: %s", err)}); err != nil { panic(err) } return } err = event.HandleEvents(joinRes.State, "") if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("JoinRoomUserHandler Error Handling Events: %s", err)}); err != nil { panic(err) } return } } memberEventContent := event.MemberEventContent{ DisplayName: foundUser.Name, IsDirect: true, Membership: "join", } memberEventContentBytes, _ := json.Marshal(memberEventContent) err, joinEvent = event.New( roomId, foundUser.Id, config.Homeserver, time.Now().Unix(), "m.room.member", foundUser.Id, string(memberEventContentBytes), "", ) err, txnId := utils.CreateUUID() err = event.CreateEvent(joinEvent, txnId) if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("JoinRoomUserHandler Database Error CreateEvent: %s", err)}); err != nil { panic(err) } return } event.HandleEvent(joinEvent, "") transaction := &event.Transaction{ Id: txnId, Origin: config.Homeserver, Timestamp: time.Now().Unix(), PDUS: []*event.Event{joinEvent}, } servers, err := event.ReadServers(roomId) if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("JoinRoomUserHandler Database Error ReadServers: %s", err)}); err != nil { panic(err) } return } for _, server := range servers { if server != config.Homeserver { log.Printf("Send Transaction to %s", server) operation := func() error { return event.SendTransaction(transaction, server, config.HttpString, config.AuthentificationCheck) } notify := func(err error, duration time.Duration) { log.Printf("Error Sending Transaction, retrying in %ss: %s", duration/1000000000, err) } backoff.RetryNotify(operation, backoff.NewExponentialBackOff(), notify) } } /* err = CreateRoomMember(roomId, foundUser.Id, config.Homeserver) if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("JoinRoomUserHandler Database Error CreateRoomMember: %s", err)}); err != nil { panic(err) } return } */ response := JoinRoomUserResponse{RoomId: roomId} w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(response); err != nil { panic(err) } } func GetPrepInfoToJoinHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") errResponse := utils.CheckRequest(r) if errResponse != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(errResponse); err != nil { panic(err) } return } vars := mux.Vars(r) roomId := vars["roomId"] userId := vars["userId"] if roomId == "" || userId == "" { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: "GetPrepInfoToJoinHandler Missing Parameter"}); err != nil { panic(err) } return } homeserver := strings.Split(userId, ":") if len(homeserver) <= 1 { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: "GetPrepInfoToJoinHandler Missing Homeserver in UserId"}); err != nil { panic(err) } return } foundRoom, err := ReadRoom(roomId) if err != nil || foundRoom == nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorCode: "MISSING_ROOM", ErrorMessage: fmt.Sprintf("%s", err)}); err != nil { panic(err) } return } memberEventContent := event.MemberEventContent{ Membership: "join", } memberEventContentBytes, err := json.Marshal(memberEventContent) if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("GetPrepInfoToJoinHandler Could not parse JSON memberEventContent: %s", err)}); err != nil { panic(err) } return } response := makeJoinResponse{ RoomVersion: foundRoom.Version, Event: event.Event{ Sender: userId, Origin: homeserver[1], Timestamp: time.Now().Unix(), EventType: "m.room.member", StateKey: userId, Content: string(memberEventContentBytes), }, } w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(response); err != nil { panic(err) } } func JoinRoomServerHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") request := event.Event{} errResponse := utils.CheckRequest(r) if errResponse != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(errResponse); err != nil { panic(err) } return } decoder := json.NewDecoder(r.Body) err := decoder.Decode(&request) if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("JoinRoomServerHandler Could not parse JSON Request: %s", err)}); err != nil { panic(err) } return } vars := mux.Vars(r) roomId := vars["roomId"] eventId := vars["eventId"] if roomId == "" || eventId == "" { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: "JoinRoomServerHandler Missing Parameter"}); err != nil { panic(err) } return } foundRoom, err := ReadRoom(roomId) if err != nil || foundRoom == nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorCode: "MISSING_ROOM", ErrorMessage: fmt.Sprintf("%s", err)}); err != nil { panic(err) } return } request.RoomId = roomId request.Id = eventId memberEventContent := event.MemberEventContent{} err = json.Unmarshal([]byte(request.Content), &memberEventContent) if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("JoinRoomServerHandler Could not parse JSON MemberEventContent: %s", err)}); err != nil { panic(err) } return } if memberEventContent.Membership != "join" { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: "JoinRoomServerHandler Wrong Membership"}); err != nil { panic(err) } return } authChain, err := event.GetAuthChain(&request) if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("JoinRoomServerHandler Error Creating Auth Chain: %s", err)}); err != nil { panic(err) } return } stateEvents, err := event.ReadStateEventsFromRoom(roomId) if err != nil { w.WriteHeader(http.StatusBadRequest) if err := json.NewEncoder(w).Encode(utils.ErrorResponse{ErrorMessage: fmt.Sprintf("JoinRoomServerHandler Database Error ReadStateEventsFromRoom: %s", err)}); err != nil { panic(err) } return } response := joinRoomServerResponse{ Origin: config.Homeserver, AuthChain: authChain, State: stateEvents, } w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(response); err != nil { panic(err) } } func generateRoomId(id string) string { return fmt.Sprintf("!%s:%s", id, config.Homeserver) }