diff --git a/appservice/registration.go b/appservice/registration.go index c0b62124..16c87fb9 100644 --- a/appservice/registration.go +++ b/appservice/registration.go @@ -29,6 +29,7 @@ type Registration struct { SoruEphemeralEvents bool `yaml:"de.sorunome.msc2409.push_ephemeral,omitempty" json:"de.sorunome.msc2409.push_ephemeral,omitempty"` EphemeralEvents bool `yaml:"push_ephemeral,omitempty" json:"push_ephemeral,omitempty"` MSC3202 bool `yaml:"org.matrix.msc3202,omitempty" json:"org.matrix.msc3202,omitempty"` + MSC4190 bool `yaml:"io.element.msc4190,omitempty" json:"io.element.msc4190,omitempty"` } // CreateRegistration creates a Registration with random appservice and homeserver tokens. diff --git a/bridge/crypto.go b/bridge/crypto.go index f0b90056..f2240606 100644 --- a/bridge/crypto.go +++ b/bridge/crypto.go @@ -232,6 +232,16 @@ func (helper *CryptoHelper) loginBot(ctx context.Context) (*mautrix.Client, bool } else if !flows.HasFlow(mautrix.AuthTypeAppservice) { return nil, deviceID != "", fmt.Errorf("homeserver does not support appservice login") } + initialDeviceDisplayName := fmt.Sprintf("%s bridge", helper.bridge.ProtocolName) + if helper.bridge.AS.Registration.MSC4190 { + err = client.CreateDeviceMSC4190(ctx, deviceID, initialDeviceDisplayName) + if err != nil { + return nil, deviceID != "", fmt.Errorf("failed to log in as bridge bot: %w", err) + } + helper.store.DeviceID = client.DeviceID + return client, deviceID != "", nil + } + resp, err := client.Login(ctx, &mautrix.ReqLogin{ Type: mautrix.AuthTypeAppservice, Identifier: mautrix.UserIdentifier{ @@ -241,7 +251,7 @@ func (helper *CryptoHelper) loginBot(ctx context.Context) (*mautrix.Client, bool DeviceID: deviceID, StoreCredentials: true, - InitialDeviceDisplayName: fmt.Sprintf("%s bridge", helper.bridge.ProtocolName), + InitialDeviceDisplayName: initialDeviceDisplayName, }) if err != nil { return nil, deviceID != "", fmt.Errorf("failed to log in as bridge bot: %w", err) diff --git a/bridgev2/matrix/crypto.go b/bridgev2/matrix/crypto.go index 04654ff5..22c122fe 100644 --- a/bridgev2/matrix/crypto.go +++ b/bridgev2/matrix/crypto.go @@ -247,17 +247,25 @@ func (helper *CryptoHelper) loginBot(ctx context.Context) (*mautrix.Client, bool } else if !flows.HasFlow(mautrix.AuthTypeAppservice) { return nil, deviceID != "", fmt.Errorf("homeserver does not support appservice login") } + initialDeviceDisplayName := fmt.Sprintf("%s bridge", helper.bridge.Bridge.Network.GetName().DisplayName) + if helper.bridge.AS.Registration.MSC4190 { + err = client.CreateDeviceMSC4190(ctx, deviceID, initialDeviceDisplayName) + if err != nil { + return nil, deviceID != "", fmt.Errorf("failed to log in as bridge bot: %w", err) + } + helper.store.DeviceID = client.DeviceID + return client, deviceID != "", nil + } + resp, err := client.Login(ctx, &mautrix.ReqLogin{ Type: mautrix.AuthTypeAppservice, Identifier: mautrix.UserIdentifier{ Type: mautrix.IdentifierTypeUser, User: string(helper.bridge.AS.BotMXID()), }, - DeviceID: deviceID, - StoreCredentials: true, - - // TODO find proper bridge name - InitialDeviceDisplayName: "Megabridge", // fmt.Sprintf("%s bridge", helper.bridge.ProtocolName), + DeviceID: deviceID, + StoreCredentials: true, + InitialDeviceDisplayName: initialDeviceDisplayName, }) if err != nil { return nil, deviceID != "", fmt.Errorf("failed to log in as bridge bot: %w", err) diff --git a/client.go b/client.go index b237612b..a19935da 100644 --- a/client.go +++ b/client.go @@ -21,6 +21,7 @@ import ( "github.com/rs/zerolog" "go.mau.fi/util/ptr" + "go.mau.fi/util/random" "go.mau.fi/util/retryafter" "golang.org/x/exp/maps" @@ -897,6 +898,27 @@ func (cli *Client) Login(ctx context.Context, req *ReqLogin) (resp *RespLogin, e return } +// Create a Device for a user of the homeserver using appservice interface defined in MSC4190 +func (cli *Client) CreateDeviceMSC4190(ctx context.Context, deviceID id.DeviceID, initialDispalyName string) (err error) { + if len(deviceID) == 0 { + deviceID = id.DeviceID(random.String(10)) + } + if !cli.SetAppServiceUserID { + return fmt.Errorf("CreateDeviceMSC4190 requires SetAppServiceUserID to be enabled") + } + if cli.AccessToken == "" { + return fmt.Errorf("CreateDeviceMSC4190 requires The AS AccessToken token to be set as the client AccessToken") + } + _, err = cli.MakeRequest(ctx, http.MethodPost, cli.BuildClientURL("v3", "devices", deviceID), ReqPutDevice{ + DisplayName: initialDispalyName, + }, nil) + if err != nil { + return err + } + cli.DeviceID = deviceID + return nil +} + // Logout the current user. See https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3logout // This does not clear the credentials from the client instance. See ClearCredentials() instead. func (cli *Client) Logout(ctx context.Context) (resp *RespLogout, err error) { diff --git a/crypto/cryptohelper/cryptohelper.go b/crypto/cryptohelper/cryptohelper.go index 0b3fbeaa..225e0f01 100644 --- a/crypto/cryptohelper/cryptohelper.go +++ b/crypto/cryptohelper/cryptohelper.go @@ -37,6 +37,7 @@ type CryptoHelper struct { DecryptErrorCallback func(*event.Event, error) LoginAs *mautrix.ReqLogin + MSC4190 bool ASEventProcessor crypto.ASEventProcessor CustomPostDecrypt func(context.Context, *event.Event) @@ -152,7 +153,20 @@ func (helper *CryptoHelper) Init(ctx context.Context) error { return fmt.Errorf("failed to find existing device ID: %w", err) } if helper.LoginAs != nil && helper.LoginAs.Type == mautrix.AuthTypeAppservice && helper.client.SetAppServiceDeviceID { - if storedDeviceID == "" { + if storedDeviceID != "" { + helper.log.Debug(). + Str("username", helper.LoginAs.Identifier.User). + Stringer("device_id", storedDeviceID). + Msg("Using existing device") + rawCryptoStore.DeviceID = storedDeviceID + helper.client.DeviceID = storedDeviceID + } else if helper.MSC4190 { + helper.log.Debug(). + Str("username", helper.LoginAs.Identifier.User). + Msg("Creating device with MSC4190") + helper.client.CreateDeviceMSC4190(ctx, storedDeviceID, "") + rawCryptoStore.DeviceID = helper.client.DeviceID + } else { helper.log.Debug(). Str("username", helper.LoginAs.Identifier.User). Msg("Logging in with appservice") @@ -163,13 +177,6 @@ func (helper *CryptoHelper) Init(ctx context.Context) error { } rawCryptoStore.DeviceID = resp.DeviceID helper.client.DeviceID = resp.DeviceID - } else { - helper.log.Debug(). - Str("username", helper.LoginAs.Identifier.User). - Stringer("device_id", storedDeviceID). - Msg("Using existing device") - rawCryptoStore.DeviceID = storedDeviceID - helper.client.DeviceID = storedDeviceID } } else if helper.LoginAs != nil { if storedDeviceID != "" { diff --git a/requests.go b/requests.go index a6b0ea8b..78e8a36b 100644 --- a/requests.go +++ b/requests.go @@ -90,6 +90,9 @@ type ReqLogin struct { // Whether or not the returned .well-known data should update the homeserver URL in the Client StoreHomeserverURL bool `json:"-"` } +type ReqPutDevice struct { + DisplayName string `json:"display_name,omitempty"` +} type ReqUIAuthFallback struct { Session string `json:"session"`