diff --git a/clientapi/routing/state.go b/clientapi/routing/state.go index f53cb3013..b0e781e8a 100644 --- a/clientapi/routing/state.go +++ b/clientapi/routing/state.go @@ -217,6 +217,21 @@ func OnIncomingStateTypeRequest( var worldReadable bool var wantLatestState bool + parsedRoomID, err := spec.NewRoomID(roomID) + if err != nil { + return util.JSONResponse{ + Code: http.StatusNotFound, + JSON: spec.InvalidParam("invalid room ID"), + } + } + + // Handle user ID state keys appropriately + newStateKey, errorResp := translateUserStateKey(ctx, rsAPI, stateKey, evType, *parsedRoomID) + if errorResp != nil { + return *errorResp + } + stateKey = *newStateKey + // Always fetch visibility so that we can work out whether to show // the latest events or the last event from when the user was joined. // Then include the requested event type and state key, assuming it @@ -389,3 +404,41 @@ func OnIncomingStateTypeRequest( JSON: res, } } + +// If provided state key is a user ID (state keys beginning with @ are reserved for this purpose) +// fetch it's associated sender ID and use that instead. Otherwise returns the same state key back. +// +// This function either returns the state key that should be used, or a JSON error response that should be returned to the user. +// +// TODO: if any step of this process fails, should we fail with 404, or silently continue without using sender ID? +func translateUserStateKey(ctx context.Context, rsAPI api.ClientRoomserverAPI, stateKey string, evType string, roomID spec.RoomID) (*string, *util.JSONResponse) { + if len(stateKey) >= 1 && stateKey[0] == '@' { + parsedStateKey, err := spec.NewUserID(stateKey, true) + if err != nil { + // If invalid user ID, then there is no associated state event. + return nil, &util.JSONResponse{ + Code: http.StatusNotFound, + JSON: spec.NotFound(fmt.Sprintf("Cannot find state event for %q", evType)), + } + } + senderID, err := rsAPI.QuerySenderIDForUser(ctx, roomID, *parsedStateKey) + if err != nil { + util.GetLogger(ctx).WithError(err).Error("QuerySenderIDForUser failed") + return nil, &util.JSONResponse{ + Code: http.StatusInternalServerError, + JSON: spec.Unknown("internal server error"), + } + } + if senderID == nil { + // If no sender ID, then there is no associated state event. + return nil, &util.JSONResponse{ + Code: http.StatusNotFound, + JSON: spec.NotFound(fmt.Sprintf("Cannot find state event for %q", evType)), + } + } + newStateKey := string(*senderID) + return &newStateKey, nil + } else { + return &stateKey, nil + } +}