Add `/_dendrite/admin/refreshDevices/{userID}`
Allows to immediately query `/devices/{userID}` over federation to
(hopefully) resolve E2EE issues.
MODIFY
clientapi/routing/admin.go
clientapi/routing/admin.go
@@ -2,20 +2,23 @@ package routing
import (
"encoding/json"
+ "fmt"
"net/http"
"time"
"github.com/gorilla/mux"
+ "github.com/matrix-org/gomatrixserverlib"
+ "github.com/matrix-org/util"
+ "github.com/nats-io/nats.go"
+ "github.com/sirupsen/logrus"
+
"github.com/matrix-org/dendrite/clientapi/jsonerror"
"github.com/matrix-org/dendrite/internal/httputil"
+ "github.com/matrix-org/dendrite/keyserver/api"
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
"github.com/matrix-org/dendrite/setup/config"
"github.com/matrix-org/dendrite/setup/jetstream"
userapi "github.com/matrix-org/dendrite/userapi/api"
- "github.com/matrix-org/gomatrixserverlib"
- "github.com/matrix-org/util"
- "github.com/nats-io/nats.go"
- "github.com/sirupsen/logrus"
)
func AdminEvacuateRoom(req *http.Request, cfg *config.ClientAPI, device *userapi.Device, rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse {
@@ -144,12 +147,6 @@ func AdminResetPassword(req *http.Request, cfg *config.ClientAPI, device *userap
}
func AdminReindex(req *http.Request, cfg *config.ClientAPI, device *userapi.Device, natsClient *nats.Conn) util.JSONResponse {
- if device.AccountType != userapi.AccountTypeAdmin {
- return util.JSONResponse{
- Code: http.StatusForbidden,
- JSON: jsonerror.Forbidden("This API can only be used by admin users."),
- }
- }
_, 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")
@@ -160,3 +157,37 @@ func AdminReindex(req *http.Request, cfg *config.ClientAPI, device *userapi.Devi
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 domain == cfg.Matrix.ServerName {
+ return util.JSONResponse{
+ Code: http.StatusBadRequest,
+ JSON: jsonerror.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: jsonerror.Unknown(fmt.Sprintf("Failed to mark device list as stale: %s", err)),
+ }
+ }
+ return util.JSONResponse{
+ Code: http.StatusOK,
+ JSON: struct{}{},
+ }
+}

MODIFY
clientapi/routing/routing.go
clientapi/routing/routing.go
@@ -163,11 +163,17 @@ func Setup(
).Methods(http.MethodPost, http.MethodOptions)
dendriteAdminRouter.Handle("/admin/fulltext/reindex",
- httputil.MakeAuthAPI("admin_fultext_reindex", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
+ httputil.MakeAdminAPI("admin_fultext_reindex", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return AdminReindex(req, cfg, device, natsClient)
}),
).Methods(http.MethodGet, http.MethodOptions)
+ dendriteAdminRouter.Handle("/admin/refreshDevices/{userID}",
+ httputil.MakeAdminAPI("admin_refresh_devices", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
+ return AdminMarkAsStale(req, cfg, keyAPI)
+ }),
+ ).Methods(http.MethodPost, http.MethodOptions)
+
// server notifications
if cfg.Matrix.ServerNotices.Enabled {
logrus.Info("Enabling server notices at /_synapse/admin/v1/send_server_notice")

MODIFY
docs/administration/4_adminapi.md
docs/administration/4_adminapi.md
@@ -62,6 +62,11 @@ the full user ID is `@alice:domain.com` then the local part is `alice`.
This endpoint instructs Dendrite to reindex all searchable events (`m.room.message`, `m.room.topic` and `m.room.name`). An empty JSON body will be returned immediately.
Indexing is done in the background, the server logs every 1000 events (or below) when they are being indexed. Once reindexing is done, you'll see something along the lines `Indexed 69586 events in 53.68223182s` in your debug logs.
+## POST `/_dendrite/admin/refreshDevices/{userID}`
+
+This endpoint instructs Dendrite to immediately query `/devices/{userID}` on a federated server. An empty JSON body will be returned on success, updating all locally stored user devices/keys. This can be used to possibly resolve E2EE issues, where the remote user can't decrypt messages.
+
+
## POST `/_synapse/admin/v1/send_server_notice`
Request body format:

MODIFY
keyserver/api/api.go
keyserver/api/api.go
@@ -45,6 +45,7 @@ type ClientKeyAPI interface {
PerformUploadDeviceSignatures(ctx context.Context, req *PerformUploadDeviceSignaturesRequest, res *PerformUploadDeviceSignaturesResponse) error
// PerformClaimKeys claims one-time keys for use in pre-key messages
PerformClaimKeys(ctx context.Context, req *PerformClaimKeysRequest, res *PerformClaimKeysResponse) error
+ PerformMarkAsStaleIfNeeded(ctx context.Context, req *PerformMarkAsStaleRequest, res *struct{}) error
}
// API functions required by the userapi

MODIFY
keyserver/internal/internal.go
keyserver/internal/internal.go
@@ -228,14 +228,21 @@ func (a *KeyInternalAPI) QueryDeviceMessages(ctx context.Context, req *api.Query
// PerformMarkAsStaleIfNeeded marks the users device list as stale, if the given deviceID is not present
// in our database.
func (a *KeyInternalAPI) PerformMarkAsStaleIfNeeded(ctx context.Context, req *api.PerformMarkAsStaleRequest, res *struct{}) error {
- knownDevices, err := a.DB.DeviceKeysForUser(ctx, req.UserID, []string{req.DeviceID}, true)
+ knownDevices, err := a.DB.DeviceKeysForUser(ctx, req.UserID, []string{}, true)
if err != nil {
return err
}
if len(knownDevices) == 0 {
- return a.Updater.ManualUpdate(ctx, req.Domain, req.UserID)
+ return fmt.Errorf("unknown user %s", req.UserID)
}
- return nil
+
+ for i := range knownDevices {
+ if knownDevices[i].DeviceID == req.DeviceID {
+ return nil // we already know about this device
+ }
+ }
+
+ return a.Updater.ManualUpdate(ctx, req.Domain, req.UserID)
}
// nolint:gocyclo

Add `/_dendrite/admin/refreshDevices/{userID}` (#2746)

Allows to immediately query `/devices/{userID}` over federation to
(hopefully) resolve E2EE issues.