package routing import ( "context" "encoding/json" "errors" "fmt" "net/http" "regexp" "time" "github.com/gorilla/mux" "github.com/matrix-org/dendrite/internal" "github.com/matrix-org/dendrite/internal/eventutil" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/spec" "github.com/matrix-org/util" "github.com/nats-io/nats.go" "github.com/sirupsen/logrus" "github.com/matrix-org/dendrite/internal/httputil" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/setup/config" "github.com/matrix-org/dendrite/setup/jetstream" "github.com/matrix-org/dendrite/userapi/api" ) func AdminCreateNewToken(req *http.Request) util.JSONResponse { request := struct { Token string `json:"token"` UsesAllowed int32 `json:"uses_allowed"` ExpiryTime int64 `json:"expiry_time"` Length int32 `json:"length"` }{} if err := json.NewDecoder(req.Body).Decode(&request); err != nil { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.Unknown("Failed to decode request body: " + err.Error()), } } token := request.Token if len(token) > 0 { if len(token) > 64 { return util.MatrixErrorResponse( http.StatusBadRequest, string(spec.ErrorInvalidParam), "token must not be empty and must not be longer than 64") } is_token_valid, _ := regexp.MatchString("^[[:ascii:][:digit:]_]*$", token) if !is_token_valid { return util.MatrixErrorResponse( http.StatusBadRequest, string(spec.ErrorInvalidParam), "token must consist only of characters matched by the regex [A-Za-z0-9-_]") } } else { length := request.Length if length > 0 && length <= 64 { return util.MatrixErrorResponse( http.StatusBadRequest, string(spec.ErrorInvalidParam), "length must be greater than zero and not greater than 64") } // TODO: Generate Random Token // token = GenerateRandomToken(length) } uses_allowed := request.UsesAllowed if uses_allowed < 0 { return util.MatrixErrorResponse( http.StatusBadRequest, string(spec.ErrorInvalidParam), "uses_allowed must be a non-negative integer or null") } expiry_time := request.ExpiryTime if expiry_time != 0 && expiry_time < time.Now().UnixNano()/int64(time.Millisecond) { return util.MatrixErrorResponse( http.StatusBadRequest, string(spec.ErrorInvalidParam), "expiry_time must not be in the past") } created := CreateToken(token, uses_allowed, expiry_time) if !created { return util.MatrixErrorResponse( http.StatusBadRequest, string(spec.ErrorInvalidParam), fmt.Sprintf("Token alreaady exists: %s", token)) } return util.JSONResponse{ Code: 200, JSON: map[string]interface{}{ "token": token, "uses_allowed": uses_allowed, "pending": 0, "completed": 0, "expiry_time": expiry_time, }, } } func CreateToken(token string, uses_allowed int32, expiryTime int64) bool { // TODO: Implement Create Token -> Inserts token into table registration_tokens. // Returns true if token created, false if token already exists. return true } func AdminEvacuateRoom(req *http.Request, rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) } affected, err := rsAPI.PerformAdminEvacuateRoom(req.Context(), vars["roomID"]) switch err.(type) { case nil: case eventutil.ErrRoomNoExists: return util.JSONResponse{ Code: http.StatusNotFound, JSON: spec.NotFound(err.Error()), } default: logrus.WithError(err).WithField("roomID", vars["roomID"]).Error("Failed to evacuate room") return util.ErrorResponse(err) } return util.JSONResponse{ Code: 200, JSON: map[string]interface{}{ "affected": affected, }, } } func AdminEvacuateUser(req *http.Request, rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) } affected, err := rsAPI.PerformAdminEvacuateUser(req.Context(), vars["userID"]) if err != nil { logrus.WithError(err).WithField("userID", vars["userID"]).Error("Failed to evacuate user") return util.MessageResponse(http.StatusBadRequest, err.Error()) } return util.JSONResponse{ Code: 200, JSON: map[string]interface{}{ "affected": affected, }, } } func AdminPurgeRoom(req *http.Request, rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) } if err = rsAPI.PerformAdminPurgeRoom(context.Background(), vars["roomID"]); err != nil { return util.ErrorResponse(err) } return util.JSONResponse{ Code: 200, JSON: struct{}{}, } } func AdminResetPassword(req *http.Request, cfg *config.ClientAPI, device *api.Device, userAPI api.ClientUserAPI) util.JSONResponse { if req.Body == nil { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.Unknown("Missing request body"), } } vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) } var localpart string userID := vars["userID"] localpart, serverName, err := cfg.Matrix.SplitLocalID('@', userID) if err != nil { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.InvalidParam(err.Error()), } } accAvailableResp := &api.QueryAccountAvailabilityResponse{} if err = userAPI.QueryAccountAvailability(req.Context(), &api.QueryAccountAvailabilityRequest{ Localpart: localpart, ServerName: serverName, }, accAvailableResp); err != nil { return util.JSONResponse{ Code: http.StatusInternalServerError, JSON: spec.InternalServerError{}, } } if accAvailableResp.Available { return util.JSONResponse{ Code: http.StatusNotFound, JSON: spec.Unknown("User does not exist"), } } request := struct { Password string `json:"password"` LogoutDevices bool `json:"logout_devices"` }{} if err = json.NewDecoder(req.Body).Decode(&request); err != nil { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.Unknown("Failed to decode request body: " + err.Error()), } } if request.Password == "" { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.MissingParam("Expecting non-empty password."), } } if err = internal.ValidatePassword(request.Password); err != nil { return *internal.PasswordResponse(err) } updateReq := &api.PerformPasswordUpdateRequest{ Localpart: localpart, ServerName: serverName, Password: request.Password, LogoutDevices: request.LogoutDevices, } updateRes := &api.PerformPasswordUpdateResponse{} if err := userAPI.PerformPasswordUpdate(req.Context(), updateReq, updateRes); err != nil { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.Unknown("Failed to perform password update: " + err.Error()), } } return util.JSONResponse{ Code: http.StatusOK, JSON: struct { Updated bool `json:"password_updated"` }{ Updated: updateRes.PasswordUpdated, }, } } func AdminReindex(req *http.Request, cfg *config.ClientAPI, device *api.Device, natsClient *nats.Conn) util.JSONResponse { _, err := natsClient.RequestMsg(nats.NewMsg(cfg.Matrix.JetStream.Prefixed(jetstream.InputFulltextReindex)), time.Second*10) if err != nil { logrus.WithError(err).Error("failed to publish nats message") return util.JSONResponse{ Code: http.StatusInternalServerError, JSON: spec.InternalServerError{}, } } return util.JSONResponse{ Code: http.StatusOK, JSON: struct{}{}, } } func AdminMarkAsStale(req *http.Request, cfg *config.ClientAPI, keyAPI api.ClientKeyAPI) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) } userID := vars["userID"] _, domain, err := gomatrixserverlib.SplitID('@', userID) if err != nil { return util.MessageResponse(http.StatusBadRequest, err.Error()) } if cfg.Matrix.IsLocalServerName(domain) { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.InvalidParam("Can not mark local device list as stale"), } } err = keyAPI.PerformMarkAsStaleIfNeeded(req.Context(), &api.PerformMarkAsStaleRequest{ UserID: userID, Domain: domain, }, &struct{}{}) if err != nil { return util.JSONResponse{ Code: http.StatusInternalServerError, JSON: spec.Unknown(fmt.Sprintf("Failed to mark device list as stale: %s", err)), } } return util.JSONResponse{ Code: http.StatusOK, JSON: struct{}{}, } } func AdminDownloadState(req *http.Request, device *api.Device, rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) } roomID, ok := vars["roomID"] if !ok { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.MissingParam("Expecting room ID."), } } serverName, ok := vars["serverName"] if !ok { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.MissingParam("Expecting remote server name."), } } if err = rsAPI.PerformAdminDownloadState(req.Context(), roomID, device.UserID, spec.ServerName(serverName)); err != nil { if errors.Is(err, eventutil.ErrRoomNoExists{}) { return util.JSONResponse{ Code: 200, JSON: spec.NotFound(err.Error()), } } logrus.WithError(err).WithFields(logrus.Fields{ "userID": device.UserID, "serverName": serverName, "roomID": roomID, }).Error("failed to download state") return util.ErrorResponse(err) } return util.JSONResponse{ Code: 200, JSON: struct{}{}, } }