custompuppet.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  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.Logger = br.AS.Log.Sub(mxid.String())
  41. client.Client = br.AS.HTTPClient
  42. client.DefaultHTTPRetries = br.AS.DefaultHTTPRetries
  43. return client, nil
  44. }
  45. func (puppet *Puppet) clearCustomMXID() {
  46. puppet.CustomMXID = ""
  47. puppet.AccessToken = ""
  48. puppet.customIntent = nil
  49. puppet.customUser = nil
  50. }
  51. func (puppet *Puppet) newCustomIntent() (*appservice.IntentAPI, error) {
  52. if puppet.CustomMXID == "" {
  53. return nil, ErrNoCustomMXID
  54. }
  55. client, err := puppet.bridge.newDoublePuppetClient(puppet.CustomMXID, puppet.AccessToken)
  56. if err != nil {
  57. return nil, err
  58. }
  59. ia := puppet.bridge.AS.NewIntentAPI("custom")
  60. ia.Client = client
  61. ia.Localpart, _, _ = puppet.CustomMXID.Parse()
  62. ia.UserID = puppet.CustomMXID
  63. ia.IsCustomPuppet = true
  64. return ia, nil
  65. }
  66. func (puppet *Puppet) StartCustomMXID(reloginOnFail bool) error {
  67. if puppet.CustomMXID == "" {
  68. puppet.clearCustomMXID()
  69. return nil
  70. }
  71. intent, err := puppet.newCustomIntent()
  72. if err != nil {
  73. puppet.clearCustomMXID()
  74. return err
  75. }
  76. resp, err := intent.Whoami()
  77. if err != nil {
  78. if !reloginOnFail || (errors.Is(err, mautrix.MUnknownToken) && !puppet.tryRelogin(err, "initializing double puppeting")) {
  79. puppet.clearCustomMXID()
  80. return err
  81. }
  82. intent.AccessToken = puppet.AccessToken
  83. } else if resp.UserID != puppet.CustomMXID {
  84. puppet.clearCustomMXID()
  85. return ErrMismatchingMXID
  86. }
  87. puppet.customIntent = intent
  88. puppet.customUser = puppet.bridge.GetUserByMXID(puppet.CustomMXID)
  89. return nil
  90. }
  91. func (puppet *Puppet) tryRelogin(cause error, action string) bool {
  92. if !puppet.bridge.Config.CanAutoDoublePuppet(puppet.CustomMXID) {
  93. return false
  94. }
  95. puppet.log.Debugfln("Trying to relogin after '%v' while %s", cause, action)
  96. accessToken, err := puppet.loginWithSharedSecret(puppet.CustomMXID)
  97. if err != nil {
  98. puppet.log.Errorfln("Failed to relogin after '%v' while %s: %v", cause, action, err)
  99. return false
  100. }
  101. puppet.log.Infofln("Successfully relogined after '%v' while %s", cause, action)
  102. puppet.AccessToken = accessToken
  103. puppet.Update()
  104. return true
  105. }
  106. func (puppet *Puppet) loginWithSharedSecret(mxid id.UserID) (string, error) {
  107. _, homeserver, _ := mxid.Parse()
  108. puppet.log.Debugfln("Logging into %s with shared secret", mxid)
  109. loginSecret := puppet.bridge.Config.Bridge.LoginSharedSecretMap[homeserver]
  110. client, err := puppet.bridge.newDoublePuppetClient(mxid, "")
  111. if err != nil {
  112. return "", fmt.Errorf("failed to create mautrix client to log in: %v", err)
  113. }
  114. req := mautrix.ReqLogin{
  115. Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: string(mxid)},
  116. DeviceID: "Discord Bridge",
  117. InitialDeviceDisplayName: "Discord Bridge",
  118. }
  119. if loginSecret == "appservice" {
  120. client.AccessToken = puppet.bridge.AS.Registration.AppToken
  121. req.Type = mautrix.AuthTypeAppservice
  122. } else {
  123. mac := hmac.New(sha512.New, []byte(loginSecret))
  124. mac.Write([]byte(mxid))
  125. req.Password = hex.EncodeToString(mac.Sum(nil))
  126. req.Type = mautrix.AuthTypePassword
  127. }
  128. resp, err := client.Login(&req)
  129. if err != nil {
  130. return "", err
  131. }
  132. return resp.AccessToken, nil
  133. }
  134. func (puppet *Puppet) SwitchCustomMXID(accessToken string, mxid id.UserID) error {
  135. prevCustomMXID := puppet.CustomMXID
  136. puppet.CustomMXID = mxid
  137. puppet.AccessToken = accessToken
  138. err := puppet.StartCustomMXID(false)
  139. if err != nil {
  140. return err
  141. }
  142. if prevCustomMXID != "" {
  143. delete(puppet.bridge.puppetsByCustomMXID, prevCustomMXID)
  144. }
  145. if puppet.CustomMXID != "" {
  146. puppet.bridge.puppetsByCustomMXID[puppet.CustomMXID] = puppet
  147. }
  148. puppet.bridge.AS.StateStore.MarkRegistered(puppet.CustomMXID)
  149. puppet.Update()
  150. // TODO leave rooms with default puppet
  151. return nil
  152. }