Handle createRoom & sendPDUs
This commit is contained in:
parent
3d02c81031
commit
a5ba533cfb
|
@ -48,6 +48,7 @@ type createRoomRequest struct {
|
||||||
RoomVersion gomatrixserverlib.RoomVersion `json:"room_version"`
|
RoomVersion gomatrixserverlib.RoomVersion `json:"room_version"`
|
||||||
PowerLevelContentOverride json.RawMessage `json:"power_level_content_override"`
|
PowerLevelContentOverride json.RawMessage `json:"power_level_content_override"`
|
||||||
IsDirect bool `json:"is_direct"`
|
IsDirect bool `json:"is_direct"`
|
||||||
|
SenderID string `json:"sender_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r createRoomRequest) Validate() *util.JSONResponse {
|
func (r createRoomRequest) Validate() *util.JSONResponse {
|
||||||
|
@ -107,6 +108,208 @@ type createRoomResponse struct {
|
||||||
RoomAlias string `json:"room_alias,omitempty"` // in synapse not spec
|
RoomAlias string `json:"room_alias,omitempty"` // in synapse not spec
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateRoomCryptoIDs implements /createRoom
|
||||||
|
func CreateRoomCryptoIDs(
|
||||||
|
req *http.Request, device *api.Device,
|
||||||
|
cfg *config.ClientAPI,
|
||||||
|
profileAPI api.ClientUserAPI, rsAPI roomserverAPI.ClientRoomserverAPI,
|
||||||
|
asAPI appserviceAPI.AppServiceInternalAPI,
|
||||||
|
) util.JSONResponse {
|
||||||
|
var createRequest createRoomRequest
|
||||||
|
resErr := httputil.UnmarshalJSONRequest(req, &createRequest)
|
||||||
|
if resErr != nil {
|
||||||
|
return *resErr
|
||||||
|
}
|
||||||
|
if resErr = createRequest.Validate(); resErr != nil {
|
||||||
|
return *resErr
|
||||||
|
}
|
||||||
|
evTime, err := httputil.ParseTSParam(req)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: spec.InvalidParam(err.Error()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return makeCreateRoomEvents(req.Context(), createRequest, device, cfg, profileAPI, rsAPI, asAPI, evTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeCreateRoomEvents(
|
||||||
|
ctx context.Context,
|
||||||
|
createRequest createRoomRequest, device *api.Device,
|
||||||
|
cfg *config.ClientAPI,
|
||||||
|
profileAPI api.ClientUserAPI, rsAPI roomserverAPI.ClientRoomserverAPI,
|
||||||
|
asAPI appserviceAPI.AppServiceInternalAPI,
|
||||||
|
evTime time.Time,
|
||||||
|
) util.JSONResponse {
|
||||||
|
userID, err := spec.NewUserID(device.UserID, true)
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(ctx).WithError(err).Error("invalid userID")
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !cfg.Matrix.IsLocalServerName(userID.Domain()) {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
JSON: spec.Forbidden(fmt.Sprintf("User domain %q not configured locally", userID.Domain())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger := util.GetLogger(ctx)
|
||||||
|
|
||||||
|
// TODO: Check room ID doesn't clash with an existing one, and we
|
||||||
|
// probably shouldn't be using pseudo-random strings, maybe GUIDs?
|
||||||
|
roomID, err := spec.NewRoomID(fmt.Sprintf("!%s:%s", util.RandomString(16), userID.Domain()))
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(ctx).WithError(err).Error("invalid roomID")
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clobber keys: creator, room_version
|
||||||
|
|
||||||
|
roomVersion := rsAPI.DefaultRoomVersion()
|
||||||
|
if createRequest.RoomVersion != "" {
|
||||||
|
candidateVersion := gomatrixserverlib.RoomVersion(createRequest.RoomVersion)
|
||||||
|
_, roomVersionError := roomserverVersion.SupportedRoomVersion(candidateVersion)
|
||||||
|
if roomVersionError != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: spec.UnsupportedRoomVersion(roomVersionError.Error()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
roomVersion = candidateVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.WithFields(log.Fields{
|
||||||
|
"userID": userID.String(),
|
||||||
|
"roomID": roomID.String(),
|
||||||
|
"roomVersion": roomVersion,
|
||||||
|
}).Info("Creating new room")
|
||||||
|
|
||||||
|
profile, err := appserviceAPI.RetrieveUserProfile(ctx, userID.String(), asAPI, profileAPI)
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(ctx).WithError(err).Error("appserviceAPI.RetrieveUserProfile failed")
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
userDisplayName := profile.DisplayName
|
||||||
|
userAvatarURL := profile.AvatarURL
|
||||||
|
|
||||||
|
keyID := cfg.Matrix.KeyID
|
||||||
|
privateKey := cfg.Matrix.PrivateKey
|
||||||
|
|
||||||
|
req := roomserverAPI.PerformCreateRoomRequest{
|
||||||
|
InvitedUsers: createRequest.Invite,
|
||||||
|
RoomName: createRequest.Name,
|
||||||
|
Visibility: createRequest.Visibility,
|
||||||
|
Topic: createRequest.Topic,
|
||||||
|
StatePreset: createRequest.Preset,
|
||||||
|
CreationContent: createRequest.CreationContent,
|
||||||
|
InitialState: createRequest.InitialState,
|
||||||
|
RoomAliasName: createRequest.RoomAliasName,
|
||||||
|
RoomVersion: roomVersion,
|
||||||
|
PowerLevelContentOverride: createRequest.PowerLevelContentOverride,
|
||||||
|
IsDirect: createRequest.IsDirect,
|
||||||
|
|
||||||
|
UserDisplayName: userDisplayName,
|
||||||
|
UserAvatarURL: userAvatarURL,
|
||||||
|
KeyID: keyID,
|
||||||
|
PrivateKey: privateKey,
|
||||||
|
EventTime: evTime,
|
||||||
|
|
||||||
|
SenderID: createRequest.SenderID,
|
||||||
|
}
|
||||||
|
|
||||||
|
createEvents, err := rsAPI.PerformCreateRoomCryptoIDs(ctx, *userID, *roomID, &req)
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(ctx).WithError(err).Error("MakeCreateRoomEvents failed")
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{Err: err.Error()},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response := createRoomCryptoIDsResponse{
|
||||||
|
RoomID: roomID.String(),
|
||||||
|
Version: string(roomVersion),
|
||||||
|
PDUs: ToProtoEvents(ctx, createEvents, rsAPI),
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: 200,
|
||||||
|
JSON: response,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type createRoomCryptoIDsResponse struct {
|
||||||
|
RoomID string `json:"room_id"`
|
||||||
|
Version string `json:"room_version"`
|
||||||
|
PDUs []json.RawMessage `json:"pdus"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToProtoEvents(ctx context.Context, events []gomatrixserverlib.PDU, rsAPI roomserverAPI.ClientRoomserverAPI) []json.RawMessage {
|
||||||
|
result := make([]json.RawMessage, len(events))
|
||||||
|
for i, event := range events {
|
||||||
|
result[i] = json.RawMessage(event.JSON())
|
||||||
|
//fmt.Printf("\nProcessing %s event (%s)\n", events[i].Type(), events[i].EventID())
|
||||||
|
//var rawJson interface{}
|
||||||
|
//json.Unmarshal(events[i].JSON(), &rawJson)
|
||||||
|
//fmt.Printf("JSON: %+v\n", rawJson)
|
||||||
|
//result[i] = gomatrixserverlib.ProtoEvent{
|
||||||
|
// SenderID: string(events[i].SenderID()),
|
||||||
|
// RoomID: events[i].RoomID().String(),
|
||||||
|
// Type: events[i].Type(),
|
||||||
|
// StateKey: events[i].StateKey(),
|
||||||
|
// PrevEvents: events[i].PrevEventIDs(),
|
||||||
|
// AuthEvents: events[i].AuthEventIDs(),
|
||||||
|
// Redacts: events[i].Redacts(),
|
||||||
|
// Depth: events[i].Depth(),
|
||||||
|
// Content: events[i].Content(),
|
||||||
|
// Unsigned: events[i].Unsigned(),
|
||||||
|
// Hashes: events[i].Hashes(),
|
||||||
|
// OriginServerTimestamp: events[i].OriginServerTS(),
|
||||||
|
//}
|
||||||
|
|
||||||
|
//roomVersion, _ := rsAPI.QueryRoomVersionForRoom(ctx, events[i].RoomID().String())
|
||||||
|
//verImpl, _ := gomatrixserverlib.GetRoomVersion(roomVersion)
|
||||||
|
//eventJSON, err := json.Marshal(result[i])
|
||||||
|
//if err != nil {
|
||||||
|
// util.GetLogger(ctx).WithError(err).Error("failed marshalling event")
|
||||||
|
// continue
|
||||||
|
//}
|
||||||
|
//pdu, err := verImpl.NewEventFromUntrustedJSON(eventJSON)
|
||||||
|
//if err != nil {
|
||||||
|
// util.GetLogger(ctx).WithError(err).Error("failed making event from json")
|
||||||
|
// continue
|
||||||
|
//}
|
||||||
|
//fmt.Printf("\nProcessing %s event (%s) - PDU\n", result[i].Type, pdu.EventID())
|
||||||
|
//fmt.Printf(" EventID: %v - %v\n", events[i].EventID(), pdu.EventID())
|
||||||
|
//fmt.Printf(" SenderID: %s - %s\n", events[i].SenderID(), pdu.SenderID())
|
||||||
|
//fmt.Printf(" RoomID: %s - %s\n", events[i].RoomID().String(), pdu.RoomID().String())
|
||||||
|
//fmt.Printf(" Type: %s - %s\n", events[i].Type(), pdu.Type())
|
||||||
|
//fmt.Printf(" StateKey: %s - %s\n", *events[i].StateKey(), *pdu.StateKey())
|
||||||
|
//fmt.Printf(" PrevEvents: %v - %v\n", events[i].PrevEventIDs(), pdu.PrevEventIDs())
|
||||||
|
//fmt.Printf(" AuthEvents: %v - %v\n", events[i].AuthEventIDs(), pdu.AuthEventIDs())
|
||||||
|
//fmt.Printf(" Redacts: %s - %s\n", events[i].Redacts(), pdu.Redacts())
|
||||||
|
//fmt.Printf(" Depth: %d - %d\n", events[i].Depth(), pdu.Depth())
|
||||||
|
//fmt.Printf(" Content: %v - %v\n", events[i].Content(), pdu.Content())
|
||||||
|
//fmt.Printf(" Unsigned: %v - %v\n", events[i].Unsigned(), pdu.Unsigned())
|
||||||
|
//fmt.Printf(" Hashes: %v - %v\n", events[i].Hashes(), pdu.Hashes())
|
||||||
|
//fmt.Printf(" OriginServerTS: %d - %d\n", events[i].OriginServerTS(), pdu.OriginServerTS())
|
||||||
|
//json.Unmarshal(eventJSON, &rawJson)
|
||||||
|
//fmt.Printf("JSON: %+v\n", rawJson)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// CreateRoom implements /createRoom
|
// CreateRoom implements /createRoom
|
||||||
func CreateRoom(
|
func CreateRoom(
|
||||||
req *http.Request, device *api.Device,
|
req *http.Request, device *api.Device,
|
||||||
|
@ -132,7 +335,6 @@ func CreateRoom(
|
||||||
return createRoom(req.Context(), createRequest, device, cfg, profileAPI, rsAPI, asAPI, evTime)
|
return createRoom(req.Context(), createRequest, device, cfg, profileAPI, rsAPI, asAPI, evTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
// createRoom implements /createRoom
|
|
||||||
func createRoom(
|
func createRoom(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
createRequest createRoomRequest, device *api.Device,
|
createRequest createRoomRequest, device *api.Device,
|
||||||
|
|
|
@ -312,6 +312,24 @@ func Setup(
|
||||||
return CreateRoom(req, device, cfg, userAPI, rsAPI, asAPI)
|
return CreateRoom(req, device, cfg, userAPI, rsAPI, asAPI)
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPost, http.MethodOptions)
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
|
unstableMux.Handle("/org.matrix.msc_cryptoids/createRoom",
|
||||||
|
httputil.MakeAuthAPI("createRoom", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
|
logrus.Info("Processing request to /org.matrix.msc_cryptoids/createRoom")
|
||||||
|
return CreateRoomCryptoIDs(req, device, cfg, userAPI, rsAPI, asAPI)
|
||||||
|
}),
|
||||||
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
|
unstableMux.Handle("/org.matrix.msc_cryptoids/sendPDUs",
|
||||||
|
httputil.MakeAuthAPI("send_pdus", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
|
logrus.Info("Processing request to /org.matrix.msc_cryptoids/sendPDUs")
|
||||||
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
|
return *r
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: when making events such as for create_room, multiple PDUs will need to be passed between the client & server.
|
||||||
|
return SendPDUs(req, device, cfg, userAPI, rsAPI, asAPI)
|
||||||
|
}),
|
||||||
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
|
// TODO: update for cryptoIDs
|
||||||
v3mux.Handle("/join/{roomIDOrAlias}",
|
v3mux.Handle("/join/{roomIDOrAlias}",
|
||||||
httputil.MakeAuthAPI(spec.Join, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI(spec.Join, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
if r := rateLimits.Limit(req, device); r != nil {
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
|
@ -336,6 +354,7 @@ func Setup(
|
||||||
).Methods(http.MethodPost, http.MethodOptions)
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
|
|
||||||
if mscCfg.Enabled("msc2753") {
|
if mscCfg.Enabled("msc2753") {
|
||||||
|
// TODO: update for cryptoIDs
|
||||||
v3mux.Handle("/peek/{roomIDOrAlias}",
|
v3mux.Handle("/peek/{roomIDOrAlias}",
|
||||||
httputil.MakeAuthAPI(spec.Peek, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI(spec.Peek, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
if r := rateLimits.Limit(req, device); r != nil {
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
|
@ -356,6 +375,7 @@ func Setup(
|
||||||
return GetJoinedRooms(req, device, rsAPI)
|
return GetJoinedRooms(req, device, rsAPI)
|
||||||
}, httputil.WithAllowGuests()),
|
}, httputil.WithAllowGuests()),
|
||||||
).Methods(http.MethodGet, http.MethodOptions)
|
).Methods(http.MethodGet, http.MethodOptions)
|
||||||
|
// TODO: update for cryptoIDs
|
||||||
v3mux.Handle("/rooms/{roomID}/join",
|
v3mux.Handle("/rooms/{roomID}/join",
|
||||||
httputil.MakeAuthAPI(spec.Join, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI(spec.Join, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
if r := rateLimits.Limit(req, device); r != nil {
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
|
@ -378,6 +398,7 @@ func Setup(
|
||||||
return resp.(util.JSONResponse)
|
return resp.(util.JSONResponse)
|
||||||
}, httputil.WithAllowGuests()),
|
}, httputil.WithAllowGuests()),
|
||||||
).Methods(http.MethodPost, http.MethodOptions)
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
|
// TODO: update for cryptoIDs
|
||||||
v3mux.Handle("/rooms/{roomID}/leave",
|
v3mux.Handle("/rooms/{roomID}/leave",
|
||||||
httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
if r := rateLimits.Limit(req, device); r != nil {
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
|
@ -392,6 +413,7 @@ func Setup(
|
||||||
)
|
)
|
||||||
}, httputil.WithAllowGuests()),
|
}, httputil.WithAllowGuests()),
|
||||||
).Methods(http.MethodPost, http.MethodOptions)
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
|
// TODO: update for cryptoIDs
|
||||||
v3mux.Handle("/rooms/{roomID}/unpeek",
|
v3mux.Handle("/rooms/{roomID}/unpeek",
|
||||||
httputil.MakeAuthAPI("unpeek", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("unpeek", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
@ -403,6 +425,7 @@ func Setup(
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPost, http.MethodOptions)
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
|
// TODO: update for cryptoIDs
|
||||||
v3mux.Handle("/rooms/{roomID}/ban",
|
v3mux.Handle("/rooms/{roomID}/ban",
|
||||||
httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
@ -412,6 +435,7 @@ func Setup(
|
||||||
return SendBan(req, userAPI, device, vars["roomID"], cfg, rsAPI, asAPI)
|
return SendBan(req, userAPI, device, vars["roomID"], cfg, rsAPI, asAPI)
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPost, http.MethodOptions)
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
|
// TODO: update for cryptoIDs
|
||||||
v3mux.Handle("/rooms/{roomID}/invite",
|
v3mux.Handle("/rooms/{roomID}/invite",
|
||||||
httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
if r := rateLimits.Limit(req, device); r != nil {
|
if r := rateLimits.Limit(req, device); r != nil {
|
||||||
|
@ -424,6 +448,7 @@ func Setup(
|
||||||
return SendInvite(req, userAPI, device, vars["roomID"], cfg, rsAPI, asAPI)
|
return SendInvite(req, userAPI, device, vars["roomID"], cfg, rsAPI, asAPI)
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPost, http.MethodOptions)
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
|
// TODO: update for cryptoIDs
|
||||||
v3mux.Handle("/rooms/{roomID}/kick",
|
v3mux.Handle("/rooms/{roomID}/kick",
|
||||||
httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
@ -433,6 +458,7 @@ func Setup(
|
||||||
return SendKick(req, userAPI, device, vars["roomID"], cfg, rsAPI, asAPI)
|
return SendKick(req, userAPI, device, vars["roomID"], cfg, rsAPI, asAPI)
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPost, http.MethodOptions)
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
|
// TODO: update for cryptoIDs
|
||||||
v3mux.Handle("/rooms/{roomID}/unban",
|
v3mux.Handle("/rooms/{roomID}/unban",
|
||||||
httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("membership", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
@ -442,6 +468,7 @@ func Setup(
|
||||||
return SendUnban(req, userAPI, device, vars["roomID"], cfg, rsAPI, asAPI)
|
return SendUnban(req, userAPI, device, vars["roomID"], cfg, rsAPI, asAPI)
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPost, http.MethodOptions)
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
|
// TODO: update for cryptoIDs
|
||||||
v3mux.Handle("/rooms/{roomID}/send/{eventType}",
|
v3mux.Handle("/rooms/{roomID}/send/{eventType}",
|
||||||
httputil.MakeAuthAPI("send_message", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("send_message", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
@ -451,6 +478,7 @@ func Setup(
|
||||||
return SendEvent(req, device, vars["roomID"], vars["eventType"], nil, nil, cfg, rsAPI, nil)
|
return SendEvent(req, device, vars["roomID"], vars["eventType"], nil, nil, cfg, rsAPI, nil)
|
||||||
}, httputil.WithAllowGuests()),
|
}, httputil.WithAllowGuests()),
|
||||||
).Methods(http.MethodPost, http.MethodOptions)
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
|
// TODO: update for cryptoIDs
|
||||||
v3mux.Handle("/rooms/{roomID}/send/{eventType}/{txnID}",
|
v3mux.Handle("/rooms/{roomID}/send/{eventType}/{txnID}",
|
||||||
httputil.MakeAuthAPI("send_message", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("send_message", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
@ -499,6 +527,7 @@ func Setup(
|
||||||
return OnIncomingStateTypeRequest(req.Context(), device, rsAPI, vars["roomID"], vars["type"], vars["stateKey"], eventFormat)
|
return OnIncomingStateTypeRequest(req.Context(), device, rsAPI, vars["roomID"], vars["type"], vars["stateKey"], eventFormat)
|
||||||
}, httputil.WithAllowGuests())).Methods(http.MethodGet, http.MethodOptions)
|
}, httputil.WithAllowGuests())).Methods(http.MethodGet, http.MethodOptions)
|
||||||
|
|
||||||
|
// TODO: update for cryptoIDs
|
||||||
v3mux.Handle("/rooms/{roomID}/state/{eventType:[^/]+/?}",
|
v3mux.Handle("/rooms/{roomID}/state/{eventType:[^/]+/?}",
|
||||||
httputil.MakeAuthAPI("send_message", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("send_message", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
@ -511,6 +540,7 @@ func Setup(
|
||||||
}, httputil.WithAllowGuests()),
|
}, httputil.WithAllowGuests()),
|
||||||
).Methods(http.MethodPut, http.MethodOptions)
|
).Methods(http.MethodPut, http.MethodOptions)
|
||||||
|
|
||||||
|
// TODO: update for cryptoIDs
|
||||||
v3mux.Handle("/rooms/{roomID}/state/{eventType}/{stateKey}",
|
v3mux.Handle("/rooms/{roomID}/state/{eventType}/{stateKey}",
|
||||||
httputil.MakeAuthAPI("send_message", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("send_message", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
@ -559,6 +589,7 @@ func Setup(
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodGet, http.MethodOptions)
|
).Methods(http.MethodGet, http.MethodOptions)
|
||||||
|
|
||||||
|
// TODO: update for cryptoIDs
|
||||||
v3mux.Handle("/directory/room/{roomAlias}",
|
v3mux.Handle("/directory/room/{roomAlias}",
|
||||||
httputil.MakeAuthAPI("directory_room", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("directory_room", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
@ -569,6 +600,7 @@ func Setup(
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPut, http.MethodOptions)
|
).Methods(http.MethodPut, http.MethodOptions)
|
||||||
|
|
||||||
|
// TODO: update for cryptoIDs
|
||||||
v3mux.Handle("/directory/room/{roomAlias}",
|
v3mux.Handle("/directory/room/{roomAlias}",
|
||||||
httputil.MakeAuthAPI("directory_room", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("directory_room", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
@ -588,6 +620,7 @@ func Setup(
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodGet, http.MethodOptions)
|
).Methods(http.MethodGet, http.MethodOptions)
|
||||||
|
|
||||||
|
// TODO: update for cryptoIDs
|
||||||
v3mux.Handle("/directory/list/room/{roomID}",
|
v3mux.Handle("/directory/list/room/{roomID}",
|
||||||
httputil.MakeAuthAPI("directory_list", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("directory_list", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
@ -597,6 +630,7 @@ func Setup(
|
||||||
return SetVisibility(req, rsAPI, device, vars["roomID"])
|
return SetVisibility(req, rsAPI, device, vars["roomID"])
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPut, http.MethodOptions)
|
).Methods(http.MethodPut, http.MethodOptions)
|
||||||
|
// TODO: update for cryptoIDs
|
||||||
v3mux.Handle("/directory/list/appservice/{networkID}/{roomID}",
|
v3mux.Handle("/directory/list/appservice/{networkID}/{roomID}",
|
||||||
httputil.MakeAuthAPI("directory_list", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("directory_list", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
@ -608,6 +642,7 @@ func Setup(
|
||||||
).Methods(http.MethodPut, http.MethodOptions)
|
).Methods(http.MethodPut, http.MethodOptions)
|
||||||
|
|
||||||
// Undocumented endpoint
|
// Undocumented endpoint
|
||||||
|
// TODO: update for cryptoIDs
|
||||||
v3mux.Handle("/directory/list/appservice/{networkID}/{roomID}",
|
v3mux.Handle("/directory/list/appservice/{networkID}/{roomID}",
|
||||||
httputil.MakeAuthAPI("directory_list", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("directory_list", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
@ -648,6 +683,7 @@ func Setup(
|
||||||
return SendTyping(req, device, vars["roomID"], vars["userID"], rsAPI, syncProducer)
|
return SendTyping(req, device, vars["roomID"], vars["userID"], rsAPI, syncProducer)
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPut, http.MethodOptions)
|
).Methods(http.MethodPut, http.MethodOptions)
|
||||||
|
// TODO: update for cryptoIDs
|
||||||
v3mux.Handle("/rooms/{roomID}/redact/{eventID}",
|
v3mux.Handle("/rooms/{roomID}/redact/{eventID}",
|
||||||
httputil.MakeAuthAPI("rooms_redact", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("rooms_redact", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
@ -657,6 +693,7 @@ func Setup(
|
||||||
return SendRedaction(req, device, vars["roomID"], vars["eventID"], cfg, rsAPI, nil, nil)
|
return SendRedaction(req, device, vars["roomID"], vars["eventID"], cfg, rsAPI, nil, nil)
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPost, http.MethodOptions)
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
|
// TODO: update for cryptoIDs
|
||||||
v3mux.Handle("/rooms/{roomID}/redact/{eventID}/{txnId}",
|
v3mux.Handle("/rooms/{roomID}/redact/{eventID}/{txnId}",
|
||||||
httputil.MakeAuthAPI("rooms_redact", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("rooms_redact", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
@ -668,6 +705,7 @@ func Setup(
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPut, http.MethodOptions)
|
).Methods(http.MethodPut, http.MethodOptions)
|
||||||
|
|
||||||
|
// TODO: update for cryptoIDs
|
||||||
v3mux.Handle("/sendToDevice/{eventType}/{txnID}",
|
v3mux.Handle("/sendToDevice/{eventType}/{txnID}",
|
||||||
httputil.MakeAuthAPI("send_to_device", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("send_to_device", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
@ -1144,6 +1182,7 @@ func Setup(
|
||||||
}),
|
}),
|
||||||
).Methods(http.MethodPost, http.MethodOptions)
|
).Methods(http.MethodPost, http.MethodOptions)
|
||||||
|
|
||||||
|
// TODO: update for cryptoIDs
|
||||||
v3mux.Handle("/rooms/{roomID}/upgrade",
|
v3mux.Handle("/rooms/{roomID}/upgrade",
|
||||||
httputil.MakeAuthAPI("rooms_upgrade", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
httputil.MakeAuthAPI("rooms_upgrade", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
|
||||||
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
vars, err := httputil.URLDecodeMapValues(mux.Vars(req))
|
||||||
|
|
140
clientapi/routing/send_pdus.go
Normal file
140
clientapi/routing/send_pdus.go
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
// Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package routing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
||||||
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
||||||
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/types"
|
||||||
|
"github.com/matrix-org/dendrite/setup/config"
|
||||||
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
||||||
|
"github.com/matrix-org/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type sendPDUsRequest struct {
|
||||||
|
Version string `json:"room_version"`
|
||||||
|
PDUs []json.RawMessage `json:"pdus"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendPDUs implements /sendPDUs
|
||||||
|
func SendPDUs(
|
||||||
|
req *http.Request, device *api.Device,
|
||||||
|
cfg *config.ClientAPI,
|
||||||
|
profileAPI api.ClientUserAPI, rsAPI roomserverAPI.ClientRoomserverAPI,
|
||||||
|
asAPI appserviceAPI.AppServiceInternalAPI,
|
||||||
|
) util.JSONResponse {
|
||||||
|
// TODO: cryptoIDs - should this include an "eventType"?
|
||||||
|
// if it's a bulk send endpoint, I don't think that makes any sense since there are multiple event types
|
||||||
|
// In that case, how do I know how to treat the events?
|
||||||
|
// I could sort them all by roomID?
|
||||||
|
// Then filter them down based on event type? (how do I collect groups of events such as for room creation?)
|
||||||
|
// Possibly based on event hash tracking that I know were sent to the client?
|
||||||
|
// For createRoom, I know what the possible list of events are, so try to find those and collect them to send to room creation.
|
||||||
|
// Could also sort by depth... but that seems dangerous and depth may not be a field forever
|
||||||
|
// Does it matter at all?
|
||||||
|
// Can't I just forward all the events to the roomserver?
|
||||||
|
// Do I need to do any specific processing on them?
|
||||||
|
|
||||||
|
var pdus sendPDUsRequest
|
||||||
|
resErr := httputil.UnmarshalJSONRequest(req, &pdus)
|
||||||
|
if resErr != nil {
|
||||||
|
return *resErr
|
||||||
|
}
|
||||||
|
|
||||||
|
userID, err := spec.NewUserID(device.UserID, true)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: spec.InvalidParam(err.Error()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inputs := make([]roomserverAPI.InputRoomEvent, 0, len(pdus.PDUs))
|
||||||
|
for _, event := range pdus.PDUs {
|
||||||
|
// TODO: cryptoIDs - event hash check?
|
||||||
|
verImpl, err := gomatrixserverlib.GetRoomVersion(gomatrixserverlib.RoomVersion(pdus.Version))
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{Err: err.Error()},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//eventJSON, err := json.Marshal(event)
|
||||||
|
//if err != nil {
|
||||||
|
// return util.JSONResponse{
|
||||||
|
// Code: http.StatusInternalServerError,
|
||||||
|
// JSON: spec.InternalServerError{Err: err.Error()},
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
// TODO: cryptoIDs - how should we be converting to a PDU here?
|
||||||
|
// if the hash matches an event we sent to the client, then the JSON should be good.
|
||||||
|
// But how do we know how to fill out if the event is redacted if we use the trustedJSON function?
|
||||||
|
// Also - untrusted JSON seems better - except it strips off the unsigned field?
|
||||||
|
// Also - gmsl events don't store the `hashes` field... problem?
|
||||||
|
|
||||||
|
pdu, err := verImpl.NewEventFromUntrustedJSON(event)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{Err: err.Error()},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
key, err := rsAPI.GetOrCreateUserRoomPrivateKey(req.Context(), *userID, pdu.RoomID())
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{Err: err.Error()},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pdu = pdu.Sign(string(pdu.SenderID()), "ed25519:1", key)
|
||||||
|
util.GetLogger(req.Context()).Infof("Processing %s event (%s)", pdu.Type(), pdu.EventID())
|
||||||
|
switch pdu.Type() {
|
||||||
|
case spec.MRoomCreate:
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: cryptoIDs - does it matter which order these are added?
|
||||||
|
// yes - if the events are for room creation.
|
||||||
|
// Could make this a client requirement? ie. events are processed based on the order they appear
|
||||||
|
// We need to check event validity after processing each event.
|
||||||
|
// ie. what if the client changes power levels that disallow further events they sent?
|
||||||
|
// We should be doing this already as part of `SendInputRoomEvents`, but how should we pass this
|
||||||
|
// failure back to the client?
|
||||||
|
inputs = append(inputs, roomserverAPI.InputRoomEvent{
|
||||||
|
Kind: roomserverAPI.KindNew,
|
||||||
|
Event: &types.HeaderedEvent{PDU: pdu},
|
||||||
|
Origin: userID.Domain(),
|
||||||
|
SendAsServer: roomserverAPI.DoNotSendToOtherServers,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// send the events to the roomserver
|
||||||
|
if err := roomserverAPI.SendInputRoomEvents(req.Context(), rsAPI, userID.Domain(), inputs, false); err != nil {
|
||||||
|
util.GetLogger(req.Context()).WithError(err).Error("roomserverAPI.SendInputRoomEvents failed")
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
JSON: spec.InternalServerError{Err: err.Error()},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
}
|
||||||
|
}
|
2
go.mod
2
go.mod
|
@ -1,5 +1,7 @@
|
||||||
module github.com/matrix-org/dendrite
|
module github.com/matrix-org/dendrite
|
||||||
|
|
||||||
|
replace github.com/matrix-org/gomatrixserverlib => ../../gomatrixserverlib/crypto-ids/
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Arceliar/ironwood v0.0.0-20221025225125-45b4281814c2
|
github.com/Arceliar/ironwood v0.0.0-20221025225125-45b4281814c2
|
||||||
github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979
|
github.com/Arceliar/phony v0.0.0-20210209235338-dde1a8dca979
|
||||||
|
|
|
@ -232,6 +232,7 @@ type ClientRoomserverAPI interface {
|
||||||
GetRoomIDForAlias(ctx context.Context, req *GetRoomIDForAliasRequest, res *GetRoomIDForAliasResponse) error
|
GetRoomIDForAlias(ctx context.Context, req *GetRoomIDForAliasRequest, res *GetRoomIDForAliasResponse) error
|
||||||
GetAliasesForRoomID(ctx context.Context, req *GetAliasesForRoomIDRequest, res *GetAliasesForRoomIDResponse) error
|
GetAliasesForRoomID(ctx context.Context, req *GetAliasesForRoomIDRequest, res *GetAliasesForRoomIDResponse) error
|
||||||
|
|
||||||
|
PerformCreateRoomCryptoIDs(ctx context.Context, userID spec.UserID, roomID spec.RoomID, createRequest *PerformCreateRoomRequest) ([]gomatrixserverlib.PDU, error)
|
||||||
PerformCreateRoom(ctx context.Context, userID spec.UserID, roomID spec.RoomID, createRequest *PerformCreateRoomRequest) (string, *util.JSONResponse)
|
PerformCreateRoom(ctx context.Context, userID spec.UserID, roomID spec.RoomID, createRequest *PerformCreateRoomRequest) (string, *util.JSONResponse)
|
||||||
// PerformRoomUpgrade upgrades a room to a newer version
|
// PerformRoomUpgrade upgrades a room to a newer version
|
||||||
PerformRoomUpgrade(ctx context.Context, roomID string, userID spec.UserID, roomVersion gomatrixserverlib.RoomVersion) (newRoomID string, err error)
|
PerformRoomUpgrade(ctx context.Context, roomID string, userID spec.UserID, roomVersion gomatrixserverlib.RoomVersion) (newRoomID string, err error)
|
||||||
|
|
|
@ -29,6 +29,8 @@ type PerformCreateRoomRequest struct {
|
||||||
KeyID gomatrixserverlib.KeyID
|
KeyID gomatrixserverlib.KeyID
|
||||||
PrivateKey ed25519.PrivateKey
|
PrivateKey ed25519.PrivateKey
|
||||||
EventTime time.Time
|
EventTime time.Time
|
||||||
|
|
||||||
|
SenderID string
|
||||||
}
|
}
|
||||||
|
|
||||||
type PerformJoinRequest struct {
|
type PerformJoinRequest struct {
|
||||||
|
|
|
@ -44,6 +44,423 @@ type Creator struct {
|
||||||
RSAPI api.RoomserverInternalAPI
|
RSAPI api.RoomserverInternalAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PerformCreateRoomCryptoIDs handles all the steps necessary to create a new room.
|
||||||
|
// nolint: gocyclo
|
||||||
|
func (c *Creator) PerformCreateRoomCryptoIDs(ctx context.Context, userID spec.UserID, roomID spec.RoomID, createRequest *api.PerformCreateRoomRequest) ([]gomatrixserverlib.PDU, error) {
|
||||||
|
verImpl, err := gomatrixserverlib.GetRoomVersion(createRequest.RoomVersion)
|
||||||
|
if err != nil {
|
||||||
|
return nil, spec.BadJSON("unknown room version")
|
||||||
|
}
|
||||||
|
|
||||||
|
createContent := map[string]interface{}{}
|
||||||
|
if len(createRequest.CreationContent) > 0 {
|
||||||
|
if err = json.Unmarshal(createRequest.CreationContent, &createContent); err != nil {
|
||||||
|
util.GetLogger(ctx).WithError(err).Error("json.Unmarshal for creation_content failed")
|
||||||
|
return nil, spec.BadJSON("invalid create content")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
senderID := spec.SenderID(createRequest.SenderID)
|
||||||
|
|
||||||
|
// TODO: cryptoIDs - should we be assigning a room NID yet?
|
||||||
|
_, err = c.DB.AssignRoomNID(ctx, roomID, createRequest.RoomVersion)
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(ctx).WithError(err).Error("failed to assign roomNID")
|
||||||
|
return nil, spec.InternalServerError{Err: err.Error()}
|
||||||
|
}
|
||||||
|
|
||||||
|
if createRequest.RoomVersion == gomatrixserverlib.RoomVersionPseudoIDs {
|
||||||
|
util.GetLogger(ctx).Infof("StoreUserRoomPublicKey - SenderID: %s UserID: %s RoomID: %s", senderID, userID.String(), roomID.String())
|
||||||
|
bytes := spec.Base64Bytes{}
|
||||||
|
err = bytes.Decode(string(senderID))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(bytes) != ed25519.PublicKeySize {
|
||||||
|
return nil, spec.BadJSON("SenderID is not a valid ed25519 public key")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: cryptoIDs - Swap this out for only storing the public key
|
||||||
|
key, keyErr := c.RSAPI.GetOrCreateUserRoomPrivateKey(ctx, userID, roomID)
|
||||||
|
if keyErr != nil {
|
||||||
|
util.GetLogger(ctx).WithError(keyErr).Error("GetOrCreateUserRoomPrivateKey failed")
|
||||||
|
return nil, spec.InternalServerError{Err: keyErr.Error()}
|
||||||
|
}
|
||||||
|
senderID = spec.SenderIDFromPseudoIDKey(key)
|
||||||
|
//err := c.RSAPI.StoreUserRoomPublicKey(ctx, senderID, userID, roomID)
|
||||||
|
//if err != nil {
|
||||||
|
// return nil, spec.InternalServerError{Err: err.Error()}
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
createContent["creator"] = senderID
|
||||||
|
createContent["room_version"] = createRequest.RoomVersion
|
||||||
|
powerLevelContent := eventutil.InitialPowerLevelsContent(string(senderID))
|
||||||
|
joinRuleContent := gomatrixserverlib.JoinRuleContent{
|
||||||
|
JoinRule: spec.Invite,
|
||||||
|
}
|
||||||
|
historyVisibilityContent := gomatrixserverlib.HistoryVisibilityContent{
|
||||||
|
HistoryVisibility: historyVisibilityShared,
|
||||||
|
}
|
||||||
|
|
||||||
|
if createRequest.PowerLevelContentOverride != nil {
|
||||||
|
// Merge powerLevelContentOverride fields by unmarshalling it atop the defaults
|
||||||
|
err = json.Unmarshal(createRequest.PowerLevelContentOverride, &powerLevelContent)
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(ctx).WithError(err).Error("json.Unmarshal for power_level_content_override failed")
|
||||||
|
return nil, spec.BadJSON("malformed power_level_content_override")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var guestsCanJoin bool
|
||||||
|
switch createRequest.StatePreset {
|
||||||
|
case spec.PresetPrivateChat:
|
||||||
|
joinRuleContent.JoinRule = spec.Invite
|
||||||
|
historyVisibilityContent.HistoryVisibility = historyVisibilityShared
|
||||||
|
guestsCanJoin = true
|
||||||
|
case spec.PresetTrustedPrivateChat:
|
||||||
|
joinRuleContent.JoinRule = spec.Invite
|
||||||
|
historyVisibilityContent.HistoryVisibility = historyVisibilityShared
|
||||||
|
for _, invitee := range createRequest.InvitedUsers {
|
||||||
|
powerLevelContent.Users[invitee] = 100
|
||||||
|
}
|
||||||
|
guestsCanJoin = true
|
||||||
|
case spec.PresetPublicChat:
|
||||||
|
joinRuleContent.JoinRule = spec.Public
|
||||||
|
historyVisibilityContent.HistoryVisibility = historyVisibilityShared
|
||||||
|
}
|
||||||
|
|
||||||
|
createEvent := gomatrixserverlib.FledglingEvent{
|
||||||
|
Type: spec.MRoomCreate,
|
||||||
|
Content: createContent,
|
||||||
|
}
|
||||||
|
powerLevelEvent := gomatrixserverlib.FledglingEvent{
|
||||||
|
Type: spec.MRoomPowerLevels,
|
||||||
|
Content: powerLevelContent,
|
||||||
|
}
|
||||||
|
joinRuleEvent := gomatrixserverlib.FledglingEvent{
|
||||||
|
Type: spec.MRoomJoinRules,
|
||||||
|
Content: joinRuleContent,
|
||||||
|
}
|
||||||
|
historyVisibilityEvent := gomatrixserverlib.FledglingEvent{
|
||||||
|
Type: spec.MRoomHistoryVisibility,
|
||||||
|
Content: historyVisibilityContent,
|
||||||
|
}
|
||||||
|
membershipEvent := gomatrixserverlib.FledglingEvent{
|
||||||
|
Type: spec.MRoomMember,
|
||||||
|
StateKey: string(senderID),
|
||||||
|
}
|
||||||
|
|
||||||
|
memberContent := gomatrixserverlib.MemberContent{
|
||||||
|
Membership: spec.Join,
|
||||||
|
DisplayName: createRequest.UserDisplayName,
|
||||||
|
AvatarURL: createRequest.UserAvatarURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we are creating a room with pseudo IDs, create and sign the MXIDMapping
|
||||||
|
if createRequest.RoomVersion == gomatrixserverlib.RoomVersionPseudoIDs {
|
||||||
|
mapping := &gomatrixserverlib.MXIDMapping{
|
||||||
|
UserRoomKey: senderID,
|
||||||
|
UserID: userID.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
identity, idErr := c.Cfg.Matrix.SigningIdentityFor(userID.Domain()) // we MUST use the server signing mxid_mapping
|
||||||
|
if idErr != nil {
|
||||||
|
logrus.WithError(idErr).WithField("domain", userID.Domain()).Error("unable to find signing identity for domain")
|
||||||
|
return nil, spec.InternalServerError{Err: idErr.Error()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign the mapping with the server identity
|
||||||
|
if err = mapping.Sign(identity.ServerName, identity.KeyID, identity.PrivateKey); err != nil {
|
||||||
|
return nil, spec.InternalServerError{Err: err.Error()}
|
||||||
|
}
|
||||||
|
memberContent.MXIDMapping = mapping
|
||||||
|
}
|
||||||
|
membershipEvent.Content = memberContent
|
||||||
|
|
||||||
|
var nameEvent *gomatrixserverlib.FledglingEvent
|
||||||
|
var topicEvent *gomatrixserverlib.FledglingEvent
|
||||||
|
var guestAccessEvent *gomatrixserverlib.FledglingEvent
|
||||||
|
var aliasEvent *gomatrixserverlib.FledglingEvent
|
||||||
|
|
||||||
|
if createRequest.RoomName != "" {
|
||||||
|
nameEvent = &gomatrixserverlib.FledglingEvent{
|
||||||
|
Type: spec.MRoomName,
|
||||||
|
Content: eventutil.NameContent{
|
||||||
|
Name: createRequest.RoomName,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if createRequest.Topic != "" {
|
||||||
|
topicEvent = &gomatrixserverlib.FledglingEvent{
|
||||||
|
Type: spec.MRoomTopic,
|
||||||
|
Content: eventutil.TopicContent{
|
||||||
|
Topic: createRequest.Topic,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if guestsCanJoin {
|
||||||
|
guestAccessEvent = &gomatrixserverlib.FledglingEvent{
|
||||||
|
Type: spec.MRoomGuestAccess,
|
||||||
|
Content: eventutil.GuestAccessContent{
|
||||||
|
GuestAccess: "can_join",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var roomAlias string
|
||||||
|
if createRequest.RoomAliasName != "" {
|
||||||
|
roomAlias = fmt.Sprintf("#%s:%s", createRequest.RoomAliasName, userID.Domain())
|
||||||
|
// check it's free
|
||||||
|
// TODO: This races but is better than nothing
|
||||||
|
hasAliasReq := api.GetRoomIDForAliasRequest{
|
||||||
|
Alias: roomAlias,
|
||||||
|
IncludeAppservices: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
var aliasResp api.GetRoomIDForAliasResponse
|
||||||
|
err = c.RSAPI.GetRoomIDForAlias(ctx, &hasAliasReq, &aliasResp)
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(ctx).WithError(err).Error("aliasAPI.GetRoomIDForAlias failed")
|
||||||
|
return nil, spec.InternalServerError{Err: err.Error()}
|
||||||
|
}
|
||||||
|
if aliasResp.RoomID != "" {
|
||||||
|
return nil, spec.RoomInUse("Room ID already exists.")
|
||||||
|
}
|
||||||
|
|
||||||
|
aliasEvent = &gomatrixserverlib.FledglingEvent{
|
||||||
|
Type: spec.MRoomCanonicalAlias,
|
||||||
|
Content: eventutil.CanonicalAlias{
|
||||||
|
Alias: roomAlias,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var initialStateEvents []gomatrixserverlib.FledglingEvent
|
||||||
|
for i := range createRequest.InitialState {
|
||||||
|
if createRequest.InitialState[i].StateKey != "" {
|
||||||
|
initialStateEvents = append(initialStateEvents, createRequest.InitialState[i])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch createRequest.InitialState[i].Type {
|
||||||
|
case spec.MRoomCreate:
|
||||||
|
continue
|
||||||
|
|
||||||
|
case spec.MRoomPowerLevels:
|
||||||
|
powerLevelEvent = createRequest.InitialState[i]
|
||||||
|
|
||||||
|
case spec.MRoomJoinRules:
|
||||||
|
joinRuleEvent = createRequest.InitialState[i]
|
||||||
|
|
||||||
|
case spec.MRoomHistoryVisibility:
|
||||||
|
historyVisibilityEvent = createRequest.InitialState[i]
|
||||||
|
|
||||||
|
case spec.MRoomGuestAccess:
|
||||||
|
guestAccessEvent = &createRequest.InitialState[i]
|
||||||
|
|
||||||
|
case spec.MRoomName:
|
||||||
|
nameEvent = &createRequest.InitialState[i]
|
||||||
|
|
||||||
|
case spec.MRoomTopic:
|
||||||
|
topicEvent = &createRequest.InitialState[i]
|
||||||
|
|
||||||
|
default:
|
||||||
|
initialStateEvents = append(initialStateEvents, createRequest.InitialState[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// send events into the room in order of:
|
||||||
|
// 1- m.room.create
|
||||||
|
// 2- room creator join member
|
||||||
|
// 3- m.room.power_levels
|
||||||
|
// 4- m.room.join_rules
|
||||||
|
// 5- m.room.history_visibility
|
||||||
|
// 6- m.room.canonical_alias (opt)
|
||||||
|
// 7- m.room.guest_access (opt)
|
||||||
|
// 8- other initial state items
|
||||||
|
// 9- m.room.name (opt)
|
||||||
|
// 10- m.room.topic (opt)
|
||||||
|
// 11- invite events (opt) - with is_direct flag if applicable TODO
|
||||||
|
// 12- 3pid invite events (opt) TODO
|
||||||
|
// This differs from Synapse slightly. Synapse would vary the ordering of 3-7
|
||||||
|
// depending on if those events were in "initial_state" or not. This made it
|
||||||
|
// harder to reason about, hence sticking to a strict static ordering.
|
||||||
|
eventsToMake := []gomatrixserverlib.FledglingEvent{
|
||||||
|
createEvent, membershipEvent, powerLevelEvent, joinRuleEvent, historyVisibilityEvent,
|
||||||
|
}
|
||||||
|
if guestAccessEvent != nil {
|
||||||
|
eventsToMake = append(eventsToMake, *guestAccessEvent)
|
||||||
|
}
|
||||||
|
eventsToMake = append(eventsToMake, initialStateEvents...)
|
||||||
|
if nameEvent != nil {
|
||||||
|
eventsToMake = append(eventsToMake, *nameEvent)
|
||||||
|
}
|
||||||
|
if topicEvent != nil {
|
||||||
|
eventsToMake = append(eventsToMake, *topicEvent)
|
||||||
|
}
|
||||||
|
if aliasEvent != nil {
|
||||||
|
// TODO: bit of a chicken and egg problem here as the alias doesn't exist and cannot until we have made the room.
|
||||||
|
// This means we might fail creating the alias but say the canonical alias is something that doesn't exist.
|
||||||
|
eventsToMake = append(eventsToMake, *aliasEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
var builtEvents []gomatrixserverlib.PDU
|
||||||
|
authEvents := gomatrixserverlib.NewAuthEvents(nil)
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(ctx).WithError(err).Error("rsapi.QuerySenderIDForUser failed")
|
||||||
|
return nil, spec.InternalServerError{Err: err.Error()}
|
||||||
|
}
|
||||||
|
for i, e := range eventsToMake {
|
||||||
|
depth := i + 1 // depth starts at 1
|
||||||
|
|
||||||
|
builder := verImpl.NewEventBuilderFromProtoEvent(&gomatrixserverlib.ProtoEvent{
|
||||||
|
SenderID: string(senderID),
|
||||||
|
RoomID: roomID.String(),
|
||||||
|
Type: e.Type,
|
||||||
|
StateKey: &e.StateKey,
|
||||||
|
Depth: int64(depth),
|
||||||
|
})
|
||||||
|
err = builder.SetContent(e.Content)
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(ctx).WithError(err).Error("builder.SetContent failed")
|
||||||
|
return nil, spec.InternalServerError{Err: err.Error()}
|
||||||
|
}
|
||||||
|
if i > 0 {
|
||||||
|
builder.PrevEvents = []string{builtEvents[i-1].EventID()}
|
||||||
|
}
|
||||||
|
var ev gomatrixserverlib.PDU
|
||||||
|
if err = builder.AddAuthEvents(&authEvents); err != nil {
|
||||||
|
util.GetLogger(ctx).WithError(err).Error("AddAuthEvents failed")
|
||||||
|
return nil, spec.InternalServerError{Err: err.Error()}
|
||||||
|
}
|
||||||
|
|
||||||
|
ev, err = builder.BuildWithoutSigning(createRequest.EventTime, userID.Domain())
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(ctx).WithError(err).Error("buildEvent failed")
|
||||||
|
return nil, spec.InternalServerError{Err: err.Error()}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = gomatrixserverlib.Allowed(ev, &authEvents, func(roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) {
|
||||||
|
return c.RSAPI.QueryUserIDForSender(ctx, roomID, senderID)
|
||||||
|
}); err != nil {
|
||||||
|
util.GetLogger(ctx).WithError(err).Error("gomatrixserverlib.Allowed failed")
|
||||||
|
return nil, spec.InternalServerError{Err: err.Error()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the event to the list of auth events
|
||||||
|
builtEvents = append(builtEvents, ev)
|
||||||
|
err = authEvents.AddEvent(ev)
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(ctx).WithError(err).Error("authEvents.AddEvent failed")
|
||||||
|
return nil, spec.InternalServerError{Err: err.Error()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(#269): Reserve room alias while we create the room. This stops us
|
||||||
|
// from creating the room but still failing due to the alias having already
|
||||||
|
// been taken.
|
||||||
|
if roomAlias != "" {
|
||||||
|
aliasAlreadyExists, aliasErr := c.RSAPI.SetRoomAlias(ctx, senderID, roomID, roomAlias)
|
||||||
|
if aliasErr != nil {
|
||||||
|
util.GetLogger(ctx).WithError(aliasErr).Error("aliasAPI.SetRoomAlias failed")
|
||||||
|
return nil, spec.InternalServerError{Err: aliasErr.Error()}
|
||||||
|
}
|
||||||
|
|
||||||
|
if aliasAlreadyExists {
|
||||||
|
return nil, spec.RoomInUse("Room alias already exists.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: cryptoIDs - this shouldn't really be done until the client calls /sendPDUs with these events
|
||||||
|
// But that would require the visibility setting also being passed along
|
||||||
|
if createRequest.Visibility == spec.Public {
|
||||||
|
// expose this room in the published room list
|
||||||
|
if err = c.RSAPI.PerformPublish(ctx, &api.PerformPublishRequest{
|
||||||
|
RoomID: roomID.String(),
|
||||||
|
Visibility: spec.Public,
|
||||||
|
}); err != nil {
|
||||||
|
util.GetLogger(ctx).WithError(err).Error("failed to publish room")
|
||||||
|
return nil, spec.InternalServerError{Err: err.Error()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is a direct message then we should invite the participants.
|
||||||
|
//if len(createRequest.InvitedUsers) > 0 {
|
||||||
|
// Build some stripped state for the invite.
|
||||||
|
//var globalStrippedState []gomatrixserverlib.InviteStrippedState
|
||||||
|
//for _, event := range builtEvents {
|
||||||
|
// // Chosen events from the spec:
|
||||||
|
// // https://spec.matrix.org/v1.3/client-server-api/#stripped-state
|
||||||
|
// switch event.Type() {
|
||||||
|
// case spec.MRoomCreate:
|
||||||
|
// fallthrough
|
||||||
|
// case spec.MRoomName:
|
||||||
|
// fallthrough
|
||||||
|
// case spec.MRoomAvatar:
|
||||||
|
// fallthrough
|
||||||
|
// case spec.MRoomTopic:
|
||||||
|
// fallthrough
|
||||||
|
// case spec.MRoomCanonicalAlias:
|
||||||
|
// fallthrough
|
||||||
|
// case spec.MRoomEncryption:
|
||||||
|
// fallthrough
|
||||||
|
// case spec.MRoomMember:
|
||||||
|
// fallthrough
|
||||||
|
// case spec.MRoomJoinRules:
|
||||||
|
// ev := event
|
||||||
|
// globalStrippedState = append(
|
||||||
|
// globalStrippedState,
|
||||||
|
// gomatrixserverlib.NewInviteStrippedState(ev),
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
// Process the invites.
|
||||||
|
//for _, invitee := range createRequest.InvitedUsers {
|
||||||
|
//inviteeUserID, userIDErr := spec.NewUserID(invitee, true)
|
||||||
|
//if userIDErr != nil {
|
||||||
|
// util.GetLogger(ctx).WithError(userIDErr).Error("invalid UserID")
|
||||||
|
// return nil, spec.InternalServerError{}
|
||||||
|
//}
|
||||||
|
|
||||||
|
// TODO: cryptoIDs - these shouldn't be here
|
||||||
|
// instead we should return proto invite events?
|
||||||
|
//err = c.RSAPI.PerformInvite(ctx, &api.PerformInviteRequest{
|
||||||
|
// InviteInput: api.InviteInput{
|
||||||
|
// RoomID: roomID,
|
||||||
|
// Inviter: userID,
|
||||||
|
// Invitee: *inviteeUserID,
|
||||||
|
// DisplayName: createRequest.UserDisplayName,
|
||||||
|
// AvatarURL: createRequest.UserAvatarURL,
|
||||||
|
// Reason: "",
|
||||||
|
// IsDirect: createRequest.IsDirect,
|
||||||
|
// KeyID: createRequest.KeyID,
|
||||||
|
// PrivateKey: createRequest.PrivateKey,
|
||||||
|
// EventTime: createRequest.EventTime,
|
||||||
|
// },
|
||||||
|
// InviteRoomState: globalStrippedState,
|
||||||
|
// SendAsServer: string(userID.Domain()),
|
||||||
|
//})
|
||||||
|
//switch e := err.(type) {
|
||||||
|
//case api.ErrInvalidID:
|
||||||
|
// return nil, spec.Unknown(e.Error())
|
||||||
|
//case api.ErrNotAllowed:
|
||||||
|
// return nil, spec.Forbidden(e.Error())
|
||||||
|
//case nil:
|
||||||
|
//default:
|
||||||
|
// util.GetLogger(ctx).WithError(err).Error("PerformInvite failed")
|
||||||
|
// sentry.CaptureException(err)
|
||||||
|
// return nil, spec.InternalServerError{}
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
|
||||||
|
return builtEvents, nil
|
||||||
|
}
|
||||||
|
|
||||||
// PerformCreateRoom handles all the steps necessary to create a new room.
|
// PerformCreateRoom handles all the steps necessary to create a new room.
|
||||||
// nolint: gocyclo
|
// nolint: gocyclo
|
||||||
func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roomID spec.RoomID, createRequest *api.PerformCreateRoomRequest) (string, *util.JSONResponse) {
|
func (c *Creator) PerformCreateRoom(ctx context.Context, userID spec.UserID, roomID spec.RoomID, createRequest *api.PerformCreateRoomRequest) (string, *util.JSONResponse) {
|
||||||
|
|
|
@ -16,7 +16,8 @@ type RoomServer struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RoomServer) Defaults(opts DefaultOpts) {
|
func (c *RoomServer) Defaults(opts DefaultOpts) {
|
||||||
c.DefaultRoomVersion = gomatrixserverlib.RoomVersionV10
|
//c.DefaultRoomVersion = gomatrixserverlib.RoomVersionV10
|
||||||
|
c.DefaultRoomVersion = gomatrixserverlib.RoomVersionPseudoIDs
|
||||||
if opts.Generate {
|
if opts.Generate {
|
||||||
if !opts.SingleDatabase {
|
if !opts.SingleDatabase {
|
||||||
c.Database.ConnectionString = "file:roomserver.db"
|
c.Database.ConnectionString = "file:roomserver.db"
|
||||||
|
|
Loading…
Reference in a new issue