custompuppet.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. package main
  2. import (
  3. "crypto/hmac"
  4. "crypto/sha512"
  5. "encoding/hex"
  6. "errors"
  7. "fmt"
  8. "maunium.net/go/mautrix"
  9. "maunium.net/go/mautrix/appservice"
  10. "maunium.net/go/mautrix/id"
  11. )
  12. var (
  13. ErrNoCustomMXID = errors.New("no custom mxid set")
  14. ErrMismatchingMXID = errors.New("whoami result does not match custom mxid")
  15. )
  16. func (br *DiscordBridge) newDoublePuppetClient(mxid id.UserID, accessToken string) (*mautrix.Client, error) {
  17. _, homeserver, err := mxid.Parse()
  18. if err != nil {
  19. return nil, err
  20. }
  21. homeserverURL, found := br.Config.Bridge.DoublePuppetServerMap[homeserver]
  22. if !found {
  23. if homeserver == br.AS.HomeserverDomain {
  24. homeserverURL = br.AS.HomeserverURL
  25. } else if br.Config.Bridge.DoublePuppetAllowDiscovery {
  26. resp, err := mautrix.DiscoverClientAPI(homeserver)
  27. if err != nil {
  28. return nil, fmt.Errorf("failed to find homeserver URL for %s: %v", homeserver, err)
  29. }
  30. homeserverURL = resp.Homeserver.BaseURL
  31. br.Log.Debugfln("Discovered URL %s for %s to enable double puppeting for %s", homeserverURL, homeserver, mxid)
  32. } else {
  33. return nil, fmt.Errorf("double puppeting from %s is not allowed", homeserver)
  34. }
  35. }
  36. client, err := mautrix.NewClient(homeserverURL, mxid, accessToken)
  37. if err != nil {
  38. return nil, err
  39. }
  40. client.Log = br.AS.Log.With().Str("as_user_id", mxid.String()).Logger()
  41. client.StateStore = br.AS.StateStore
  42. client.Client = br.AS.HTTPClient
  43. client.DefaultHTTPRetries = br.AS.DefaultHTTPRetries
  44. return client, nil
  45. }
  46. func (puppet *Puppet) clearCustomMXID() {
  47. puppet.CustomMXID = ""
  48. puppet.AccessToken = ""
  49. puppet.customIntent = nil
  50. puppet.customUser = nil
  51. }
  52. func (puppet *Puppet) newCustomIntent() (*appservice.IntentAPI, error) {
  53. if puppet.CustomMXID == "" {
  54. return nil, ErrNoCustomMXID
  55. }
  56. client, err := puppet.bridge.newDoublePuppetClient(puppet.CustomMXID, puppet.AccessToken)
  57. if err != nil {
  58. return nil, err
  59. }
  60. ia := puppet.bridge.AS.NewIntentAPI("custom")
  61. ia.Client = client
  62. ia.Localpart, _, _ = puppet.CustomMXID.Parse()
  63. ia.UserID = puppet.CustomMXID
  64. ia.IsCustomPuppet = true
  65. return ia, nil
  66. }
  67. func (puppet *Puppet) StartCustomMXID(reloginOnFail bool) error {
  68. if puppet.CustomMXID == "" {
  69. puppet.clearCustomMXID()
  70. return nil
  71. }
  72. intent, err := puppet.newCustomIntent()
  73. if err != nil {
  74. puppet.clearCustomMXID()
  75. return err
  76. }
  77. resp, err := intent.Whoami()
  78. if err != nil {
  79. if !reloginOnFail || (errors.Is(err, mautrix.MUnknownToken) && !puppet.tryRelogin(err, "initializing double puppeting")) {
  80. puppet.clearCustomMXID()
  81. return err
  82. }
  83. intent.AccessToken = puppet.AccessToken
  84. } else if resp.UserID != puppet.CustomMXID {
  85. puppet.clearCustomMXID()
  86. return ErrMismatchingMXID
  87. }
  88. puppet.customIntent = intent
  89. puppet.customUser = puppet.bridge.GetUserByMXID(puppet.CustomMXID)
  90. return nil
  91. }
  92. func (puppet *Puppet) tryRelogin(cause error, action string) bool {
  93. if !puppet.bridge.Config.CanAutoDoublePuppet(puppet.CustomMXID) {
  94. return false
  95. }
  96. puppet.log.Debugfln("Trying to relogin after '%v' while %s", cause, action)
  97. accessToken, err := puppet.loginWithSharedSecret(puppet.CustomMXID)
  98. if err != nil {
  99. puppet.log.Errorfln("Failed to relogin after '%v' while %s: %v", cause, action, err)
  100. return false
  101. }
  102. puppet.log.Infofln("Successfully relogined after '%v' while %s", cause, action)
  103. puppet.AccessToken = accessToken
  104. puppet.Update()
  105. return true
  106. }
  107. func (puppet *Puppet) loginWithSharedSecret(mxid id.UserID) (string, error) {
  108. _, homeserver, _ := mxid.Parse()
  109. puppet.log.Debugfln("Logging into %s with shared secret", mxid)
  110. loginSecret := puppet.bridge.Config.Bridge.LoginSharedSecretMap[homeserver]
  111. client, err := puppet.bridge.newDoublePuppetClient(mxid, "")
  112. if err != nil {
  113. return "", fmt.Errorf("failed to create mautrix client to log in: %v", err)
  114. }
  115. req := mautrix.ReqLogin{
  116. Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: string(mxid)},
  117. DeviceID: "Discord Bridge",
  118. InitialDeviceDisplayName: "Discord Bridge",
  119. }
  120. if loginSecret == "appservice" {
  121. client.AccessToken = puppet.bridge.AS.Registration.AppToken
  122. req.Type = mautrix.AuthTypeAppservice
  123. } else {
  124. mac := hmac.New(sha512.New, []byte(loginSecret))
  125. mac.Write([]byte(mxid))
  126. req.Password = hex.EncodeToString(mac.Sum(nil))
  127. req.Type = mautrix.AuthTypePassword
  128. }
  129. resp, err := client.Login(&req)
  130. if err != nil {
  131. return "", err
  132. }
  133. return resp.AccessToken, nil
  134. }
  135. func (puppet *Puppet) SwitchCustomMXID(accessToken string, mxid id.UserID) error {
  136. prevCustomMXID := puppet.CustomMXID
  137. puppet.CustomMXID = mxid
  138. puppet.AccessToken = accessToken
  139. err := puppet.StartCustomMXID(false)
  140. if err != nil {
  141. return err
  142. }
  143. if prevCustomMXID != "" {
  144. delete(puppet.bridge.puppetsByCustomMXID, prevCustomMXID)
  145. }
  146. if puppet.CustomMXID != "" {
  147. puppet.bridge.puppetsByCustomMXID[puppet.CustomMXID] = puppet
  148. }
  149. puppet.bridge.AS.StateStore.MarkRegistered(puppet.CustomMXID)
  150. puppet.Update()
  151. // TODO leave rooms with default puppet
  152. return nil
  153. }