diff --git a/pkg/connector/login.go b/pkg/connector/login.go index 3a596fa..ff643b4 100644 --- a/pkg/connector/login.go +++ b/pkg/connector/login.go @@ -150,11 +150,6 @@ func (m *MetaCookieLogin) SubmitCookies(ctx context.Context, strCookies map[stri return nil, ErrLoginMissingCookies.AppendMessage(": %v", missingCookies) } - err := c.GeneratePushKeys() - if err != nil { - return nil, fmt.Errorf("failed to generate push keys: %w", err) - } - log := m.User.Log.With().Str("component", "messagix").Logger() client := messagix.NewClient(c, log) diff --git a/pkg/connector/push.go b/pkg/connector/push.go new file mode 100644 index 0000000..cdafee8 --- /dev/null +++ b/pkg/connector/push.go @@ -0,0 +1,60 @@ +// mautrix-meta - A Matrix-Facebook Messenger and Instagram DM puppeting bridge. +// Copyright (C) 2024 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package connector + +import ( + "context" + "fmt" + + "maunium.net/go/mautrix/bridgev2" + + "go.mau.fi/mautrix-meta/pkg/messagix" + "go.mau.fi/mautrix-meta/pkg/metaid" +) + +var _ bridgev2.PushableNetworkAPI = (*MetaClient)(nil) + +var pushCfg = &bridgev2.PushConfig{ + Web: &bridgev2.WebPushConfig{VapidKey: "BIBn3E_rWTci8Xn6P9Xj3btShT85Wdtne0LtwNUyRQ5XjFNkuTq9j4MPAVLvAFhXrUU1A9UxyxBA7YIOjqDIDHI"}, +} + +func (m *MetaClient) GetPushConfigs() *bridgev2.PushConfig { + return pushCfg +} + +func (m *MetaClient) RegisterPushNotifications(ctx context.Context, pushType bridgev2.PushType, token string) error { + if pushType != bridgev2.PushTypeWeb { + return fmt.Errorf("unsupported push type %s", pushType) + } + meta := m.UserLogin.Metadata.(*metaid.UserLoginMetadata) + if meta.PushKeys == nil { + meta.GeneratePushKeys() + err := m.UserLogin.Save(ctx) + if err != nil { + return fmt.Errorf("failed to save push key: %w", err) + } + } + keys := messagix.PushKeys{ + P256DH: meta.PushKeys.P256DH, + Auth: meta.PushKeys.Auth, + } + if m.Client.Platform.IsMessenger() { + return m.Client.Facebook.RegisterPushNotifications(token, keys) + } else { + return m.Client.Instagram.RegisterPushNotifications(token, keys) + } +} diff --git a/pkg/messagix/cookies/cookies.go b/pkg/messagix/cookies/cookies.go index 3972f15..8f9c24d 100644 --- a/pkg/messagix/cookies/cookies.go +++ b/pkg/messagix/cookies/cookies.go @@ -50,7 +50,6 @@ type Cookies struct { values map[MetaCookieName]string lock sync.RWMutex - PushKeys *PushKeys IGWWWClaim string } @@ -91,17 +90,6 @@ func (c *Cookies) GetViewports() (width, height string) { return pxs[0], pxs[1] } -func (c *Cookies) GeneratePushKeys() error { - c.lock.RLock() - defer c.lock.RUnlock() - pushKeys, err := generatePushKeys() - if err != nil { - return err - } - c.PushKeys = pushKeys - return nil -} - func (c *Cookies) GetMissingCookieNames() []MetaCookieName { c.lock.RLock() defer c.lock.RUnlock() diff --git a/pkg/messagix/cookies/pushkeys.go b/pkg/messagix/cookies/pushkeys.go deleted file mode 100644 index 932caf0..0000000 --- a/pkg/messagix/cookies/pushkeys.go +++ /dev/null @@ -1,47 +0,0 @@ -package cookies - -import ( - "crypto/ecdh" - "crypto/rand" - "encoding/base64" -) - -type PushKeysPublic struct { - P256dh string `json:"p256dh"` - Auth string `json:"auth"` -} - -type PushKeys struct { - Public PushKeysPublic - Private string -} - -func generatePushKeys() (*PushKeys, error) { - curve := ecdh.P256() - privateKey, err := curve.GenerateKey(rand.Reader) - if err != nil { - return nil, err - } - - publicKey := privateKey.Public().(*ecdh.PublicKey) - encodedPublicKey := base64.URLEncoding.EncodeToString(publicKey.Bytes()) - - privateKeyBytes := privateKey.Bytes() - encodedPrivateKey := base64.URLEncoding.EncodeToString(privateKeyBytes) - - auth := make([]byte, 16) - _, err = rand.Read(auth) - if err != nil { - return nil, err - } - encodedAuth := base64.URLEncoding.EncodeToString(auth) - - pushKeys := &PushKeys{ - Public: PushKeysPublic{ - P256dh: encodedPublicKey, - Auth: encodedAuth, - }, - Private: encodedPrivateKey, - } - return pushKeys, nil -} diff --git a/pkg/messagix/facebook.go b/pkg/messagix/facebook.go index b03eaa1..0339cfe 100644 --- a/pkg/messagix/facebook.go +++ b/pkg/messagix/facebook.go @@ -1,12 +1,12 @@ package messagix import ( + "bytes" "encoding/json" "errors" "fmt" "reflect" "strconv" - "strings" "github.com/google/go-querystring/query" @@ -76,9 +76,14 @@ func (fb *FacebookMethods) Login(identifier, password string) (*cookies.Cookies, return fb.client.cookies, nil } -func (fb *FacebookMethods) RegisterPushNotifications(endpoint string) error { +type PushKeys struct { + P256DH []byte `json:"p256dh"` + Auth []byte `json:"auth"` +} + +func (fb *FacebookMethods) RegisterPushNotifications(endpoint string, keys PushKeys) error { c := fb.client - jsonKeys, err := json.Marshal(c.cookies.PushKeys.Public) + jsonKeys, err := json.Marshal(&keys) if err != nil { c.Logger.Err(err).Msg("failed to encode push keys to json") return err @@ -111,14 +116,12 @@ func (fb *FacebookMethods) RegisterPushNotifications(endpoint string) error { return fmt.Errorf("bad status code: %d", resp.StatusCode) } - bodyStr := string(body) - jsonStr := strings.TrimPrefix(bodyStr, "for (;;);") - jsonBytes := []byte(jsonStr) + body = bytes.TrimPrefix(body, antiJSPrefix) var r pushNotificationsResponse - err = json.Unmarshal(jsonBytes, &r) + err = json.Unmarshal(body, &r) if err != nil { - c.Logger.Err(err).Str("body", bodyStr).Msg("failed to unmarshal response") + c.Logger.Err(err).Bytes("body", body).Msg("failed to unmarshal response") return err } diff --git a/pkg/messagix/instagram.go b/pkg/messagix/instagram.go index 41a5636..171965a 100644 --- a/pkg/messagix/instagram.go +++ b/pkg/messagix/instagram.go @@ -182,10 +182,10 @@ func (ig *InstagramMethods) FetchHighlights(highlightIds []string) (*responses.R return ig.FetchReel(highlightIds, "") } -func (ig *InstagramMethods) RegisterPushNotifications(endpoint string) error { +func (ig *InstagramMethods) RegisterPushNotifications(endpoint string, keys PushKeys) error { c := ig.client - jsonKeys, err := json.Marshal(c.cookies.PushKeys.Public) + jsonKeys, err := json.Marshal(&keys) if err != nil { c.Logger.Err(err).Msg("failed to encode push keys to json") return err diff --git a/pkg/metaid/dbmeta.go b/pkg/metaid/dbmeta.go index 8da9f11..caec6a5 100644 --- a/pkg/metaid/dbmeta.go +++ b/pkg/metaid/dbmeta.go @@ -1,8 +1,12 @@ package metaid import ( + "crypto/ecdh" + "crypto/rand" "sync/atomic" + "go.mau.fi/util/exerrors" + "go.mau.fi/util/random" waTypes "go.mau.fi/whatsmeow/types" "maunium.net/go/mautrix/bridgev2/networkid" @@ -23,6 +27,22 @@ type UserLoginMetadata struct { Platform types.Platform `json:"platform"` Cookies *cookies.Cookies `json:"cookies"` WADeviceID uint16 `json:"wa_device_id,omitempty"` + PushKeys *PushKeys `json:"push_keys,omitempty"` +} + +type PushKeys struct { + P256DH []byte `json:"p256dh"` + Auth []byte `json:"auth"` + Private []byte `json:"private"` +} + +func (m *UserLoginMetadata) GeneratePushKeys() { + privateKey := exerrors.Must(ecdh.P256().GenerateKey(rand.Reader)) + m.PushKeys = &PushKeys{ + P256DH: privateKey.Public().(*ecdh.PublicKey).Bytes(), + Auth: random.Bytes(16), + Private: privateKey.Bytes(), + } } type PortalMetadata struct {