diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index c0add220..deaa1f1d 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -15,7 +15,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v6
with:
- go-version: "1.26"
+ go-version: "1.25"
cache: true
- name: Install libolm
@@ -35,8 +35,8 @@ jobs:
strategy:
fail-fast: false
matrix:
- go-version: ["1.25", "1.26"]
- name: Build (${{ matrix.go-version == '1.26' && 'latest' || 'old' }}, libolm)
+ go-version: ["1.24", "1.25"]
+ name: Build (${{ matrix.go-version == '1.25' && 'latest' || 'old' }}, libolm)
steps:
- uses: actions/checkout@v6
@@ -62,6 +62,7 @@ jobs:
run: go test -json -v ./... 2>&1 | gotestfmt
- name: Test (jsonv2)
+ if: matrix.go-version == '1.25'
env:
GOEXPERIMENT: jsonv2
run: go test -json -v ./... 2>&1 | gotestfmt
@@ -71,8 +72,8 @@ jobs:
strategy:
fail-fast: false
matrix:
- go-version: ["1.25", "1.26"]
- name: Build (${{ matrix.go-version == '1.26' && 'latest' || 'old' }}, goolm)
+ go-version: ["1.24", "1.25"]
+ name: Build (${{ matrix.go-version == '1.25' && 'latest' || 'old' }}, goolm)
steps:
- uses: actions/checkout@v6
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f2829199..dbc7c494 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,24 +1,3 @@
-## v0.26.3 (2026-02-16)
-
-* Bumped minimum Go version to 1.25.
-* *(client)* Added fields for sending [MSC4354] sticky events.
-* *(bridgev2)* Added automatic message request accepting when sending message.
-* *(mediaproxy)* Added support for federation thumbnail endpoint.
-* *(crypto/ssss)* Improved support for recovery keys with slightly broken
- metadata.
-* *(crypto)* Changed key import to call session received callback even for
- sessions that already exist in the database.
-* *(appservice)* Fixed building websocket URL accidentally using file path
- separators instead of always `/`.
-* *(crypto)* Fixed key exports not including the `sender_claimed_keys` field.
-* *(client)* Fixed incorrect context usage in async uploads.
-* *(crypto)* Fixed panic when passing invalid input to megolm message index
- parser used for debugging.
-* *(bridgev2/provisioning)* Fixed completed or failed logins not being cleaned
- up properly.
-
-[MSC4354]: https://github.com/matrix-org/matrix-spec-proposals/pull/4354
-
## v0.26.2 (2026-01-16)
* *(bridgev2)* Added chunked portal deletion to avoid database locks when
diff --git a/README.md b/README.md
index b1a2edf8..ac41ca78 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,8 @@
# mautrix-go
[](https://pkg.go.dev/maunium.net/go/mautrix)
-A Golang Matrix framework. Used by [gomuks](https://gomuks.app),
-[go-neb](https://github.com/matrix-org/go-neb),
-[mautrix-whatsapp](https://github.com/mautrix/whatsapp)
+A Golang Matrix framework. Used by [gomuks](https://matrix.org/docs/projects/client/gomuks),
+[go-neb](https://github.com/matrix-org/go-neb), [mautrix-whatsapp](https://github.com/mautrix/whatsapp)
and others.
Matrix room: [`#go:maunium.net`](https://matrix.to/#/#go:maunium.net)
@@ -14,10 +13,9 @@ The original project is licensed under [Apache 2.0](https://github.com/matrix-or
In addition to the basic client API features the original project has, this framework also has:
* Appservice support (Intent API like mautrix-python, room state storage, etc)
-* End-to-end encryption support (incl. key backup, cross-signing, interactive verification, etc)
+* End-to-end encryption support (incl. interactive SAS verification)
* High-level module for building puppeting bridges
-* Partial federation module (making requests, PDU processing and event authorization)
-* A media proxy server which can be used to expose anything as a Matrix media repo
+* High-level module for building chat clients
* Wrapper functions for the Synapse admin API
* Structs for parsing event content
* Helpers for parsing and generating Matrix HTML
diff --git a/appservice/intent.go b/appservice/intent.go
index 5d43f190..e4d8e100 100644
--- a/appservice/intent.go
+++ b/appservice/intent.go
@@ -51,7 +51,7 @@ func (as *AppService) NewIntentAPI(localpart string) *IntentAPI {
}
func (intent *IntentAPI) Register(ctx context.Context) error {
- _, err := intent.Client.MakeRequest(ctx, http.MethodPost, intent.BuildClientURL("v3", "register"), &mautrix.ReqRegister[any]{
+ _, err := intent.Client.MakeRequest(ctx, http.MethodPost, intent.BuildClientURL("v3", "register"), &mautrix.ReqRegister{
Username: intent.Localpart,
Type: mautrix.AuthTypeAppservice,
InhibitLogin: true,
@@ -222,17 +222,6 @@ func (intent *IntentAPI) SendMessageEvent(ctx context.Context, roomID id.RoomID,
return intent.Client.SendMessageEvent(ctx, roomID, eventType, contentJSON, extra...)
}
-func (intent *IntentAPI) BeeperSendEphemeralEvent(ctx context.Context, roomID id.RoomID, eventType event.Type, contentJSON any, extra ...mautrix.ReqSendEvent) (*mautrix.RespSendEvent, error) {
- if err := intent.EnsureJoined(ctx, roomID); err != nil {
- return nil, err
- }
- if !intent.SpecVersions.Supports(mautrix.BeeperFeatureEphemeralEvents) {
- return nil, mautrix.MUnrecognized.WithMessage("Homeserver does not advertise com.beeper.ephemeral support")
- }
- contentJSON = intent.AddDoublePuppetValue(contentJSON)
- return intent.Client.BeeperSendEphemeralEvent(ctx, roomID, eventType, contentJSON, extra...)
-}
-
// Deprecated: use SendMessageEvent with mautrix.ReqSendEvent.Timestamp instead
func (intent *IntentAPI) SendMassagedMessageEvent(ctx context.Context, roomID id.RoomID, eventType event.Type, contentJSON interface{}, ts int64) (*mautrix.RespSendEvent, error) {
return intent.SendMessageEvent(ctx, roomID, eventType, contentJSON, mautrix.ReqSendEvent{Timestamp: ts})
diff --git a/appservice/websocket.go b/appservice/websocket.go
index ef65e65a..4f2538bf 100644
--- a/appservice/websocket.go
+++ b/appservice/websocket.go
@@ -14,7 +14,7 @@ import (
"io"
"net/http"
"net/url"
- "path"
+ "path/filepath"
"strings"
"sync"
"sync/atomic"
@@ -374,7 +374,7 @@ func (as *AppService) StartWebsocket(ctx context.Context, baseURL string, onConn
copiedURL := *as.hsURLForClient
parsed = &copiedURL
}
- parsed.Path = path.Join(parsed.Path, "_matrix/client/unstable/fi.mau.as_sync")
+ parsed.Path = filepath.Join(parsed.Path, "_matrix/client/unstable/fi.mau.as_sync")
if parsed.Scheme == "http" {
parsed.Scheme = "ws"
} else if parsed.Scheme == "https" {
diff --git a/bridgev2/bridgeconfig/config.go b/bridgev2/bridgeconfig/config.go
index bd6b9c06..8b9aa019 100644
--- a/bridgev2/bridgeconfig/config.go
+++ b/bridgev2/bridgeconfig/config.go
@@ -62,40 +62,38 @@ type CleanupOnLogouts struct {
}
type BridgeConfig struct {
- CommandPrefix string `yaml:"command_prefix"`
- PersonalFilteringSpaces bool `yaml:"personal_filtering_spaces"`
- PrivateChatPortalMeta bool `yaml:"private_chat_portal_meta"`
- AsyncEvents bool `yaml:"async_events"`
- SplitPortals bool `yaml:"split_portals"`
- ResendBridgeInfo bool `yaml:"resend_bridge_info"`
- NoBridgeInfoStateKey bool `yaml:"no_bridge_info_state_key"`
- BridgeStatusNotices string `yaml:"bridge_status_notices"`
- UnknownErrorAutoReconnect time.Duration `yaml:"unknown_error_auto_reconnect"`
- UnknownErrorMaxAutoReconnects int `yaml:"unknown_error_max_auto_reconnects"`
- BridgeMatrixLeave bool `yaml:"bridge_matrix_leave"`
- BridgeNotices bool `yaml:"bridge_notices"`
- TagOnlyOnCreate bool `yaml:"tag_only_on_create"`
- OnlyBridgeTags []event.RoomTag `yaml:"only_bridge_tags"`
- MuteOnlyOnCreate bool `yaml:"mute_only_on_create"`
- DeduplicateMatrixMessages bool `yaml:"deduplicate_matrix_messages"`
- CrossRoomReplies bool `yaml:"cross_room_replies"`
- OutgoingMessageReID bool `yaml:"outgoing_message_re_id"`
- RevertFailedStateChanges bool `yaml:"revert_failed_state_changes"`
- KickMatrixUsers bool `yaml:"kick_matrix_users"`
- CleanupOnLogout CleanupOnLogouts `yaml:"cleanup_on_logout"`
- Relay RelayConfig `yaml:"relay"`
- Permissions PermissionConfig `yaml:"permissions"`
- Backfill BackfillConfig `yaml:"backfill"`
+ CommandPrefix string `yaml:"command_prefix"`
+ PersonalFilteringSpaces bool `yaml:"personal_filtering_spaces"`
+ PrivateChatPortalMeta bool `yaml:"private_chat_portal_meta"`
+ AsyncEvents bool `yaml:"async_events"`
+ SplitPortals bool `yaml:"split_portals"`
+ ResendBridgeInfo bool `yaml:"resend_bridge_info"`
+ NoBridgeInfoStateKey bool `yaml:"no_bridge_info_state_key"`
+ BridgeStatusNotices string `yaml:"bridge_status_notices"`
+ UnknownErrorAutoReconnect time.Duration `yaml:"unknown_error_auto_reconnect"`
+ BridgeMatrixLeave bool `yaml:"bridge_matrix_leave"`
+ BridgeNotices bool `yaml:"bridge_notices"`
+ TagOnlyOnCreate bool `yaml:"tag_only_on_create"`
+ OnlyBridgeTags []event.RoomTag `yaml:"only_bridge_tags"`
+ MuteOnlyOnCreate bool `yaml:"mute_only_on_create"`
+ DeduplicateMatrixMessages bool `yaml:"deduplicate_matrix_messages"`
+ CrossRoomReplies bool `yaml:"cross_room_replies"`
+ OutgoingMessageReID bool `yaml:"outgoing_message_re_id"`
+ RevertFailedStateChanges bool `yaml:"revert_failed_state_changes"`
+ KickMatrixUsers bool `yaml:"kick_matrix_users"`
+ CleanupOnLogout CleanupOnLogouts `yaml:"cleanup_on_logout"`
+ Relay RelayConfig `yaml:"relay"`
+ Permissions PermissionConfig `yaml:"permissions"`
+ Backfill BackfillConfig `yaml:"backfill"`
}
type MatrixConfig struct {
- MessageStatusEvents bool `yaml:"message_status_events"`
- DeliveryReceipts bool `yaml:"delivery_receipts"`
- MessageErrorNotices bool `yaml:"message_error_notices"`
- SyncDirectChatList bool `yaml:"sync_direct_chat_list"`
- FederateRooms bool `yaml:"federate_rooms"`
- UploadFileThreshold int64 `yaml:"upload_file_threshold"`
- GhostExtraProfileInfo bool `yaml:"ghost_extra_profile_info"`
+ MessageStatusEvents bool `yaml:"message_status_events"`
+ DeliveryReceipts bool `yaml:"delivery_receipts"`
+ MessageErrorNotices bool `yaml:"message_error_notices"`
+ SyncDirectChatList bool `yaml:"sync_direct_chat_list"`
+ FederateRooms bool `yaml:"federate_rooms"`
+ UploadFileThreshold int64 `yaml:"upload_file_threshold"`
}
type AnalyticsConfig struct {
diff --git a/bridgev2/bridgeconfig/upgrade.go b/bridgev2/bridgeconfig/upgrade.go
index 92515ea0..a0278672 100644
--- a/bridgev2/bridgeconfig/upgrade.go
+++ b/bridgev2/bridgeconfig/upgrade.go
@@ -33,7 +33,6 @@ func doUpgrade(helper up.Helper) {
helper.Copy(up.Bool, "bridge", "no_bridge_info_state_key")
helper.Copy(up.Str|up.Null, "bridge", "bridge_status_notices")
helper.Copy(up.Str|up.Int|up.Null, "bridge", "unknown_error_auto_reconnect")
- helper.Copy(up.Int, "bridge", "unknown_error_max_auto_reconnects")
helper.Copy(up.Bool, "bridge", "bridge_matrix_leave")
helper.Copy(up.Bool, "bridge", "bridge_notices")
helper.Copy(up.Bool, "bridge", "tag_only_on_create")
@@ -101,7 +100,6 @@ func doUpgrade(helper up.Helper) {
helper.Copy(up.Bool, "matrix", "sync_direct_chat_list")
helper.Copy(up.Bool, "matrix", "federate_rooms")
helper.Copy(up.Int, "matrix", "upload_file_threshold")
- helper.Copy(up.Bool, "matrix", "ghost_extra_profile_info")
helper.Copy(up.Str|up.Null, "analytics", "token")
helper.Copy(up.Str|up.Null, "analytics", "url")
diff --git a/bridgev2/bridgestate.go b/bridgev2/bridgestate.go
index 96d9fd5c..babbccab 100644
--- a/bridgev2/bridgestate.go
+++ b/bridgev2/bridgestate.go
@@ -37,8 +37,6 @@ type BridgeStateQueue struct {
stopChan chan struct{}
stopReconnect atomic.Pointer[context.CancelFunc]
-
- unknownErrorReconnects int
}
func (br *Bridge) SendGlobalBridgeState(state status.BridgeState) {
@@ -194,14 +192,8 @@ func (bsq *BridgeStateQueue) unknownErrorReconnect(triggeredBy status.BridgeStat
} else if prevUnsent.StateEvent != status.StateUnknownError || prev.StateEvent != status.StateUnknownError {
log.Debug().Msg("Not reconnecting as the previous state was not an unknown error")
return
- } else if bsq.unknownErrorReconnects > bsq.bridge.Config.UnknownErrorMaxAutoReconnects {
- log.Warn().Msg("Not reconnecting as the maximum number of unknown error reconnects has been reached")
- return
}
- bsq.unknownErrorReconnects++
- log.Info().
- Int("reconnect_num", bsq.unknownErrorReconnects).
- Msg("Disconnecting and reconnecting login due to unknown error")
+ log.Info().Msg("Disconnecting and reconnecting login due to unknown error")
bsq.login.Disconnect()
log.Debug().Msg("Disconnection finished, recreating client and reconnecting")
err := bsq.login.recreateClient(ctx)
diff --git a/bridgev2/commands/login.go b/bridgev2/commands/login.go
index 96d62d3e..80a7c733 100644
--- a/bridgev2/commands/login.go
+++ b/bridgev2/commands/login.go
@@ -121,7 +121,6 @@ func fnLogin(ce *Event) {
ce.Reply("Failed to start login: %v", err)
return
}
- ce.Log.Debug().Any("first_step", nextStep).Msg("Created login process")
nextStep = checkLoginCommandDirectParams(ce, login, nextStep)
if nextStep != nil {
@@ -252,19 +251,14 @@ func sendQR(ce *Event, qr string, prevEventID *id.EventID) error {
return fmt.Errorf("failed to upload image: %w", err)
}
content := &event.MessageEventContent{
- MsgType: event.MsgImage,
- FileName: "qr.png",
- URL: qrMXC,
- File: qrFile,
+ MsgType: event.MsgImage,
+ FileName: "qr.png",
+ URL: qrMXC,
+ File: qrFile,
+
Body: qr,
Format: event.FormatHTML,
FormattedBody: fmt.Sprintf("
%s
", html.EscapeString(qr)),
- Info: &event.FileInfo{
- MimeType: "image/png",
- Width: qrSizePx,
- Height: qrSizePx,
- Size: len(qrData),
- },
}
if *prevEventID != "" {
content.SetEdit(*prevEventID)
@@ -279,36 +273,6 @@ func sendQR(ce *Event, qr string, prevEventID *id.EventID) error {
return nil
}
-func sendUserInputAttachments(ce *Event, atts []*bridgev2.LoginUserInputAttachment) error {
- for _, att := range atts {
- if att.FileName == "" {
- return fmt.Errorf("missing attachment filename")
- }
- mxc, file, err := ce.Bot.UploadMedia(ce.Ctx, ce.RoomID, att.Content, att.FileName, att.Info.MimeType)
- if err != nil {
- return fmt.Errorf("failed to upload attachment %q: %w", att.FileName, err)
- }
- content := &event.MessageEventContent{
- MsgType: att.Type,
- FileName: att.FileName,
- URL: mxc,
- File: file,
- Info: &event.FileInfo{
- MimeType: att.Info.MimeType,
- Width: att.Info.Width,
- Height: att.Info.Height,
- Size: att.Info.Size,
- },
- Body: att.FileName,
- }
- _, err = ce.Bot.SendMessage(ce.Ctx, ce.RoomID, event.EventMessage, &event.Content{Parsed: content}, nil)
- if err != nil {
- return nil
- }
- }
- return nil
-}
-
type contextKey int
const (
@@ -500,7 +464,6 @@ func maybeURLDecodeCookie(val string, field *bridgev2.LoginCookieField) string {
}
func doLoginStep(ce *Event, login bridgev2.LoginProcess, step *bridgev2.LoginStep, override *bridgev2.UserLogin) {
- ce.Log.Debug().Any("next_step", step).Msg("Got next login step")
if step.Instructions != "" {
ce.Reply(step.Instructions)
}
@@ -515,10 +478,6 @@ func doLoginStep(ce *Event, login bridgev2.LoginProcess, step *bridgev2.LoginSte
Override: override,
}).prompt(ce)
case bridgev2.LoginStepTypeUserInput:
- err := sendUserInputAttachments(ce, step.UserInputParams.Attachments)
- if err != nil {
- ce.Reply("Failed to send attachments: %v", err)
- }
(&userInputLoginCommandState{
Login: login.(bridgev2.LoginProcessUserInput),
RemainingFields: step.UserInputParams.Fields,
diff --git a/bridgev2/database/ghost.go b/bridgev2/database/ghost.go
index 16af35ca..c32929ad 100644
--- a/bridgev2/database/ghost.go
+++ b/bridgev2/database/ghost.go
@@ -7,17 +7,12 @@
package database
import (
- "bytes"
"context"
"encoding/hex"
- "encoding/json"
- "fmt"
"go.mau.fi/util/dbutil"
- "go.mau.fi/util/exerrors"
"maunium.net/go/mautrix/bridgev2/networkid"
- "maunium.net/go/mautrix/crypto/canonicaljson"
"maunium.net/go/mautrix/id"
)
@@ -27,55 +22,6 @@ type GhostQuery struct {
*dbutil.QueryHelper[*Ghost]
}
-type ExtraProfile map[string]json.RawMessage
-
-func (ep *ExtraProfile) Set(key string, value any) error {
- if key == "displayname" || key == "avatar_url" {
- return fmt.Errorf("cannot set reserved profile key %q", key)
- }
- marshaled, err := json.Marshal(value)
- if err != nil {
- return err
- }
- if *ep == nil {
- *ep = make(ExtraProfile)
- }
- (*ep)[key] = canonicaljson.CanonicalJSONAssumeValid(marshaled)
- return nil
-}
-
-func (ep *ExtraProfile) With(key string, value any) *ExtraProfile {
- exerrors.PanicIfNotNil(ep.Set(key, value))
- return ep
-}
-
-func canonicalizeIfObject(data json.RawMessage) json.RawMessage {
- if len(data) > 0 && (data[0] == '{' || data[0] == '[') {
- return canonicaljson.CanonicalJSONAssumeValid(data)
- }
- return data
-}
-
-func (ep *ExtraProfile) CopyTo(dest *ExtraProfile) (changed bool) {
- if len(*ep) == 0 {
- return
- }
- if *dest == nil {
- *dest = make(ExtraProfile)
- }
- for key, val := range *ep {
- if key == "displayname" || key == "avatar_url" {
- continue
- }
- existing, exists := (*dest)[key]
- if !exists || !bytes.Equal(canonicalizeIfObject(existing), val) {
- (*dest)[key] = val
- changed = true
- }
- }
- return
-}
-
type Ghost struct {
BridgeID networkid.BridgeID
ID networkid.UserID
@@ -89,14 +35,13 @@ type Ghost struct {
ContactInfoSet bool
IsBot bool
Identifiers []string
- ExtraProfile ExtraProfile
Metadata any
}
const (
getGhostBaseQuery = `
SELECT bridge_id, id, name, avatar_id, avatar_hash, avatar_mxc,
- name_set, avatar_set, contact_info_set, is_bot, identifiers, extra_profile, metadata
+ name_set, avatar_set, contact_info_set, is_bot, identifiers, metadata
FROM ghost
`
getGhostByIDQuery = getGhostBaseQuery + `WHERE bridge_id=$1 AND id=$2`
@@ -104,14 +49,13 @@ const (
insertGhostQuery = `
INSERT INTO ghost (
bridge_id, id, name, avatar_id, avatar_hash, avatar_mxc,
- name_set, avatar_set, contact_info_set, is_bot, identifiers, extra_profile, metadata
+ name_set, avatar_set, contact_info_set, is_bot, identifiers, metadata
)
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
`
updateGhostQuery = `
UPDATE ghost SET name=$3, avatar_id=$4, avatar_hash=$5, avatar_mxc=$6,
- name_set=$7, avatar_set=$8, contact_info_set=$9, is_bot=$10,
- identifiers=$11, extra_profile=$12, metadata=$13
+ name_set=$7, avatar_set=$8, contact_info_set=$9, is_bot=$10, identifiers=$11, metadata=$12
WHERE bridge_id=$1 AND id=$2
`
)
@@ -142,7 +86,7 @@ func (g *Ghost) Scan(row dbutil.Scannable) (*Ghost, error) {
&g.BridgeID, &g.ID,
&g.Name, &g.AvatarID, &avatarHash, &g.AvatarMXC,
&g.NameSet, &g.AvatarSet, &g.ContactInfoSet, &g.IsBot,
- dbutil.JSON{Data: &g.Identifiers}, dbutil.JSON{Data: &g.ExtraProfile}, dbutil.JSON{Data: g.Metadata},
+ dbutil.JSON{Data: &g.Identifiers}, dbutil.JSON{Data: g.Metadata},
)
if err != nil {
return nil, err
@@ -172,6 +116,6 @@ func (g *Ghost) sqlVariables() []any {
g.BridgeID, g.ID,
g.Name, g.AvatarID, avatarHash, g.AvatarMXC,
g.NameSet, g.AvatarSet, g.ContactInfoSet, g.IsBot,
- dbutil.JSON{Data: &g.Identifiers}, dbutil.JSON{Data: g.ExtraProfile}, dbutil.JSON{Data: g.Metadata},
+ dbutil.JSON{Data: &g.Identifiers}, dbutil.JSON{Data: g.Metadata},
}
}
diff --git a/bridgev2/database/message.go b/bridgev2/database/message.go
index 4fd599a8..43f33666 100644
--- a/bridgev2/database/message.go
+++ b/bridgev2/database/message.go
@@ -68,8 +68,8 @@ const (
getFirstMessagePartByIDQuery = getMessageBaseQuery + `WHERE bridge_id=$1 AND (room_receiver=$2 OR room_receiver='') AND id=$3 ORDER BY part_id ASC LIMIT 1`
getMessagesBetweenTimeQuery = getMessageBaseQuery + `WHERE bridge_id=$1 AND room_id=$2 AND room_receiver=$3 AND timestamp>$4 AND timestamp<=$5`
getOldestMessageInPortal = getMessageBaseQuery + `WHERE bridge_id=$1 AND room_id=$2 AND room_receiver=$3 ORDER BY timestamp ASC, part_id ASC LIMIT 1`
- getFirstMessageInThread = getMessageBaseQuery + `WHERE bridge_id=$1 AND room_id=$2 AND room_receiver=$3 AND (id=$4 OR thread_root_id=$4) ORDER BY thread_root_id NULLS FIRST, timestamp ASC, part_id ASC LIMIT 1`
- getLastMessageInThread = getMessageBaseQuery + `WHERE bridge_id=$1 AND room_id=$2 AND room_receiver=$3 AND (id=$4 OR thread_root_id=$4) ORDER BY thread_root_id NULLS LAST, timestamp DESC, part_id DESC LIMIT 1`
+ getFirstMessageInThread = getMessageBaseQuery + `WHERE bridge_id=$1 AND room_id=$2 AND room_receiver=$3 AND (id=$4 OR thread_root_id=$4) ORDER BY timestamp ASC, part_id ASC LIMIT 1`
+ getLastMessageInThread = getMessageBaseQuery + `WHERE bridge_id=$1 AND room_id=$2 AND room_receiver=$3 AND (id=$4 OR thread_root_id=$4) ORDER BY timestamp DESC, part_id DESC LIMIT 1`
getLastNInPortal = getMessageBaseQuery + `WHERE bridge_id=$1 AND room_id=$2 AND room_receiver=$3 ORDER BY timestamp DESC, part_id DESC LIMIT $4`
getLastMessagePartAtOrBeforeTimeQuery = getMessageBaseQuery + `WHERE bridge_id = $1 AND room_id=$2 AND room_receiver=$3 AND timestamp<=$4 ORDER BY timestamp DESC, part_id DESC LIMIT 1`
diff --git a/bridgev2/database/upgrades/00-latest.sql b/bridgev2/database/upgrades/00-latest.sql
index 6092dc24..b193d314 100644
--- a/bridgev2/database/upgrades/00-latest.sql
+++ b/bridgev2/database/upgrades/00-latest.sql
@@ -1,4 +1,4 @@
--- v0 -> v27 (compatible with v9+): Latest revision
+-- v0 -> v26 (compatible with v9+): Latest revision
CREATE TABLE "user" (
bridge_id TEXT NOT NULL,
mxid TEXT NOT NULL,
@@ -80,7 +80,6 @@ CREATE TABLE ghost (
contact_info_set BOOLEAN NOT NULL,
is_bot BOOLEAN NOT NULL,
identifiers jsonb NOT NULL,
- extra_profile jsonb,
metadata jsonb NOT NULL,
PRIMARY KEY (bridge_id, id)
diff --git a/bridgev2/database/upgrades/27-ghost-extra-profile.sql b/bridgev2/database/upgrades/27-ghost-extra-profile.sql
deleted file mode 100644
index e8e0549a..00000000
--- a/bridgev2/database/upgrades/27-ghost-extra-profile.sql
+++ /dev/null
@@ -1,2 +0,0 @@
--- v27 (compatible with v9+): Add column for extra ghost profile metadata
-ALTER TABLE ghost ADD COLUMN extra_profile jsonb;
diff --git a/bridgev2/database/userlogin.go b/bridgev2/database/userlogin.go
index 00ff01c9..9fa6569a 100644
--- a/bridgev2/database/userlogin.go
+++ b/bridgev2/database/userlogin.go
@@ -116,7 +116,7 @@ func (u *UserLogin) ensureHasMetadata(metaType MetaTypeCreator) *UserLogin {
func (u *UserLogin) sqlVariables() []any {
var remoteProfile dbutil.JSON
- if !u.RemoteProfile.IsZero() {
+ if !u.RemoteProfile.IsEmpty() {
remoteProfile.Data = &u.RemoteProfile
}
return []any{u.BridgeID, u.UserMXID, u.ID, u.RemoteName, remoteProfile, dbutil.StrPtr(u.SpaceRoom), dbutil.JSON{Data: u.Metadata}}
diff --git a/bridgev2/errors.go b/bridgev2/errors.go
index f6677d2e..514dc238 100644
--- a/bridgev2/errors.go
+++ b/bridgev2/errors.go
@@ -75,7 +75,6 @@ var (
ErrMediaConvertFailed error = WrapErrorInStatus(errors.New("failed to convert media")).WithMessage("failed to convert media").WithIsCertain(true).WithSendNotice(true)
ErrMembershipNotSupported error = WrapErrorInStatus(errors.New("this bridge does not support changing group membership")).WithIsCertain(true).WithErrorAsMessage().WithSendNotice(false).WithErrorReason(event.MessageStatusUnsupported)
ErrDeleteChatNotSupported error = WrapErrorInStatus(errors.New("this bridge does not support deleting chats")).WithIsCertain(true).WithErrorAsMessage().WithSendNotice(false).WithErrorReason(event.MessageStatusUnsupported)
- ErrBeeperAIStreamNotSupported error = WrapErrorInStatus(errors.New("this bridge does not support Beeper AI stream events")).WithIsCertain(true).WithErrorAsMessage().WithSendNotice(false).WithErrorReason(event.MessageStatusUnsupported)
ErrPowerLevelsNotSupported error = WrapErrorInStatus(errors.New("this bridge does not support changing group power levels")).WithIsCertain(true).WithErrorAsMessage().WithSendNotice(false).WithErrorReason(event.MessageStatusUnsupported)
ErrRemoteEchoTimeout = WrapErrorInStatus(errors.New("remote echo timed out")).WithIsCertain(false).WithSendNotice(true).WithErrorReason(event.MessageStatusTooOld)
ErrRemoteAckTimeout = WrapErrorInStatus(errors.New("remote ack timed out")).WithIsCertain(false).WithSendNotice(true).WithErrorReason(event.MessageStatusTooOld)
diff --git a/bridgev2/ghost.go b/bridgev2/ghost.go
index 590dd1dc..6cef6f06 100644
--- a/bridgev2/ghost.go
+++ b/bridgev2/ghost.go
@@ -9,15 +9,12 @@ package bridgev2
import (
"context"
"crypto/sha256"
- "encoding/json"
"fmt"
- "maps"
"net/http"
- "slices"
"github.com/rs/zerolog"
- "go.mau.fi/util/exerrors"
"go.mau.fi/util/exmime"
+ "golang.org/x/exp/slices"
"maunium.net/go/mautrix/bridgev2/database"
"maunium.net/go/mautrix/bridgev2/networkid"
@@ -137,11 +134,10 @@ func (a *Avatar) Reupload(ctx context.Context, intent MatrixAPI, currentHash [32
}
type UserInfo struct {
- Identifiers []string
- Name *string
- Avatar *Avatar
- IsBot *bool
- ExtraProfile database.ExtraProfile
+ Identifiers []string
+ Name *string
+ Avatar *Avatar
+ IsBot *bool
ExtraUpdates ExtraUpdater[*Ghost]
}
@@ -189,9 +185,9 @@ func (ghost *Ghost) UpdateAvatar(ctx context.Context, avatar *Avatar) bool {
return true
}
-func (ghost *Ghost) getExtraProfileMeta() any {
+func (ghost *Ghost) getExtraProfileMeta() *event.BeeperProfileExtra {
bridgeName := ghost.Bridge.Network.GetName()
- baseExtra := &event.BeeperProfileExtra{
+ return &event.BeeperProfileExtra{
RemoteID: string(ghost.ID),
Identifiers: ghost.Identifiers,
Service: bridgeName.BeeperBridgeType,
@@ -199,35 +195,23 @@ func (ghost *Ghost) getExtraProfileMeta() any {
IsBridgeBot: false,
IsNetworkBot: ghost.IsBot,
}
- if len(ghost.ExtraProfile) == 0 {
- return baseExtra
- }
- mergedExtra := maps.Clone(ghost.ExtraProfile)
- baseExtraMarshaled := exerrors.Must(json.Marshal(baseExtra))
- exerrors.PanicIfNotNil(json.Unmarshal(baseExtraMarshaled, &mergedExtra))
- return mergedExtra
}
-func (ghost *Ghost) UpdateContactInfo(ctx context.Context, identifiers []string, isBot *bool, extraProfile database.ExtraProfile) bool {
- if !ghost.Bridge.Matrix.GetCapabilities().ExtraProfileMeta {
- ghost.ContactInfoSet = false
- return false
- }
+func (ghost *Ghost) UpdateContactInfo(ctx context.Context, identifiers []string, isBot *bool) bool {
if identifiers != nil {
slices.Sort(identifiers)
}
- changed := extraProfile.CopyTo(&ghost.ExtraProfile)
+ if ghost.ContactInfoSet &&
+ (identifiers == nil || slices.Equal(identifiers, ghost.Identifiers)) &&
+ (isBot == nil || *isBot == ghost.IsBot) {
+ return false
+ }
if identifiers != nil {
- changed = changed || !slices.Equal(identifiers, ghost.Identifiers)
ghost.Identifiers = identifiers
}
if isBot != nil {
- changed = changed || *isBot != ghost.IsBot
ghost.IsBot = *isBot
}
- if ghost.ContactInfoSet && !changed {
- return false
- }
err := ghost.Intent.SetExtraProfileMeta(ctx, ghost.getExtraProfileMeta())
if err != nil {
zerolog.Ctx(ctx).Err(err).Msg("Failed to set extra profile metadata")
@@ -250,7 +234,7 @@ func (br *Bridge) allowAggressiveUpdateForType(evtType RemoteEventType) bool {
}
func (ghost *Ghost) UpdateInfoIfNecessary(ctx context.Context, source *UserLogin, evtType RemoteEventType) {
- if ghost.Name != "" && ghost.NameSet && ghost.AvatarSet && !ghost.Bridge.allowAggressiveUpdateForType(evtType) {
+ if ghost.Name != "" && ghost.NameSet && !ghost.Bridge.allowAggressiveUpdateForType(evtType) {
return
}
info, err := source.Client.GetUserInfo(ctx, ghost)
@@ -260,16 +244,12 @@ func (ghost *Ghost) UpdateInfoIfNecessary(ctx context.Context, source *UserLogin
zerolog.Ctx(ctx).Debug().
Bool("has_name", ghost.Name != "").
Bool("name_set", ghost.NameSet).
- Bool("has_avatar", ghost.AvatarMXC != "").
- Bool("avatar_set", ghost.AvatarSet).
Msg("Updating ghost info in IfNecessary call")
ghost.UpdateInfo(ctx, info)
} else {
zerolog.Ctx(ctx).Trace().
Bool("has_name", ghost.Name != "").
Bool("name_set", ghost.NameSet).
- Bool("has_avatar", ghost.AvatarMXC != "").
- Bool("avatar_set", ghost.AvatarSet).
Msg("No ghost info received in IfNecessary call")
}
}
@@ -297,14 +277,9 @@ func (ghost *Ghost) UpdateInfo(ctx context.Context, info *UserInfo) {
}
if info.Avatar != nil {
update = ghost.UpdateAvatar(ctx, info.Avatar) || update
- } else if oldAvatar == "" && !ghost.AvatarSet {
- // Special case: nil avatar means we're not expecting one ever, if we don't currently have
- // one we flag it as set to avoid constantly refetching in UpdateInfoIfNecessary.
- ghost.AvatarSet = true
- update = true
}
- if info.Identifiers != nil || info.IsBot != nil || info.ExtraProfile != nil {
- update = ghost.UpdateContactInfo(ctx, info.Identifiers, info.IsBot, info.ExtraProfile) || update
+ if info.Identifiers != nil || info.IsBot != nil {
+ update = ghost.UpdateContactInfo(ctx, info.Identifiers, info.IsBot) || update
}
if info.ExtraUpdates != nil {
update = info.ExtraUpdates(ctx, ghost) || update
diff --git a/bridgev2/login.go b/bridgev2/login.go
index b8321719..4ddbf13e 100644
--- a/bridgev2/login.go
+++ b/bridgev2/login.go
@@ -13,7 +13,6 @@ import (
"strings"
"maunium.net/go/mautrix/bridgev2/networkid"
- "maunium.net/go/mautrix/event"
)
// LoginProcess represents a single occurrence of a user logging into the remote network.
@@ -180,7 +179,6 @@ const (
LoginInputFieldTypeURL LoginInputFieldType = "url"
LoginInputFieldTypeDomain LoginInputFieldType = "domain"
LoginInputFieldTypeSelect LoginInputFieldType = "select"
- LoginInputFieldTypeCaptchaCode LoginInputFieldType = "captcha_code"
)
type LoginInputDataField struct {
@@ -273,23 +271,6 @@ func (f *LoginInputDataField) FillDefaultValidate() {
type LoginUserInputParams struct {
// The fields that the user needs to fill in.
Fields []LoginInputDataField `json:"fields"`
-
- // Attachments to display alongside the input fields.
- Attachments []*LoginUserInputAttachment `json:"attachments"`
-}
-
-type LoginUserInputAttachment struct {
- Type event.MessageType `json:"type,omitempty"`
- FileName string `json:"filename,omitempty"`
- Content []byte `json:"content,omitempty"`
- Info LoginUserInputAttachmentInfo `json:"info,omitempty"`
-}
-
-type LoginUserInputAttachmentInfo struct {
- MimeType string `json:"mimetype,omitempty"`
- Width int `json:"w,omitempty"`
- Height int `json:"h,omitempty"`
- Size int `json:"size,omitempty"`
}
type LoginCompleteParams struct {
diff --git a/bridgev2/matrix/connector.go b/bridgev2/matrix/connector.go
index 5a2df953..aed6d3bd 100644
--- a/bridgev2/matrix/connector.go
+++ b/bridgev2/matrix/connector.go
@@ -144,7 +144,6 @@ func (br *Connector) Init(bridge *bridgev2.Bridge) {
br.EventProcessor.On(event.EventReaction, br.handleRoomEvent)
br.EventProcessor.On(event.EventRedaction, br.handleRoomEvent)
br.EventProcessor.On(event.EventEncrypted, br.handleEncryptedEvent)
- br.EventProcessor.On(event.EphemeralEventEncrypted, br.handleEncryptedEvent)
br.EventProcessor.On(event.StateMember, br.handleRoomEvent)
br.EventProcessor.On(event.StatePowerLevels, br.handleRoomEvent)
br.EventProcessor.On(event.StateRoomName, br.handleRoomEvent)
@@ -157,7 +156,6 @@ func (br *Connector) Init(bridge *bridgev2.Bridge) {
br.EventProcessor.On(event.BeeperAcceptMessageRequest, br.handleRoomEvent)
br.EventProcessor.On(event.EphemeralEventReceipt, br.handleEphemeralEvent)
br.EventProcessor.On(event.EphemeralEventTyping, br.handleEphemeralEvent)
- br.EventProcessor.On(event.BeeperEphemeralEventAIStream, br.handleEphemeralEvent)
br.Bot = br.AS.BotIntent()
br.Crypto = NewCryptoHelper(br)
br.Bridge.Commands.(*commands.Processor).AddHandlers(
@@ -369,8 +367,6 @@ func (br *Connector) ensureConnection(ctx context.Context) {
br.Capabilities.AutoJoinInvites = br.SpecVersions.Supports(mautrix.BeeperFeatureAutojoinInvites)
br.Capabilities.BatchSending = br.SpecVersions.Supports(mautrix.BeeperFeatureBatchSending)
br.Capabilities.ArbitraryMemberChange = br.SpecVersions.Supports(mautrix.BeeperFeatureArbitraryMemberChange)
- br.Capabilities.ExtraProfileMeta = br.SpecVersions.Supports(mautrix.BeeperFeatureArbitraryProfileMeta) ||
- (br.SpecVersions.Supports(mautrix.FeatureArbitraryProfileFields) && br.Config.Matrix.GhostExtraProfileInfo)
break
}
}
diff --git a/bridgev2/matrix/intent.go b/bridgev2/matrix/intent.go
index f7254bd4..3d2692f9 100644
--- a/bridgev2/matrix/intent.go
+++ b/bridgev2/matrix/intent.go
@@ -9,7 +9,6 @@ package matrix
import (
"bytes"
"context"
- "encoding/json"
"errors"
"fmt"
"io"
@@ -28,7 +27,6 @@ import (
"maunium.net/go/mautrix/bridgev2"
"maunium.net/go/mautrix/bridgev2/bridgeconfig"
"maunium.net/go/mautrix/crypto/attachment"
- "maunium.net/go/mautrix/crypto/canonicaljson"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
"maunium.net/go/mautrix/pushrules"
@@ -45,7 +43,6 @@ type ASIntent struct {
var _ bridgev2.MatrixAPI = (*ASIntent)(nil)
var _ bridgev2.MarkAsDMMatrixAPI = (*ASIntent)(nil)
-var _ bridgev2.EphemeralSendingMatrixAPI = (*ASIntent)(nil)
func (as *ASIntent) SendMessage(ctx context.Context, roomID id.RoomID, eventType event.Type, content *event.Content, extra *bridgev2.MatrixSendExtra) (*mautrix.RespSendEvent, error) {
if extra == nil {
@@ -87,21 +84,6 @@ func (as *ASIntent) SendMessage(ctx context.Context, roomID id.RoomID, eventType
return as.Matrix.SendMessageEvent(ctx, roomID, eventType, content, mautrix.ReqSendEvent{Timestamp: extra.Timestamp.UnixMilli()})
}
-func (as *ASIntent) BeeperSendEphemeralEvent(ctx context.Context, roomID id.RoomID, eventType event.Type, content *event.Content, txnID string) (*mautrix.RespSendEvent, error) {
- if !as.Connector.SpecVersions.Supports(mautrix.BeeperFeatureEphemeralEvents) {
- return nil, mautrix.MUnrecognized.WithMessage("Homeserver does not advertise com.beeper.ephemeral support")
- }
- if encrypted, err := as.Matrix.StateStore.IsEncrypted(ctx, roomID); err != nil {
- return nil, fmt.Errorf("failed to check if room is encrypted: %w", err)
- } else if encrypted && as.Connector.Crypto != nil {
- if err = as.Connector.Crypto.Encrypt(ctx, roomID, eventType, content); err != nil {
- return nil, err
- }
- eventType = event.EventEncrypted
- }
- return as.Matrix.BeeperSendEphemeralEvent(ctx, roomID, eventType, content, mautrix.ReqSendEvent{TransactionID: txnID})
-}
-
func (as *ASIntent) fillMemberEvent(ctx context.Context, roomID id.RoomID, userID id.UserID, content *event.Content) {
targetContent, ok := content.Parsed.(*event.MemberEventContent)
if !ok || targetContent.Displayname != "" || targetContent.AvatarURL != "" {
@@ -421,7 +403,6 @@ func (as *ASIntent) UploadMediaStream(
removeAndClose(replFile)
removeAndClose(tempFile)
}
- req.AsyncContext = zerolog.Ctx(ctx).WithContext(as.Connector.Bridge.BackgroundCtx)
startedAsyncUpload = true
var resp *mautrix.RespCreateMXC
resp, err = as.Matrix.UploadAsync(ctx, req)
@@ -454,7 +435,6 @@ func (as *ASIntent) doUploadReq(ctx context.Context, file *event.EncryptedFileIn
as.Connector.uploadSema.Release(int64(len(req.ContentBytes)))
}
}
- req.AsyncContext = zerolog.Ctx(ctx).WithContext(as.Connector.Bridge.BackgroundCtx)
var resp *mautrix.RespCreateMXC
resp, err = as.Matrix.UploadAsync(ctx, req)
if resp != nil {
@@ -486,62 +466,11 @@ func (as *ASIntent) SetAvatarURL(ctx context.Context, avatarURL id.ContentURIStr
return as.Matrix.SetAvatarURL(ctx, parsedAvatarURL)
}
-func dataToFields(data any) (map[string]json.RawMessage, error) {
- fields, ok := data.(map[string]json.RawMessage)
- if ok {
- return fields, nil
- }
- d, err := json.Marshal(data)
- if err != nil {
- return nil, err
- }
- d = canonicaljson.CanonicalJSONAssumeValid(d)
- err = json.Unmarshal(d, &fields)
- return fields, err
-}
-
-func marshalField(val any) json.RawMessage {
- data, _ := json.Marshal(val)
- if len(data) > 0 && (data[0] == '{' || data[0] == '[') {
- return canonicaljson.CanonicalJSONAssumeValid(data)
- }
- return data
-}
-
-var nullJSON = json.RawMessage("null")
-
func (as *ASIntent) SetExtraProfileMeta(ctx context.Context, data any) error {
- if as.Connector.SpecVersions.Supports(mautrix.BeeperFeatureArbitraryProfileMeta) {
- return as.Matrix.BeeperUpdateProfile(ctx, data)
- } else if as.Connector.SpecVersions.Supports(mautrix.FeatureArbitraryProfileFields) && as.Connector.Config.Matrix.GhostExtraProfileInfo {
- fields, err := dataToFields(data)
- if err != nil {
- return fmt.Errorf("failed to marshal fields: %w", err)
- }
- currentProfile, err := as.Matrix.GetProfile(ctx, as.Matrix.UserID)
- if err != nil {
- return fmt.Errorf("failed to get current profile: %w", err)
- }
- for key, val := range fields {
- existing, ok := currentProfile.Extra[key]
- if !ok {
- if bytes.Equal(val, nullJSON) {
- continue
- }
- err = as.Matrix.SetProfileField(ctx, key, val)
- } else if !bytes.Equal(marshalField(existing), val) {
- if bytes.Equal(val, nullJSON) {
- err = as.Matrix.DeleteProfileField(ctx, key)
- } else {
- err = as.Matrix.SetProfileField(ctx, key, val)
- }
- }
- if err != nil {
- return fmt.Errorf("failed to set profile field %q: %w", key, err)
- }
- }
+ if !as.Connector.SpecVersions.Supports(mautrix.BeeperFeatureArbitraryProfileMeta) {
+ return nil
}
- return nil
+ return as.Matrix.BeeperUpdateProfile(ctx, data)
}
func (as *ASIntent) GetMXID() id.UserID {
diff --git a/bridgev2/matrix/matrix.go b/bridgev2/matrix/matrix.go
index 954d0ad9..570ae5f1 100644
--- a/bridgev2/matrix/matrix.go
+++ b/bridgev2/matrix/matrix.go
@@ -68,10 +68,6 @@ func (br *Connector) handleEphemeralEvent(ctx context.Context, evt *event.Event)
case event.EphemeralEventTyping:
typingContent := evt.Content.AsTyping()
typingContent.UserIDs = slices.DeleteFunc(typingContent.UserIDs, br.shouldIgnoreEventFromUser)
- case event.BeeperEphemeralEventAIStream:
- if br.shouldIgnoreEvent(evt) {
- return
- }
}
br.Bridge.QueueMatrixEvent(ctx, evt)
}
@@ -235,6 +231,7 @@ func (br *Connector) postDecrypt(ctx context.Context, original, decrypted *event
go br.sendSuccessCheckpoint(ctx, decrypted, status.MsgStepDecrypted, retryCount)
decrypted.Mautrix.CheckpointSent = true
decrypted.Mautrix.DecryptionDuration = duration
+ decrypted.Mautrix.EventSource |= event.SourceDecrypted
br.EventProcessor.Dispatch(ctx, decrypted)
if errorEventID != nil && *errorEventID != "" {
_, _ = br.Bot.RedactEvent(ctx, decrypted.RoomID, *errorEventID)
diff --git a/bridgev2/matrix/mxmain/example-config.yaml b/bridgev2/matrix/mxmain/example-config.yaml
index ccc81c4b..b0e83696 100644
--- a/bridgev2/matrix/mxmain/example-config.yaml
+++ b/bridgev2/matrix/mxmain/example-config.yaml
@@ -29,9 +29,6 @@ bridge:
# How long after an unknown error should the bridge attempt a full reconnect?
# Must be at least 1 minute. The bridge will add an extra ±20% jitter to this value.
unknown_error_auto_reconnect: null
- # Maximum number of times to do the auto-reconnect above.
- # The counter is per login, but is never reset except on logout and restart.
- unknown_error_max_auto_reconnects: 10
# Should leaving Matrix rooms be bridged as leaving groups on the remote network?
bridge_matrix_leave: false
@@ -244,9 +241,6 @@ matrix:
# The threshold as bytes after which the bridge should roundtrip uploads via the disk
# rather than keeping the whole file in memory.
upload_file_threshold: 5242880
- # Should the bridge set additional custom profile info for ghosts?
- # This can make a lot of requests, as there's no batch profile update endpoint.
- ghost_extra_profile_info: false
# Segment-compatible analytics endpoint for tracking some events, like provisioning API login and encryption errors.
analytics:
diff --git a/bridgev2/matrix/provisioning.go b/bridgev2/matrix/provisioning.go
index 243b91da..e3d3a0b4 100644
--- a/bridgev2/matrix/provisioning.go
+++ b/bridgev2/matrix/provisioning.go
@@ -324,7 +324,7 @@ func (prov *ProvisioningAPI) GetWhoami(w http.ResponseWriter, r *http.Request) {
prevState.UserID = ""
prevState.RemoteID = ""
prevState.RemoteName = ""
- prevState.RemoteProfile = status.RemoteProfile{}
+ prevState.RemoteProfile = nil
resp.Logins[i] = RespWhoamiLogin{
StateEvent: prevState.StateEvent,
StateTS: prevState.Timestamp,
@@ -403,18 +403,10 @@ func (prov *ProvisioningAPI) PostLoginStart(w http.ResponseWriter, r *http.Reque
Override: overrideLogin,
}
prov.loginsLock.Unlock()
- zerolog.Ctx(r.Context()).Info().
- Any("first_step", firstStep).
- Msg("Created login process")
exhttp.WriteJSONResponse(w, http.StatusOK, &RespSubmitLogin{LoginID: loginID, LoginStep: firstStep})
}
func (prov *ProvisioningAPI) handleCompleteStep(ctx context.Context, login *ProvLogin, step *bridgev2.LoginStep) {
- zerolog.Ctx(ctx).Info().
- Str("step_id", step.StepID).
- Str("user_login_id", string(step.CompleteParams.UserLoginID)).
- Msg("Login completed successfully")
- prov.deleteLogin(login, false)
if login.Override == nil || login.Override.ID == step.CompleteParams.UserLoginID {
return
}
@@ -428,15 +420,6 @@ func (prov *ProvisioningAPI) handleCompleteStep(ctx context.Context, login *Prov
}, bridgev2.DeleteOpts{LogoutRemote: true})
}
-func (prov *ProvisioningAPI) deleteLogin(login *ProvLogin, cancel bool) {
- if cancel {
- login.Process.Cancel()
- }
- prov.loginsLock.Lock()
- delete(prov.logins, login.ID)
- prov.loginsLock.Unlock()
-}
-
func (prov *ProvisioningAPI) PostLoginStep(w http.ResponseWriter, r *http.Request) {
loginID := r.PathValue("loginProcessID")
prov.loginsLock.RLock()
@@ -507,14 +490,11 @@ func (prov *ProvisioningAPI) PostLoginSubmitInput(w http.ResponseWriter, r *http
if err != nil {
zerolog.Ctx(r.Context()).Err(err).Msg("Failed to submit input")
RespondWithError(w, err, "Internal error submitting input")
- prov.deleteLogin(login, true)
return
}
login.NextStep = nextStep
if nextStep.Type == bridgev2.LoginStepTypeComplete {
prov.handleCompleteStep(r.Context(), login, nextStep)
- } else {
- zerolog.Ctx(r.Context()).Debug().Any("next_step", nextStep).Msg("Returning next login step")
}
exhttp.WriteJSONResponse(w, http.StatusOK, &RespSubmitLogin{LoginID: login.ID, LoginStep: nextStep})
}
@@ -528,14 +508,11 @@ func (prov *ProvisioningAPI) PostLoginWait(w http.ResponseWriter, r *http.Reques
if err != nil {
zerolog.Ctx(r.Context()).Err(err).Msg("Failed to wait")
RespondWithError(w, err, "Internal error waiting for login")
- prov.deleteLogin(login, true)
return
}
login.NextStep = nextStep
if nextStep.Type == bridgev2.LoginStepTypeComplete {
prov.handleCompleteStep(r.Context(), login, nextStep)
- } else {
- zerolog.Ctx(r.Context()).Debug().Any("next_step", nextStep).Msg("Returning next login step")
}
exhttp.WriteJSONResponse(w, http.StatusOK, &RespSubmitLogin{LoginID: login.ID, LoginStep: nextStep})
}
diff --git a/bridgev2/matrix/provisioning.yaml b/bridgev2/matrix/provisioning.yaml
index 26068db4..d19a7e83 100644
--- a/bridgev2/matrix/provisioning.yaml
+++ b/bridgev2/matrix/provisioning.yaml
@@ -740,41 +740,6 @@ components:
description: For fields of type select, the valid options.
items:
type: string
- attachments:
- type: array
- description: A list of media attachments to show the user alongside the form fields.
- items:
- type: object
- description: A media attachment to show the user.
- required: [ type, filename, content ]
- properties:
- type:
- type: string
- description: The type of media attachment, using the same media type identifiers as Matrix attachments. Only some are supported.
- enum: [ m.image, m.audio ]
- filename:
- type: string
- description: The filename for the media attachment.
- content:
- type: string
- description: The raw file content for the attachment encoded in base64.
- info:
- type: object
- description: Optional but recommended metadata for the attachment. Can generally be derived from the raw content if omitted.
- properties:
- mimetype:
- type: string
- description: The MIME type for the media content.
- examples: [ image/png, audio/mpeg ]
- w:
- type: number
- description: The width of the media in pixels. Only applicable for images and videos.
- h:
- type: number
- description: The height of the media in pixels. Only applicable for images and videos.
- size:
- type: number
- description: The size of the media content in number of bytes. Strongly recommended to include.
- description: Cookie login step
required: [ type, cookies ]
properties:
diff --git a/bridgev2/matrixinterface.go b/bridgev2/matrixinterface.go
index be26db49..57f786bb 100644
--- a/bridgev2/matrixinterface.go
+++ b/bridgev2/matrixinterface.go
@@ -28,7 +28,6 @@ type MatrixCapabilities struct {
AutoJoinInvites bool
BatchSending bool
ArbitraryMemberChange bool
- ExtraProfileMeta bool
}
type MatrixConnector interface {
@@ -218,8 +217,3 @@ type MarkAsDMMatrixAPI interface {
MatrixAPI
MarkAsDM(ctx context.Context, roomID id.RoomID, otherUser id.UserID) error
}
-
-type EphemeralSendingMatrixAPI interface {
- MatrixAPI
- BeeperSendEphemeralEvent(ctx context.Context, roomID id.RoomID, eventType event.Type, content *event.Content, txnID string) (*mautrix.RespSendEvent, error)
-}
diff --git a/bridgev2/networkinterface.go b/bridgev2/networkinterface.go
index b706aedb..0e9a8543 100644
--- a/bridgev2/networkinterface.go
+++ b/bridgev2/networkinterface.go
@@ -726,11 +726,6 @@ type MessageRequestAcceptingNetworkAPI interface {
HandleMatrixAcceptMessageRequest(ctx context.Context, msg *MatrixAcceptMessageRequest) error
}
-type BeeperAIStreamHandlingNetworkAPI interface {
- NetworkAPI
- HandleMatrixBeeperAIStream(ctx context.Context, msg *MatrixBeeperAIStream) error
-}
-
type ResolveIdentifierResponse struct {
// Ghost is the ghost of the user that the identifier resolves to.
// This field should be set whenever possible. However, it is not required,
@@ -1115,11 +1110,6 @@ type RemoteEvent interface {
GetSender() EventSender
}
-type RemoteEventWithContextMutation interface {
- RemoteEvent
- MutateContext(ctx context.Context) context.Context
-}
-
type RemoteEventWithUncertainPortalReceiver interface {
RemoteEvent
PortalReceiverIsUncertain() bool
@@ -1449,7 +1439,6 @@ type MatrixViewingChat struct {
type MatrixDeleteChat = MatrixEventBase[*event.BeeperChatDeleteEventContent]
type MatrixAcceptMessageRequest = MatrixEventBase[*event.BeeperAcceptMessageRequestEventContent]
-type MatrixBeeperAIStream = MatrixEventBase[*event.BeeperAIStreamEventContent]
type MatrixMarkedUnread = MatrixRoomMeta[*event.MarkedUnreadEventContent]
type MatrixMute = MatrixRoomMeta[*event.BeeperMuteEventContent]
type MatrixRoomTag = MatrixRoomMeta[*event.TagEventContent]
diff --git a/bridgev2/portal.go b/bridgev2/portal.go
index 5ba29507..e9feb448 100644
--- a/bridgev2/portal.go
+++ b/bridgev2/portal.go
@@ -169,9 +169,7 @@ func (br *Bridge) loadPortal(ctx context.Context, dbPortal *database.Portal, que
}
func (portal *Portal) updateLogger() {
- logWith := portal.Bridge.Log.With().
- Str("portal_id", string(portal.ID)).
- Str("portal_receiver", string(portal.Receiver))
+ logWith := portal.Bridge.Log.With().Str("portal_id", string(portal.ID))
if portal.MXID != "" {
logWith = logWith.Stringer("portal_mxid", portal.MXID)
}
@@ -448,23 +446,6 @@ func (portal *Portal) handleSingleEventWithDelayLogging(idx int, rawEvt any) (ou
return
}
-type contextKey int
-
-const (
- contextKeyRemoteEvent contextKey = iota
- contextKeyMatrixEvent
-)
-
-func GetMatrixEventFromContext(ctx context.Context) (evt *event.Event) {
- evt, _ = ctx.Value(contextKeyMatrixEvent).(*event.Event)
- return
-}
-
-func GetRemoteEventFromContext(ctx context.Context) (evt RemoteEvent) {
- evt, _ = ctx.Value(contextKeyRemoteEvent).(RemoteEvent)
- return
-}
-
func (portal *Portal) getEventCtxWithLog(rawEvt any, idx int) context.Context {
var logWith zerolog.Context
switch evt := rawEvt.(type) {
@@ -478,10 +459,6 @@ func (portal *Portal) getEventCtxWithLog(rawEvt any, idx int) context.Context {
Stringer("event_id", evt.evt.ID).
Stringer("sender", evt.sender.MXID)
}
- ctx := portal.Bridge.BackgroundCtx
- ctx = context.WithValue(ctx, contextKeyMatrixEvent, evt.evt)
- ctx = logWith.Logger().WithContext(ctx)
- return ctx
case *portalRemoteEvent:
evt.evtType = evt.evt.GetType()
logWith = portal.Log.With().Int("event_loop_index", idx).
@@ -507,23 +484,10 @@ func (portal *Portal) getEventCtxWithLog(rawEvt any, idx int) context.Context {
logWith = logWith.Int64("remote_stream_order", remoteStreamOrder)
}
}
- if remoteMsg, ok := evt.evt.(RemoteEventWithTimestamp); ok {
- if remoteTimestamp := remoteMsg.GetTimestamp(); !remoteTimestamp.IsZero() {
- logWith = logWith.Time("remote_timestamp", remoteTimestamp)
- }
- }
- ctx := portal.Bridge.BackgroundCtx
- ctx = context.WithValue(ctx, contextKeyRemoteEvent, evt.evt)
- ctx = logWith.Logger().WithContext(ctx)
- if ctxMut, ok := evt.evt.(RemoteEventWithContextMutation); ok {
- ctx = ctxMut.MutateContext(ctx)
- }
- return ctx
case *portalCreateEvent:
return evt.ctx
- default:
- panic(fmt.Errorf("invalid type %T in getEventCtxWithLog", evt))
}
+ return logWith.Logger().WithContext(portal.Bridge.BackgroundCtx)
}
func (portal *Portal) handleSingleEvent(ctx context.Context, rawEvt any, doneCallback func(res EventHandlingResult)) {
@@ -728,8 +692,6 @@ func (portal *Portal) handleMatrixEvent(ctx context.Context, sender *User, evt *
return portal.handleMatrixReceipts(ctx, evt)
case event.EphemeralEventTyping:
return portal.handleMatrixTyping(ctx, evt)
- case event.BeeperEphemeralEventAIStream:
- return portal.handleMatrixAIStream(ctx, sender, evt)
default:
return EventHandlingResultIgnored
}
@@ -974,50 +936,6 @@ func (portal *Portal) handleMatrixTyping(ctx context.Context, evt *event.Event)
return EventHandlingResultSuccess
}
-func (portal *Portal) handleMatrixAIStream(ctx context.Context, sender *User, evt *event.Event) EventHandlingResult {
- log := zerolog.Ctx(ctx)
- if sender == nil {
- log.Error().Msg("Missing sender for Matrix AI stream event")
- return EventHandlingResultIgnored
- }
- login, _, err := portal.FindPreferredLogin(ctx, sender, true)
- if err != nil {
- log.Err(err).Msg("Failed to get user login to handle Matrix AI stream event")
- return EventHandlingResultFailed.WithMSSError(err)
- }
- var origSender *OrigSender
- if login == nil {
- if portal.Relay == nil {
- return EventHandlingResultIgnored
- }
- login = portal.Relay
- origSender = &OrigSender{
- User: sender,
- UserID: sender.MXID,
- }
- }
- content, ok := evt.Content.Parsed.(*event.BeeperAIStreamEventContent)
- if !ok {
- log.Error().Type("content_type", evt.Content.Parsed).Msg("Unexpected parsed content type")
- return EventHandlingResultFailed.WithMSSError(fmt.Errorf("%w: %T", ErrUnexpectedParsedContentType, evt.Content.Parsed))
- }
- api, ok := login.Client.(BeeperAIStreamHandlingNetworkAPI)
- if !ok {
- return EventHandlingResultIgnored.WithMSSError(ErrBeeperAIStreamNotSupported)
- }
- err = api.HandleMatrixBeeperAIStream(ctx, &MatrixBeeperAIStream{
- Event: evt,
- Content: content,
- Portal: portal,
- OrigSender: origSender,
- })
- if err != nil {
- log.Err(err).Msg("Failed to handle Matrix AI stream event")
- return EventHandlingResultFailed.WithMSSError(err)
- }
- return EventHandlingResultSuccess.WithMSS()
-}
-
func (portal *Portal) sendTypings(ctx context.Context, userIDs []id.UserID, typing bool) {
for _, userID := range userIDs {
login, ok := portal.currentlyTypingLogins[userID]
@@ -1305,12 +1223,6 @@ func (portal *Portal) handleMatrixMessage(ctx context.Context, sender *UserLogin
}
}
- err = portal.autoAcceptMessageRequest(ctx, evt, sender, origSender, caps)
- if err != nil {
- log.Warn().Err(err).Msg("Failed to auto-accept message request on message")
- // TODO stop processing?
- }
-
var resp *MatrixMessageResponse
if msgContent != nil {
resp, err = sender.Client.HandleMatrixMessage(ctx, wrappedMsgEvt)
@@ -1590,12 +1502,6 @@ func (portal *Portal) handleMatrixReaction(ctx context.Context, sender *UserLogi
log.Warn().Msg("Reaction target message not found in database")
return EventHandlingResultFailed.WithMSSError(fmt.Errorf("reaction %w", ErrTargetMessageNotFound))
}
- caps := sender.Client.GetCapabilities(ctx, portal)
- err = portal.autoAcceptMessageRequest(ctx, evt, sender, nil, caps)
- if err != nil {
- log.Warn().Err(err).Msg("Failed to auto-accept message request on reaction")
- // TODO stop processing?
- }
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
return c.Str("reaction_target_remote_id", string(reactionTarget.ID))
})
@@ -1618,12 +1524,6 @@ func (portal *Portal) handleMatrixReaction(ctx context.Context, sender *UserLogi
if portal.Bridge.Config.OutgoingMessageReID {
deterministicID = portal.Bridge.Matrix.GenerateReactionEventID(portal.MXID, reactionTarget, preResp.SenderID, preResp.EmojiID)
}
- defer func() {
- // Do this in a defer so that it happens after any potential defer calls to removeOutdatedReaction
- if handleRes.Success {
- portal.sendSuccessStatus(ctx, evt, 0, deterministicID)
- }
- }()
removeOutdatedReaction := func(oldReact *database.Reaction, deleteDB bool) {
if !handleRes.Success {
return
@@ -1669,10 +1569,6 @@ func (portal *Portal) handleMatrixReaction(ctx context.Context, sender *UserLogi
// Keep n-1 previous reactions and remove the rest
react.ExistingReactionsToKeep = allReactions[:preResp.MaxReactions-1]
for _, oldReaction := range allReactions[preResp.MaxReactions-1:] {
- if existing != nil && oldReaction.EmojiID == existing.EmojiID {
- // Don't double-delete on networks that only allow one emoji
- continue
- }
// Intentionally defer in a loop, there won't be that many items,
// and we want all of them to be done after this function completes successfully
//goland:noinspection GoDeferInLoop
@@ -1721,6 +1617,7 @@ func (portal *Portal) handleMatrixReaction(ctx context.Context, sender *UserLogi
if err != nil {
log.Err(err).Msg("Failed to save reaction to database")
}
+ portal.sendSuccessStatus(ctx, evt, 0, deterministicID)
return EventHandlingResultSuccess.WithEventID(deterministicID)
}
@@ -1904,38 +1801,6 @@ func (portal *Portal) handleMatrixAcceptMessageRequest(
return EventHandlingResultSuccess.WithMSS()
}
-func (portal *Portal) autoAcceptMessageRequest(
- ctx context.Context, evt *event.Event, sender *UserLogin, origSender *OrigSender, caps *event.RoomFeatures,
-) error {
- if !portal.MessageRequest || caps.MessageRequest == nil || caps.MessageRequest.AcceptWithMessage == event.CapLevelFullySupported {
- return nil
- }
- mran, ok := sender.Client.(MessageRequestAcceptingNetworkAPI)
- if !ok {
- return nil
- }
- err := mran.HandleMatrixAcceptMessageRequest(ctx, &MatrixAcceptMessageRequest{
- Event: evt,
- Content: &event.BeeperAcceptMessageRequestEventContent{
- IsImplicit: true,
- },
- Portal: portal,
- OrigSender: origSender,
- })
- if err != nil {
- return err
- }
- if portal.MessageRequest {
- portal.MessageRequest = false
- portal.UpdateBridgeInfo(ctx)
- err = portal.Save(ctx)
- if err != nil {
- zerolog.Ctx(ctx).Err(err).Msg("Failed to save portal after accepting message request")
- }
- }
- return nil
-}
-
func (portal *Portal) handleMatrixDeleteChat(
ctx context.Context,
sender *UserLogin,
@@ -2792,7 +2657,7 @@ func (portal *Portal) getRelationMeta(
log.Err(err).Msg("Failed to get last thread message from database")
}
if prevThreadEvent == nil {
- prevThreadEvent = ptr.Clone(threadRoot)
+ prevThreadEvent = threadRoot
}
}
return
@@ -3730,7 +3595,7 @@ func (portal *Portal) handleRemoteMarkUnread(ctx context.Context, source *UserLo
}
func (portal *Portal) handleRemoteDeliveryReceipt(ctx context.Context, source *UserLogin, evt RemoteDeliveryReceipt) EventHandlingResult {
- if portal.RoomType != database.RoomTypeDM || (evt.GetSender().Sender != portal.OtherUserID && portal.OtherUserID != "") {
+ if portal.RoomType != database.RoomTypeDM || evt.GetSender().Sender != portal.OtherUserID {
return EventHandlingResultIgnored
}
intent, ok := portal.GetIntentFor(ctx, evt.GetSender(), source, RemoteEventDeliveryReceipt)
@@ -5403,9 +5268,6 @@ func (portal *Portal) removeInPortalCache(ctx context.Context) {
}
func (portal *Portal) unlockedDelete(ctx context.Context) error {
- if portal.deleted.IsSet() {
- return nil
- }
err := portal.safeDBDelete(ctx)
if err != nil {
return err
diff --git a/bridgev2/portalreid.go b/bridgev2/portalreid.go
index c976d97c..6a5091fc 100644
--- a/bridgev2/portalreid.go
+++ b/bridgev2/portalreid.go
@@ -38,20 +38,17 @@ func (br *Bridge) ReIDPortal(ctx context.Context, source, target networkid.Porta
Stringer("target_portal_key", target).
Logger()
ctx = log.WithContext(ctx)
+ if !br.cacheLock.TryLock() {
+ log.Debug().Msg("Waiting for cache lock")
+ br.cacheLock.Lock()
+ log.Debug().Msg("Acquired cache lock after waiting")
+ }
defer func() {
+ br.cacheLock.Unlock()
log.Debug().Msg("Finished handling portal re-ID")
}()
- acquireCacheLock := func() {
- if !br.cacheLock.TryLock() {
- log.Debug().Msg("Waiting for global cache lock")
- br.cacheLock.Lock()
- log.Debug().Msg("Acquired global cache lock after waiting")
- } else {
- log.Trace().Msg("Acquired global cache lock without waiting")
- }
- }
log.Debug().Msg("Re-ID'ing portal")
- sourcePortal, err := br.GetExistingPortalByKey(ctx, source)
+ sourcePortal, err := br.UnlockedGetPortalByKey(ctx, source, true)
if err != nil {
return ReIDResultError, nil, fmt.Errorf("failed to get source portal: %w", err)
} else if sourcePortal == nil {
@@ -78,24 +75,18 @@ func (br *Bridge) ReIDPortal(ctx context.Context, source, target networkid.Porta
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
return c.Stringer("source_portal_mxid", sourcePortal.MXID)
})
-
- acquireCacheLock()
targetPortal, err := br.UnlockedGetPortalByKey(ctx, target, true)
if err != nil {
- br.cacheLock.Unlock()
return ReIDResultError, nil, fmt.Errorf("failed to get target portal: %w", err)
}
if targetPortal == nil {
log.Info().Msg("Target portal doesn't exist, re-ID'ing source portal")
err = sourcePortal.unlockedReID(ctx, target)
- br.cacheLock.Unlock()
if err != nil {
return ReIDResultError, nil, fmt.Errorf("failed to re-ID source portal: %w", err)
}
return ReIDResultSourceReIDd, sourcePortal, nil
}
- br.cacheLock.Unlock()
-
if !targetPortal.roomCreateLock.TryLock() {
if cancelCreate := targetPortal.cancelRoomCreate.Swap(nil); cancelCreate != nil {
(*cancelCreate)()
@@ -107,8 +98,6 @@ func (br *Bridge) ReIDPortal(ctx context.Context, source, target networkid.Porta
defer targetPortal.roomCreateLock.Unlock()
if targetPortal.MXID == "" {
log.Info().Msg("Target portal row exists, but doesn't have a Matrix room. Deleting target portal row and re-ID'ing source portal")
- acquireCacheLock()
- defer br.cacheLock.Unlock()
err = targetPortal.unlockedDelete(ctx)
if err != nil {
return ReIDResultError, nil, fmt.Errorf("failed to delete target portal: %w", err)
@@ -123,9 +112,6 @@ func (br *Bridge) ReIDPortal(ctx context.Context, source, target networkid.Porta
return c.Stringer("target_portal_mxid", targetPortal.MXID)
})
log.Info().Msg("Both target and source portals have Matrix rooms, tombstoning source portal")
- sourcePortal.removeInPortalCache(ctx)
- acquireCacheLock()
- defer br.cacheLock.Unlock()
err = sourcePortal.unlockedDelete(ctx)
if err != nil {
return ReIDResultError, nil, fmt.Errorf("failed to delete source portal row: %w", err)
diff --git a/bridgev2/provisionutil/creategroup.go b/bridgev2/provisionutil/creategroup.go
index 72bacaff..fbe0a513 100644
--- a/bridgev2/provisionutil/creategroup.go
+++ b/bridgev2/provisionutil/creategroup.go
@@ -32,9 +32,6 @@ func CreateGroup(ctx context.Context, login *bridgev2.UserLogin, params *bridgev
if !ok {
return nil, bridgev2.RespError(mautrix.MUnrecognized.WithMessage("This bridge does not support creating groups"))
}
- zerolog.Ctx(ctx).Debug().
- Any("create_params", params).
- Msg("Creating group chat on remote network")
caps := login.Bridge.Network.GetCapabilities()
typeSpec, validType := caps.Provisioning.GroupCreation[params.Type]
if !validType {
@@ -101,9 +98,6 @@ func CreateGroup(ctx context.Context, login *bridgev2.UserLogin, params *bridgev
if resp.PortalKey.IsEmpty() {
return nil, ErrNoPortalKey
}
- zerolog.Ctx(ctx).Debug().
- Object("portal_key", resp.PortalKey).
- Msg("Successfully created group on remote network")
if resp.Portal == nil {
resp.Portal, err = login.Bridge.GetPortalByKey(ctx, resp.PortalKey)
if err != nil {
diff --git a/bridgev2/simplevent/meta.go b/bridgev2/simplevent/meta.go
index 96c8a9c5..449a8773 100644
--- a/bridgev2/simplevent/meta.go
+++ b/bridgev2/simplevent/meta.go
@@ -27,9 +27,8 @@ type EventMeta struct {
Timestamp time.Time
StreamOrder int64
- PreHandleFunc func(context.Context, *bridgev2.Portal)
- PostHandleFunc func(context.Context, *bridgev2.Portal)
- MutateContextFunc func(context.Context) context.Context
+ PreHandleFunc func(context.Context, *bridgev2.Portal)
+ PostHandleFunc func(context.Context, *bridgev2.Portal)
}
var (
@@ -40,7 +39,6 @@ var (
_ bridgev2.RemoteEventWithStreamOrder = (*EventMeta)(nil)
_ bridgev2.RemotePreHandler = (*EventMeta)(nil)
_ bridgev2.RemotePostHandler = (*EventMeta)(nil)
- _ bridgev2.RemoteEventWithContextMutation = (*EventMeta)(nil)
)
func (evt *EventMeta) AddLogContext(c zerolog.Context) zerolog.Context {
@@ -93,13 +91,6 @@ func (evt *EventMeta) PostHandle(ctx context.Context, portal *bridgev2.Portal) {
}
}
-func (evt *EventMeta) MutateContext(ctx context.Context) context.Context {
- if evt.MutateContextFunc == nil {
- return ctx
- }
- return evt.MutateContextFunc(ctx)
-}
-
func (evt EventMeta) WithType(t bridgev2.RemoteEventType) EventMeta {
evt.Type = t
return evt
diff --git a/bridgev2/status/bridgestate.go b/bridgev2/status/bridgestate.go
index 5925dd4f..430d4c7c 100644
--- a/bridgev2/status/bridgestate.go
+++ b/bridgev2/status/bridgestate.go
@@ -19,6 +19,7 @@ import (
"github.com/tidwall/sjson"
"go.mau.fi/util/jsontime"
+ "go.mau.fi/util/ptr"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/bridgev2/networkid"
@@ -111,7 +112,7 @@ func (rp *RemoteProfile) Merge(other RemoteProfile) RemoteProfile {
return other
}
-func (rp *RemoteProfile) IsZero() bool {
+func (rp *RemoteProfile) IsEmpty() bool {
return rp == nil || (rp.Phone == "" && rp.Email == "" && rp.Username == "" && rp.Name == "" && rp.Avatar == "" && rp.AvatarFile == nil)
}
@@ -129,7 +130,7 @@ type BridgeState struct {
UserID id.UserID `json:"user_id,omitempty"`
RemoteID networkid.UserLoginID `json:"remote_id,omitempty"`
RemoteName string `json:"remote_name,omitempty"`
- RemoteProfile RemoteProfile `json:"remote_profile,omitzero"`
+ RemoteProfile *RemoteProfile `json:"remote_profile,omitempty"`
Reason string `json:"reason,omitempty"`
Info map[string]interface{} `json:"info,omitempty"`
@@ -209,7 +210,7 @@ func (pong *BridgeState) ShouldDeduplicate(newPong *BridgeState) bool {
pong.StateEvent == newPong.StateEvent &&
pong.RemoteName == newPong.RemoteName &&
pong.UserAction == newPong.UserAction &&
- pong.RemoteProfile == newPong.RemoteProfile &&
+ ptr.Val(pong.RemoteProfile) == ptr.Val(newPong.RemoteProfile) &&
pong.Error == newPong.Error &&
maps.EqualFunc(pong.Info, newPong.Info, reflect.DeepEqual) &&
pong.Timestamp.Add(time.Duration(pong.TTL)*time.Second).After(time.Now())
diff --git a/bridgev2/userlogin.go b/bridgev2/userlogin.go
index d56dc4cc..c9102248 100644
--- a/bridgev2/userlogin.go
+++ b/bridgev2/userlogin.go
@@ -51,8 +51,6 @@ func (br *Bridge) loadUserLogin(ctx context.Context, user *User, dbUserLogin *da
if err != nil {
return nil, fmt.Errorf("failed to get user: %w", err)
}
- // TODO if loading the user caused the provided userlogin to be loaded, cancel here?
- // Currently this will double-load it
}
userLogin := &UserLogin{
UserLogin: dbUserLogin,
@@ -512,7 +510,7 @@ func (ul *UserLogin) FillBridgeState(state status.BridgeState) status.BridgeStat
state.UserID = ul.UserMXID
state.RemoteID = ul.ID
state.RemoteName = ul.RemoteName
- state.RemoteProfile = ul.RemoteProfile
+ state.RemoteProfile = &ul.RemoteProfile
filler, ok := ul.Client.(status.BridgeStateFiller)
if ok {
return filler.FillBridgeState(state)
diff --git a/client.go b/client.go
index 045d7b8e..87b6d87e 100644
--- a/client.go
+++ b/client.go
@@ -386,14 +386,7 @@ func (cli *Client) LogRequestDone(req *http.Request, resp *http.Response, err er
}
}
if body := req.Context().Value(LogBodyContextKey); body != nil {
- switch typedLogBody := body.(type) {
- case json.RawMessage:
- evt.RawJSON("req_body", typedLogBody)
- case string:
- evt.Str("req_body", typedLogBody)
- default:
- panic(fmt.Errorf("invalid type for LogBodyContextKey: %T", body))
- }
+ evt.Interface("req_body", body)
}
if errors.Is(err, context.Canceled) {
evt.Msg("Request canceled")
@@ -457,10 +450,8 @@ func (params *FullRequest) compileRequest(ctx context.Context) (*http.Request, e
}
if params.SensitiveContent && !logSensitiveContent {
logBody = ""
- } else if len(jsonStr) > 32768 {
- logBody = fmt.Sprintf("", len(jsonStr))
} else {
- logBody = json.RawMessage(jsonStr)
+ logBody = params.RequestJSON
}
reqBody = bytes.NewReader(jsonStr)
reqLen = int64(len(jsonStr))
@@ -485,7 +476,7 @@ func (params *FullRequest) compileRequest(ctx context.Context) (*http.Request, e
}
} else if params.Method != http.MethodGet && params.Method != http.MethodHead {
params.RequestJSON = struct{}{}
- logBody = json.RawMessage("{}")
+ logBody = params.RequestJSON
reqBody = bytes.NewReader([]byte("{}"))
reqLen = 2
}
@@ -918,7 +909,7 @@ func (cli *Client) RegisterAvailable(ctx context.Context, username string) (resp
return
}
-func (cli *Client) register(ctx context.Context, url string, req *ReqRegister[any]) (resp *RespRegister, uiaResp *RespUserInteractive, err error) {
+func (cli *Client) register(ctx context.Context, url string, req *ReqRegister) (resp *RespRegister, uiaResp *RespUserInteractive, err error) {
var bodyBytes []byte
bodyBytes, err = cli.MakeFullRequest(ctx, FullRequest{
Method: http.MethodPost,
@@ -942,7 +933,7 @@ func (cli *Client) register(ctx context.Context, url string, req *ReqRegister[an
// Register makes an HTTP request according to https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3register
//
// Registers with kind=user. For kind=guest, see RegisterGuest.
-func (cli *Client) Register(ctx context.Context, req *ReqRegister[any]) (*RespRegister, *RespUserInteractive, error) {
+func (cli *Client) Register(ctx context.Context, req *ReqRegister) (*RespRegister, *RespUserInteractive, error) {
u := cli.BuildClientURL("v3", "register")
return cli.register(ctx, u, req)
}
@@ -951,7 +942,7 @@ func (cli *Client) Register(ctx context.Context, req *ReqRegister[any]) (*RespRe
// with kind=guest.
//
// For kind=user, see Register.
-func (cli *Client) RegisterGuest(ctx context.Context, req *ReqRegister[any]) (*RespRegister, *RespUserInteractive, error) {
+func (cli *Client) RegisterGuest(ctx context.Context, req *ReqRegister) (*RespRegister, *RespUserInteractive, error) {
query := map[string]string{
"kind": "guest",
}
@@ -974,7 +965,7 @@ func (cli *Client) RegisterGuest(ctx context.Context, req *ReqRegister[any]) (*R
// panic(err)
// }
// token := res.AccessToken
-func (cli *Client) RegisterDummy(ctx context.Context, req *ReqRegister[any]) (*RespRegister, error) {
+func (cli *Client) RegisterDummy(ctx context.Context, req *ReqRegister) (*RespRegister, error) {
_, uia, err := cli.Register(ctx, req)
if err != nil && uia == nil {
return nil, err
@@ -1158,9 +1149,7 @@ func (cli *Client) SearchUserDirectory(ctx context.Context, query string, limit
}
func (cli *Client) GetMutualRooms(ctx context.Context, otherUserID id.UserID, extras ...ReqMutualRooms) (resp *RespMutualRooms, err error) {
- supportsStable := cli.SpecVersions.Supports(FeatureStableMutualRooms)
- supportsUnstable := cli.SpecVersions.Supports(FeatureUnstableMutualRooms)
- if cli.SpecVersions != nil && !supportsUnstable && !supportsStable {
+ if cli.SpecVersions != nil && !cli.SpecVersions.Supports(FeatureMutualRooms) {
err = fmt.Errorf("server does not support fetching mutual rooms")
return
}
@@ -1170,10 +1159,7 @@ func (cli *Client) GetMutualRooms(ctx context.Context, otherUserID id.UserID, ex
if len(extras) > 0 {
query["from"] = extras[0].From
}
- urlPath := cli.BuildURLWithQuery(ClientURLPath{"v1", "mutual_rooms"}, query)
- if !supportsStable && supportsUnstable {
- urlPath = cli.BuildURLWithQuery(ClientURLPath{"unstable", "uk.half-shot.msc2666", "user", "mutual_rooms"}, query)
- }
+ urlPath := cli.BuildURLWithQuery(ClientURLPath{"unstable", "uk.half-shot.msc2666", "user", "mutual_rooms"}, query)
_, err = cli.MakeRequest(ctx, http.MethodGet, urlPath, nil, &resp)
return
}
@@ -1338,9 +1324,6 @@ func (cli *Client) SendMessageEvent(ctx context.Context, roomID id.RoomID, event
if req.UnstableDelay > 0 {
queryParams["org.matrix.msc4140.delay"] = strconv.FormatInt(req.UnstableDelay.Milliseconds(), 10)
}
- if req.UnstableStickyDuration > 0 {
- queryParams["org.matrix.msc4354.sticky_duration_ms"] = strconv.FormatInt(req.UnstableStickyDuration.Milliseconds(), 10)
- }
if !req.DontEncrypt && cli != nil && cli.Crypto != nil && eventType != event.EventReaction && eventType != event.EventEncrypted {
var isEncrypted bool
@@ -1364,48 +1347,6 @@ func (cli *Client) SendMessageEvent(ctx context.Context, roomID id.RoomID, event
return
}
-// BeeperSendEphemeralEvent sends an ephemeral event into a room using Beeper's unstable endpoint.
-// contentJSON should be a value that can be encoded as JSON using json.Marshal.
-func (cli *Client) BeeperSendEphemeralEvent(ctx context.Context, roomID id.RoomID, eventType event.Type, contentJSON any, extra ...ReqSendEvent) (resp *RespSendEvent, err error) {
- var req ReqSendEvent
- if len(extra) > 0 {
- req = extra[0]
- }
-
- var txnID string
- if len(req.TransactionID) > 0 {
- txnID = req.TransactionID
- } else {
- txnID = cli.TxnID()
- }
-
- queryParams := map[string]string{}
- if req.Timestamp > 0 {
- queryParams["ts"] = strconv.FormatInt(req.Timestamp, 10)
- }
-
- if !req.DontEncrypt && cli != nil && cli.Crypto != nil && eventType != event.EventEncrypted {
- var isEncrypted bool
- isEncrypted, err = cli.StateStore.IsEncrypted(ctx, roomID)
- if err != nil {
- err = fmt.Errorf("failed to check if room is encrypted: %w", err)
- return
- }
- if isEncrypted {
- if contentJSON, err = cli.Crypto.Encrypt(ctx, roomID, eventType, contentJSON); err != nil {
- err = fmt.Errorf("failed to encrypt event: %w", err)
- return
- }
- eventType = event.EventEncrypted
- }
- }
-
- urlData := ClientURLPath{"unstable", "com.beeper.ephemeral", "rooms", roomID, "ephemeral", eventType.String(), txnID}
- urlPath := cli.BuildURLWithQuery(urlData, queryParams)
- _, err = cli.MakeRequest(ctx, http.MethodPut, urlPath, contentJSON, &resp)
- return
-}
-
// SendStateEvent sends a state event into a room. See https://spec.matrix.org/v1.16/client-server-api/#put_matrixclientv3roomsroomidstateeventtypestatekey
// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal.
func (cli *Client) SendStateEvent(ctx context.Context, roomID id.RoomID, eventType event.Type, stateKey string, contentJSON any, extra ...ReqSendEvent) (resp *RespSendEvent, err error) {
@@ -1424,9 +1365,6 @@ func (cli *Client) SendStateEvent(ctx context.Context, roomID id.RoomID, eventTy
if req.UnstableDelay > 0 {
queryParams["org.matrix.msc4140.delay"] = strconv.FormatInt(req.UnstableDelay.Milliseconds(), 10)
}
- if req.UnstableStickyDuration > 0 {
- queryParams["org.matrix.msc4354.sticky_duration_ms"] = strconv.FormatInt(req.UnstableStickyDuration.Milliseconds(), 10)
- }
if req.Timestamp > 0 {
queryParams["ts"] = strconv.FormatInt(req.Timestamp, 10)
}
@@ -1989,15 +1927,10 @@ func (cli *Client) UploadAsync(ctx context.Context, req ReqUploadMedia) (*RespCr
}
req.MXC = resp.ContentURI
req.UnstableUploadURL = resp.UnstableUploadURL
- if req.AsyncContext == nil {
- req.AsyncContext = cli.cliOrContextLog(ctx).WithContext(context.Background())
- }
go func() {
- _, err = cli.UploadMedia(req.AsyncContext, req)
+ _, err = cli.UploadMedia(ctx, req)
if err != nil {
- zerolog.Ctx(req.AsyncContext).Err(err).
- Stringer("mxc", req.MXC).
- Msg("Async upload of media failed")
+ cli.Log.Error().Stringer("mxc", req.MXC).Err(err).Msg("Async upload of media failed")
}
}()
return resp, nil
@@ -2033,7 +1966,6 @@ type ReqUploadMedia struct {
ContentType string
FileName string
- AsyncContext context.Context
DoneCallback func()
// MXC specifies an existing MXC URI which doesn't have content yet to upload into.
@@ -2046,10 +1978,7 @@ type ReqUploadMedia struct {
}
func (cli *Client) tryUploadMediaToURL(ctx context.Context, url, contentType string, content io.Reader, contentLength int64) (*http.Response, error) {
- cli.Log.Debug().
- Str("url", url).
- Int64("content_length", contentLength).
- Msg("Uploading media to external URL")
+ cli.Log.Debug().Str("url", url).Msg("Uploading media to external URL")
req, err := http.NewRequestWithContext(ctx, http.MethodPut, url, content)
if err != nil {
return nil, err
@@ -2687,13 +2616,13 @@ func (cli *Client) SetDeviceInfo(ctx context.Context, deviceID id.DeviceID, req
return err
}
-func (cli *Client) DeleteDevice(ctx context.Context, deviceID id.DeviceID, req *ReqDeleteDevice[any]) error {
+func (cli *Client) DeleteDevice(ctx context.Context, deviceID id.DeviceID, req *ReqDeleteDevice) error {
urlPath := cli.BuildClientURL("v3", "devices", deviceID)
_, err := cli.MakeRequest(ctx, http.MethodDelete, urlPath, req, nil)
return err
}
-func (cli *Client) DeleteDevices(ctx context.Context, req *ReqDeleteDevices[any]) error {
+func (cli *Client) DeleteDevices(ctx context.Context, req *ReqDeleteDevices) error {
urlPath := cli.BuildClientURL("v3", "delete_devices")
_, err := cli.MakeRequest(ctx, http.MethodPost, urlPath, req, nil)
return err
@@ -2704,7 +2633,7 @@ type UIACallback = func(*RespUserInteractive) interface{}
// UploadCrossSigningKeys uploads the given cross-signing keys to the server.
// Because the endpoint requires user-interactive authentication a callback must be provided that,
// given the UI auth parameters, produces the required result (or nil to end the flow).
-func (cli *Client) UploadCrossSigningKeys(ctx context.Context, keys *UploadCrossSigningKeysReq[any], uiaCallback UIACallback) error {
+func (cli *Client) UploadCrossSigningKeys(ctx context.Context, keys *UploadCrossSigningKeysReq, uiaCallback UIACallback) error {
content, err := cli.MakeFullRequest(ctx, FullRequest{
Method: http.MethodPost,
URL: cli.BuildClientURL("v3", "keys", "device_signing", "upload"),
diff --git a/client_ephemeral_test.go b/client_ephemeral_test.go
deleted file mode 100644
index c2846427..00000000
--- a/client_ephemeral_test.go
+++ /dev/null
@@ -1,158 +0,0 @@
-// Copyright (c) 2026 Tulir Asokan
-//
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-package mautrix_test
-
-import (
- "context"
- "encoding/json"
- "errors"
- "net/http"
- "net/http/httptest"
- "strings"
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "maunium.net/go/mautrix"
- "maunium.net/go/mautrix/event"
- "maunium.net/go/mautrix/id"
-)
-
-func TestClient_SendEphemeralEvent_UsesUnstablePathTxnAndTS(t *testing.T) {
- roomID := id.RoomID("!room:example.com")
- evtType := event.Type{Type: "com.example.ephemeral", Class: event.EphemeralEventType}
- txnID := "txn-123"
-
- var gotPath string
- var gotQueryTS string
- ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- gotPath = r.URL.Path
- gotQueryTS = r.URL.Query().Get("ts")
- assert.Equal(t, http.MethodPut, r.Method)
- _, _ = w.Write([]byte(`{"event_id":"$evt"}`))
- }))
- defer ts.Close()
-
- cli, err := mautrix.NewClient(ts.URL, "", "")
- require.NoError(t, err)
-
- _, err = cli.BeeperSendEphemeralEvent(
- context.Background(),
- roomID,
- evtType,
- map[string]any{"foo": "bar"},
- mautrix.ReqSendEvent{TransactionID: txnID, Timestamp: 1234},
- )
- require.NoError(t, err)
-
- assert.True(t, strings.Contains(gotPath, "/_matrix/client/unstable/com.beeper.ephemeral/rooms/"))
- assert.True(t, strings.HasSuffix(gotPath, "/ephemeral/com.example.ephemeral/"+txnID))
- assert.Equal(t, "1234", gotQueryTS)
-}
-
-func TestClient_SendEphemeralEvent_UnsupportedReturnsMUnrecognized(t *testing.T) {
- ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
- w.WriteHeader(http.StatusNotFound)
- _, _ = w.Write([]byte(`{"errcode":"M_UNRECOGNIZED","error":"Unrecognized endpoint"}`))
- }))
- defer ts.Close()
-
- cli, err := mautrix.NewClient(ts.URL, "", "")
- require.NoError(t, err)
-
- _, err = cli.BeeperSendEphemeralEvent(
- context.Background(),
- id.RoomID("!room:example.com"),
- event.Type{Type: "com.example.ephemeral", Class: event.EphemeralEventType},
- map[string]any{"foo": "bar"},
- )
- require.Error(t, err)
- assert.True(t, errors.Is(err, mautrix.MUnrecognized))
-}
-
-func TestClient_SendEphemeralEvent_EncryptsInEncryptedRooms(t *testing.T) {
- roomID := id.RoomID("!room:example.com")
- evtType := event.Type{Type: "com.example.ephemeral", Class: event.EphemeralEventType}
- txnID := "txn-encrypted"
-
- stateStore := mautrix.NewMemoryStateStore()
- err := stateStore.SetEncryptionEvent(context.Background(), roomID, &event.EncryptionEventContent{
- Algorithm: id.AlgorithmMegolmV1,
- })
- require.NoError(t, err)
-
- fakeCrypto := &fakeCryptoHelper{
- encryptedContent: &event.EncryptedEventContent{
- Algorithm: id.AlgorithmMegolmV1,
- MegolmCiphertext: []byte("ciphertext"),
- },
- }
-
- var gotPath string
- var gotBody map[string]any
- ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- gotPath = r.URL.Path
- assert.Equal(t, http.MethodPut, r.Method)
- err := json.NewDecoder(r.Body).Decode(&gotBody)
- require.NoError(t, err)
- _, _ = w.Write([]byte(`{"event_id":"$evt"}`))
- }))
- defer ts.Close()
-
- cli, err := mautrix.NewClient(ts.URL, "", "")
- require.NoError(t, err)
- cli.StateStore = stateStore
- cli.Crypto = fakeCrypto
-
- _, err = cli.BeeperSendEphemeralEvent(
- context.Background(),
- roomID,
- evtType,
- map[string]any{"foo": "bar"},
- mautrix.ReqSendEvent{TransactionID: txnID},
- )
- require.NoError(t, err)
-
- assert.True(t, strings.HasSuffix(gotPath, "/ephemeral/m.room.encrypted/"+txnID))
- assert.Equal(t, string(id.AlgorithmMegolmV1), gotBody["algorithm"])
- assert.Equal(t, 1, fakeCrypto.encryptCalls)
- assert.Equal(t, roomID, fakeCrypto.lastRoomID)
- assert.Equal(t, evtType, fakeCrypto.lastEventType)
-}
-
-type fakeCryptoHelper struct {
- encryptCalls int
- lastRoomID id.RoomID
- lastEventType event.Type
- lastEncryptInput any
- encryptedContent *event.EncryptedEventContent
-}
-
-func (f *fakeCryptoHelper) Encrypt(_ context.Context, roomID id.RoomID, eventType event.Type, content any) (*event.EncryptedEventContent, error) {
- f.encryptCalls++
- f.lastRoomID = roomID
- f.lastEventType = eventType
- f.lastEncryptInput = content
- return f.encryptedContent, nil
-}
-
-func (f *fakeCryptoHelper) Decrypt(context.Context, *event.Event) (*event.Event, error) {
- return nil, nil
-}
-
-func (f *fakeCryptoHelper) WaitForSession(context.Context, id.RoomID, id.SenderKey, id.SessionID, time.Duration) bool {
- return false
-}
-
-func (f *fakeCryptoHelper) RequestSession(context.Context, id.RoomID, id.SenderKey, id.SessionID, id.UserID, id.DeviceID) {
-}
-
-func (f *fakeCryptoHelper) Init(context.Context) error {
- return nil
-}
diff --git a/crypto/cross_sign_key.go b/crypto/cross_sign_key.go
index 5d9bf5b3..4094f695 100644
--- a/crypto/cross_sign_key.go
+++ b/crypto/cross_sign_key.go
@@ -135,7 +135,7 @@ func (mach *OlmMachine) PublishCrossSigningKeys(ctx context.Context, keys *Cross
}
userKey.Signatures = signatures.NewSingleSignature(userID, id.KeyAlgorithmEd25519, keys.MasterKey.PublicKey().String(), userSig)
- err = mach.Client.UploadCrossSigningKeys(ctx, &mautrix.UploadCrossSigningKeysReq[any]{
+ err = mach.Client.UploadCrossSigningKeys(ctx, &mautrix.UploadCrossSigningKeysReq{
Master: masterKey,
SelfSigning: selfKey,
UserSigning: userKey,
diff --git a/crypto/cross_sign_ssss.go b/crypto/cross_sign_ssss.go
index fd42880d..50b58ea0 100644
--- a/crypto/cross_sign_ssss.go
+++ b/crypto/cross_sign_ssss.go
@@ -8,7 +8,6 @@ package crypto
import (
"context"
- "errors"
"fmt"
"maunium.net/go/mautrix"
@@ -78,11 +77,7 @@ func (mach *OlmMachine) VerifyWithRecoveryKey(ctx context.Context, recoveryKey s
return fmt.Errorf("failed to get default SSSS key data: %w", err)
}
key, err := keyData.VerifyRecoveryKey(keyID, recoveryKey)
- if errors.Is(err, ssss.ErrUnverifiableKey) {
- mach.machOrContextLog(ctx).Warn().
- Str("key_id", keyID).
- Msg("SSSS key is unverifiable, trying to use without verifying")
- } else if err != nil {
+ if err != nil {
return err
}
err = mach.FetchCrossSigningKeysFromSSSS(ctx, key)
diff --git a/crypto/decryptmegolm.go b/crypto/decryptmegolm.go
index 457d5a0c..d8b419ab 100644
--- a/crypto/decryptmegolm.go
+++ b/crypto/decryptmegolm.go
@@ -29,8 +29,8 @@ var (
ErrDuplicateMessageIndex = errors.New("duplicate megolm message index")
ErrWrongRoom = errors.New("encrypted megolm event is not intended for this room")
ErrDeviceKeyMismatch = errors.New("device keys in event and verified device info do not match")
+ ErrSenderKeyMismatch = errors.New("sender keys in content and megolm session do not match")
ErrRatchetError = errors.New("failed to ratchet session after use")
- ErrCorruptedMegolmPayload = errors.New("corrupted megolm payload")
)
// Deprecated: use variables prefixed with Err
@@ -40,6 +40,7 @@ var (
DuplicateMessageIndex = ErrDuplicateMessageIndex
WrongRoom = ErrWrongRoom
DeviceKeyMismatch = ErrDeviceKeyMismatch
+ SenderKeyMismatch = ErrSenderKeyMismatch
RatchetError = ErrRatchetError
)
@@ -55,17 +56,6 @@ var (
relatesToTopLevelPath = exgjson.Path("content", "m.relates_to")
)
-const sessionIDLength = 43
-
-func validateCiphertextCharacters(ciphertext []byte) bool {
- for _, b := range ciphertext {
- if (b < 'a' || b > 'z') && (b < 'A' || b > 'Z') && (b < '0' || b > '9') && b != '+' && b != '/' {
- return false
- }
- }
- return true
-}
-
// DecryptMegolmEvent decrypts an m.room.encrypted event where the algorithm is m.megolm.v1.aes-sha2
func (mach *OlmMachine) DecryptMegolmEvent(ctx context.Context, evt *event.Event) (*event.Event, error) {
content, ok := evt.Content.Parsed.(*event.EncryptedEventContent)
@@ -73,12 +63,6 @@ func (mach *OlmMachine) DecryptMegolmEvent(ctx context.Context, evt *event.Event
return nil, ErrIncorrectEncryptedContentType
} else if content.Algorithm != id.AlgorithmMegolmV1 {
return nil, ErrUnsupportedAlgorithm
- } else if len(content.MegolmCiphertext) < 74 {
- return nil, fmt.Errorf("%w: ciphertext too short (%d bytes)", ErrCorruptedMegolmPayload, len(content.MegolmCiphertext))
- } else if len(content.SessionID) != sessionIDLength {
- return nil, fmt.Errorf("%w: invalid session ID length %d", ErrCorruptedMegolmPayload, len(content.SessionID))
- } else if !validateCiphertextCharacters(content.MegolmCiphertext) {
- return nil, fmt.Errorf("%w: invalid characters in ciphertext", ErrCorruptedMegolmPayload)
}
log := mach.machOrContextLog(ctx).With().
Str("action", "decrypt megolm event").
@@ -124,13 +108,7 @@ func (mach *OlmMachine) DecryptMegolmEvent(ctx context.Context, evt *event.Event
Msg("Couldn't resolve trust level of session: sent by unknown device")
trustLevel = id.TrustStateUnknownDevice
} else if device.SigningKey != sess.SigningKey || device.IdentityKey != sess.SenderKey {
- log.Debug().
- Stringer("session_sender_key", sess.SenderKey).
- Stringer("device_sender_key", device.IdentityKey).
- Stringer("session_signing_key", sess.SigningKey).
- Stringer("device_signing_key", device.SigningKey).
- Msg("Device keys don't match keys in session, marking as untrusted")
- trustLevel = id.TrustStateDeviceKeyMismatch
+ return nil, ErrDeviceKeyMismatch
} else {
trustLevel, err = mach.ResolveTrustContext(ctx, device)
if err != nil {
@@ -213,7 +191,6 @@ func (mach *OlmMachine) DecryptMegolmEvent(ctx context.Context, evt *event.Event
TrustSource: device,
ForwardedKeys: forwardedKeys,
WasEncrypted: true,
- EventSource: evt.Mautrix.EventSource | event.SourceDecrypted,
ReceivedAt: evt.Mautrix.ReceivedAt,
},
}, nil
@@ -259,6 +236,8 @@ func (mach *OlmMachine) actuallyDecryptMegolmEvent(ctx context.Context, evt *eve
return nil, nil, 0, fmt.Errorf("failed to get group session: %w", err)
} else if sess == nil {
return nil, nil, 0, fmt.Errorf("%w (ID %s)", ErrNoSessionFound, content.SessionID)
+ } else if content.SenderKey != "" && content.SenderKey != sess.SenderKey {
+ return sess, nil, 0, ErrSenderKeyMismatch
}
plaintext, messageIndex, err := sess.Internal.Decrypt(content.MegolmCiphertext)
if err != nil {
diff --git a/crypto/decryptolm.go b/crypto/decryptolm.go
index aea5e6dc..cd02726d 100644
--- a/crypto/decryptolm.go
+++ b/crypto/decryptolm.go
@@ -134,9 +134,6 @@ func (mach *OlmMachine) decryptAndParseOlmCiphertext(ctx context.Context, evt *e
}
func olmMessageHash(ciphertext string) ([32]byte, error) {
- if ciphertext == "" {
- return [32]byte{}, fmt.Errorf("empty ciphertext")
- }
ciphertextBytes, err := base64.RawStdEncoding.DecodeString(ciphertext)
return sha256.Sum256(ciphertextBytes), err
}
diff --git a/crypto/encryptmegolm.go b/crypto/encryptmegolm.go
index 88f9c8d4..8ce70ca0 100644
--- a/crypto/encryptmegolm.go
+++ b/crypto/encryptmegolm.go
@@ -91,16 +91,11 @@ func IsShareError(err error) bool {
}
func ParseMegolmMessageIndex(ciphertext []byte) (uint, error) {
- if len(ciphertext) == 0 {
- return 0, fmt.Errorf("empty ciphertext")
- }
decoded := make([]byte, base64.RawStdEncoding.DecodedLen(len(ciphertext)))
var err error
_, err = base64.RawStdEncoding.Decode(decoded, ciphertext)
if err != nil {
return 0, err
- } else if len(decoded) < 2+binary.MaxVarintLen64 {
- return 0, fmt.Errorf("decoded ciphertext too short: %d bytes", len(decoded))
} else if decoded[0] != 3 || decoded[1] != 8 {
return 0, fmt.Errorf("unexpected initial bytes %d and %d", decoded[0], decoded[1])
}
@@ -370,19 +365,26 @@ func (mach *OlmMachine) encryptAndSendGroupSession(ctx context.Context, session
log.Trace().Msg("Encrypting group session for all found devices")
deviceCount := 0
toDevice := &mautrix.ReqSendToDevice{Messages: make(map[id.UserID]map[id.DeviceID]*event.Content)}
- logUsers := zerolog.Dict()
for userID, sessions := range olmSessions {
if len(sessions) == 0 {
continue
}
- logDevices := zerolog.Dict()
output := make(map[id.DeviceID]*event.Content)
toDevice.Messages[userID] = output
for deviceID, device := range sessions {
+ log.Trace().
+ Stringer("target_user_id", userID).
+ Stringer("target_device_id", deviceID).
+ Stringer("target_identity_key", device.identity.IdentityKey).
+ Msg("Encrypting group session for device")
content := mach.encryptOlmEvent(ctx, device.session, device.identity, event.ToDeviceRoomKey, session.ShareContent())
output[deviceID] = &event.Content{Parsed: content}
- logDevices.Str(string(deviceID), string(device.identity.IdentityKey))
deviceCount++
+ log.Debug().
+ Stringer("target_user_id", userID).
+ Stringer("target_device_id", deviceID).
+ Stringer("target_identity_key", device.identity.IdentityKey).
+ Msg("Encrypted group session for device")
if !mach.DisableSharedGroupSessionTracking {
err := mach.CryptoStore.MarkOutboundGroupSessionShared(ctx, userID, device.identity.IdentityKey, session.id)
if err != nil {
@@ -396,13 +398,11 @@ func (mach *OlmMachine) encryptAndSendGroupSession(ctx context.Context, session
}
}
}
- logUsers.Dict(string(userID), logDevices)
}
log.Debug().
Int("device_count", deviceCount).
Int("user_count", len(toDevice.Messages)).
- Dict("destination_map", logUsers).
Msg("Sending to-device messages to share group session")
_, err := mach.Client.SendToDevice(ctx, event.ToDeviceEncrypted, toDevice)
return err
diff --git a/crypto/encryptolm.go b/crypto/encryptolm.go
index 765307af..80b76dc5 100644
--- a/crypto/encryptolm.go
+++ b/crypto/encryptolm.go
@@ -96,19 +96,15 @@ func (mach *OlmMachine) encryptOlmEvent(ctx context.Context, session *OlmSession
panic(err)
}
log := mach.machOrContextLog(ctx)
+ log.Debug().
+ Str("recipient_identity_key", recipient.IdentityKey.String()).
+ Str("olm_session_id", session.ID().String()).
+ Str("olm_session_description", session.Describe()).
+ Msg("Encrypting olm message")
msgType, ciphertext, err := session.Encrypt(plaintext)
if err != nil {
panic(err)
}
- ciphertextStr := string(ciphertext)
- ciphertextHash, _ := olmMessageHash(ciphertextStr)
- log.Debug().
- Stringer("event_type", evtType).
- Str("recipient_identity_key", recipient.IdentityKey.String()).
- Str("olm_session_id", session.ID().String()).
- Str("olm_session_description", session.Describe()).
- Hex("ciphertext_hash", ciphertextHash[:]).
- Msg("Encrypted olm message")
err = mach.CryptoStore.UpdateSession(ctx, recipient.IdentityKey, session)
if err != nil {
log.Error().Err(err).Msg("Failed to update olm session in crypto store after encrypting")
@@ -119,7 +115,7 @@ func (mach *OlmMachine) encryptOlmEvent(ctx context.Context, session *OlmSession
OlmCiphertext: event.OlmCiphertexts{
recipient.IdentityKey: {
Type: msgType,
- Body: ciphertextStr,
+ Body: string(ciphertext),
},
},
}
diff --git a/crypto/goolm/crypto/curve25519.go b/crypto/goolm/crypto/curve25519.go
index 6e42d886..e9759501 100644
--- a/crypto/goolm/crypto/curve25519.go
+++ b/crypto/goolm/crypto/curve25519.go
@@ -53,7 +53,6 @@ func (c Curve25519KeyPair) B64Encoded() id.Curve25519 {
// SharedSecret returns the shared secret between the key pair and the given public key.
func (c Curve25519KeyPair) SharedSecret(pubKey Curve25519PublicKey) ([]byte, error) {
- // Note: the standard library checks that the output is non-zero
return c.PrivateKey.SharedSecret(pubKey)
}
diff --git a/crypto/goolm/crypto/curve25519_test.go b/crypto/goolm/crypto/curve25519_test.go
index 2550f15e..9039c126 100644
--- a/crypto/goolm/crypto/curve25519_test.go
+++ b/crypto/goolm/crypto/curve25519_test.go
@@ -25,8 +25,6 @@ func TestCurve25519(t *testing.T) {
fromPrivate, err := crypto.Curve25519GenerateFromPrivate(firstKeypair.PrivateKey)
assert.NoError(t, err)
assert.Equal(t, fromPrivate, firstKeypair)
- _, err = secondKeypair.SharedSecret(make([]byte, crypto.Curve25519PublicKeyLength))
- assert.Error(t, err)
}
func TestCurve25519Case1(t *testing.T) {
diff --git a/crypto/keybackup.go b/crypto/keybackup.go
index 7b3c30db..ceec1d58 100644
--- a/crypto/keybackup.go
+++ b/crypto/keybackup.go
@@ -200,14 +200,13 @@ func (mach *OlmMachine) ImportRoomKeyFromBackupWithoutSaving(
SigningKey: keyBackupData.SenderClaimedKeys.Ed25519,
SenderKey: keyBackupData.SenderKey,
RoomID: roomID,
- ForwardingChains: keyBackupData.ForwardingKeyChain,
+ ForwardingChains: append(keyBackupData.ForwardingKeyChain, keyBackupData.SenderKey.String()),
id: sessionID,
ReceivedAt: time.Now().UTC(),
MaxAge: maxAge.Milliseconds(),
MaxMessages: maxMessages,
KeyBackupVersion: version,
- KeySource: id.KeySourceBackup,
}, nil
}
diff --git a/crypto/keyexport_test.go b/crypto/keyexport_test.go
index fd6f105d..47616a20 100644
--- a/crypto/keyexport_test.go
+++ b/crypto/keyexport_test.go
@@ -31,5 +31,5 @@ func TestExportKeys(t *testing.T) {
))
data, err := crypto.ExportKeys("meow", []*crypto.InboundGroupSession{sess})
assert.NoError(t, err)
- assert.Len(t, data, 893)
+ assert.Len(t, data, 836)
}
diff --git a/crypto/keyimport.go b/crypto/keyimport.go
index 3ffc74a5..36ad6b9c 100644
--- a/crypto/keyimport.go
+++ b/crypto/keyimport.go
@@ -108,20 +108,19 @@ func (mach *OlmMachine) importExportedRoomKey(ctx context.Context, session Expor
return false, ErrMismatchingExportedSessionID
}
igs := &InboundGroupSession{
- Internal: igsInternal,
- SigningKey: session.SenderClaimedKeys.Ed25519,
- SenderKey: session.SenderKey,
- RoomID: session.RoomID,
+ Internal: igsInternal,
+ SigningKey: session.SenderClaimedKeys.Ed25519,
+ SenderKey: session.SenderKey,
+ RoomID: session.RoomID,
+ // TODO should we add something here to mark the signing key as unverified like key requests do?
ForwardingChains: session.ForwardingChains,
- KeySource: id.KeySourceImport,
- ReceivedAt: time.Now().UTC(),
+
+ ReceivedAt: time.Now().UTC(),
}
existingIGS, _ := mach.CryptoStore.GetGroupSession(ctx, igs.RoomID, igs.ID())
firstKnownIndex := igs.Internal.FirstKnownIndex()
if existingIGS != nil && existingIGS.Internal.FirstKnownIndex() <= firstKnownIndex {
- // We already have an equivalent or better session in the store, so don't override it,
- // but do notify the session received callback just in case.
- mach.MarkSessionReceived(ctx, session.RoomID, igs.ID(), existingIGS.Internal.FirstKnownIndex())
+ // We already have an equivalent or better session in the store, so don't override it.
return false, nil
}
err = mach.CryptoStore.PutGroupSession(ctx, igs)
diff --git a/crypto/keysharing.go b/crypto/keysharing.go
index 19a68c87..f1d427af 100644
--- a/crypto/keysharing.go
+++ b/crypto/keysharing.go
@@ -189,7 +189,6 @@ func (mach *OlmMachine) importForwardedRoomKey(ctx context.Context, evt *Decrypt
MaxAge: maxAge.Milliseconds(),
MaxMessages: maxMessages,
IsScheduled: content.IsScheduled,
- KeySource: id.KeySourceForward,
}
existingIGS, _ := mach.CryptoStore.GetGroupSession(ctx, igs.RoomID, igs.ID())
if existingIGS != nil && existingIGS.Internal.FirstKnownIndex() <= igs.Internal.FirstKnownIndex() {
@@ -215,7 +214,6 @@ func (mach *OlmMachine) rejectKeyRequest(ctx context.Context, rejection KeyShare
RoomID: request.RoomID,
Algorithm: request.Algorithm,
SessionID: request.SessionID,
- //lint:ignore SA1019 This is just echoing back the deprecated field
SenderKey: request.SenderKey,
Code: rejection.Code,
Reason: rejection.Reason,
@@ -265,14 +263,9 @@ func (mach *OlmMachine) defaultAllowKeyShare(ctx context.Context, device *id.Dev
log.Err(err).Msg("Rejecting key request due to internal error when checking session sharing")
return &KeyShareRejectNoResponse
} else if !isShared {
- igs, _ := mach.CryptoStore.GetGroupSession(ctx, evt.RoomID, evt.SessionID)
- if igs != nil && igs.SenderKey == mach.OwnIdentity().IdentityKey {
- log.Debug().Msg("Rejecting key request for unshared session")
- return &KeyShareRejectNotRecipient
- }
- // Note: this case will also happen for redacted sessions and database errors
- log.Debug().Msg("Rejecting key request for session created by another device")
- return &KeyShareRejectNoResponse
+ // TODO differentiate session not shared with requester vs session not created by this device?
+ log.Debug().Msg("Rejecting key request for unshared session")
+ return &KeyShareRejectNotRecipient
}
log.Debug().Msg("Accepting key request for shared session")
return nil
@@ -330,9 +323,7 @@ func (mach *OlmMachine) HandleRoomKeyRequest(ctx context.Context, sender id.User
if err != nil {
if errors.Is(err, ErrGroupSessionWithheld) {
log.Debug().Err(err).Msg("Requested group session not available")
- if sender != mach.Client.UserID {
- mach.rejectKeyRequest(ctx, KeyShareRejectUnavailable, device, content.Body)
- }
+ mach.rejectKeyRequest(ctx, KeyShareRejectUnavailable, device, content.Body)
} else {
log.Error().Err(err).Msg("Failed to get group session to forward")
mach.rejectKeyRequest(ctx, KeyShareRejectInternalError, device, content.Body)
@@ -340,9 +331,7 @@ func (mach *OlmMachine) HandleRoomKeyRequest(ctx context.Context, sender id.User
return
} else if igs == nil {
log.Error().Msg("Didn't find group session to forward")
- if sender != mach.Client.UserID {
- mach.rejectKeyRequest(ctx, KeyShareRejectUnavailable, device, content.Body)
- }
+ mach.rejectKeyRequest(ctx, KeyShareRejectUnavailable, device, content.Body)
return
}
if internalID := igs.ID(); internalID != content.Body.SessionID {
@@ -367,7 +356,7 @@ func (mach *OlmMachine) HandleRoomKeyRequest(ctx context.Context, sender id.User
SessionID: igs.ID(),
SessionKey: string(exportedKey),
},
- SenderKey: igs.SenderKey,
+ SenderKey: content.Body.SenderKey,
ForwardingKeyChain: igs.ForwardingChains,
SenderClaimedKey: igs.SigningKey,
},
diff --git a/crypto/sessions.go b/crypto/sessions.go
index ccc7b784..6b90c998 100644
--- a/crypto/sessions.go
+++ b/crypto/sessions.go
@@ -117,7 +117,6 @@ type InboundGroupSession struct {
MaxMessages int
IsScheduled bool
KeyBackupVersion id.KeyBackupVersion
- KeySource id.KeySource
id id.SessionID
}
@@ -137,7 +136,6 @@ func NewInboundGroupSession(senderKey id.SenderKey, signingKey id.Ed25519, roomI
MaxAge: maxAge.Milliseconds(),
MaxMessages: maxMessages,
IsScheduled: isScheduled,
- KeySource: id.KeySourceDirect,
}, nil
}
@@ -171,7 +169,7 @@ func (igs *InboundGroupSession) export() (*ExportedSession, error) {
ForwardingChains: igs.ForwardingChains,
RoomID: igs.RoomID,
SenderKey: igs.SenderKey,
- SenderClaimedKeys: SenderClaimedKeys{Ed25519: igs.SigningKey},
+ SenderClaimedKeys: SenderClaimedKeys{},
SessionID: igs.ID(),
SessionKey: string(key),
}, nil
diff --git a/crypto/sql_store.go b/crypto/sql_store.go
index 138cc557..ca75b3f6 100644
--- a/crypto/sql_store.go
+++ b/crypto/sql_store.go
@@ -346,23 +346,22 @@ func (store *SQLCryptoStore) PutGroupSession(ctx context.Context, session *Inbou
Int("max_messages", session.MaxMessages).
Bool("is_scheduled", session.IsScheduled).
Stringer("key_backup_version", session.KeyBackupVersion).
- Stringer("key_source", session.KeySource).
Msg("Upserting megolm inbound group session")
_, err = store.DB.Exec(ctx, `
INSERT INTO crypto_megolm_inbound_session (
session_id, sender_key, signing_key, room_id, session, forwarding_chains,
- ratchet_safety, received_at, max_age, max_messages, is_scheduled, key_backup_version, key_source, account_id
- ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
+ ratchet_safety, received_at, max_age, max_messages, is_scheduled, key_backup_version, account_id
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
ON CONFLICT (session_id, account_id) DO UPDATE
SET withheld_code=NULL, withheld_reason=NULL, sender_key=excluded.sender_key, signing_key=excluded.signing_key,
room_id=excluded.room_id, session=excluded.session, forwarding_chains=excluded.forwarding_chains,
ratchet_safety=excluded.ratchet_safety, received_at=excluded.received_at,
max_age=excluded.max_age, max_messages=excluded.max_messages, is_scheduled=excluded.is_scheduled,
- key_backup_version=excluded.key_backup_version, key_source=excluded.key_source
+ key_backup_version=excluded.key_backup_version
`,
session.ID(), session.SenderKey, session.SigningKey, session.RoomID, sessionBytes, forwardingChains,
ratchetSafety, datePtr(session.ReceivedAt), dbutil.NumPtr(session.MaxAge), dbutil.NumPtr(session.MaxMessages),
- session.IsScheduled, session.KeyBackupVersion, session.KeySource, store.AccountID,
+ session.IsScheduled, session.KeyBackupVersion, store.AccountID,
)
return err
}
@@ -375,13 +374,12 @@ func (store *SQLCryptoStore) GetGroupSession(ctx context.Context, roomID id.Room
var maxAge, maxMessages sql.NullInt64
var isScheduled bool
var version id.KeyBackupVersion
- var keySource id.KeySource
err := store.DB.QueryRow(ctx, `
- SELECT sender_key, signing_key, session, forwarding_chains, withheld_code, withheld_reason, ratchet_safety, received_at, max_age, max_messages, is_scheduled, key_backup_version, key_source
+ SELECT sender_key, signing_key, session, forwarding_chains, withheld_code, withheld_reason, ratchet_safety, received_at, max_age, max_messages, is_scheduled, key_backup_version
FROM crypto_megolm_inbound_session
WHERE room_id=$1 AND session_id=$2 AND account_id=$3`,
roomID, sessionID, store.AccountID,
- ).Scan(&senderKey, &signingKey, &sessionBytes, &forwardingChains, &withheldCode, &withheldReason, &ratchetSafetyBytes, &receivedAt, &maxAge, &maxMessages, &isScheduled, &version, &keySource)
+ ).Scan(&senderKey, &signingKey, &sessionBytes, &forwardingChains, &withheldCode, &withheldReason, &ratchetSafetyBytes, &receivedAt, &maxAge, &maxMessages, &isScheduled, &version)
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
} else if err != nil {
@@ -412,7 +410,6 @@ func (store *SQLCryptoStore) GetGroupSession(ctx context.Context, roomID id.Room
MaxMessages: int(maxMessages.Int64),
IsScheduled: isScheduled,
KeyBackupVersion: version,
- KeySource: keySource,
}, nil
}
@@ -537,8 +534,7 @@ func (store *SQLCryptoStore) scanInboundGroupSession(rows dbutil.Scannable) (*In
var maxAge, maxMessages sql.NullInt64
var isScheduled bool
var version id.KeyBackupVersion
- var keySource id.KeySource
- err := rows.Scan(&roomID, &senderKey, &signingKey, &sessionBytes, &forwardingChains, &ratchetSafetyBytes, &receivedAt, &maxAge, &maxMessages, &isScheduled, &version, &keySource)
+ err := rows.Scan(&roomID, &senderKey, &signingKey, &sessionBytes, &forwardingChains, &ratchetSafetyBytes, &receivedAt, &maxAge, &maxMessages, &isScheduled, &version)
if err != nil {
return nil, err
}
@@ -558,13 +554,12 @@ func (store *SQLCryptoStore) scanInboundGroupSession(rows dbutil.Scannable) (*In
MaxMessages: int(maxMessages.Int64),
IsScheduled: isScheduled,
KeyBackupVersion: version,
- KeySource: keySource,
}, nil
}
func (store *SQLCryptoStore) GetGroupSessionsForRoom(ctx context.Context, roomID id.RoomID) dbutil.RowIter[*InboundGroupSession] {
rows, err := store.DB.Query(ctx, `
- SELECT room_id, sender_key, signing_key, session, forwarding_chains, ratchet_safety, received_at, max_age, max_messages, is_scheduled, key_backup_version, key_source
+ SELECT room_id, sender_key, signing_key, session, forwarding_chains, ratchet_safety, received_at, max_age, max_messages, is_scheduled, key_backup_version
FROM crypto_megolm_inbound_session WHERE room_id=$1 AND account_id=$2 AND session IS NOT NULL`,
roomID, store.AccountID,
)
@@ -573,7 +568,7 @@ func (store *SQLCryptoStore) GetGroupSessionsForRoom(ctx context.Context, roomID
func (store *SQLCryptoStore) GetAllGroupSessions(ctx context.Context) dbutil.RowIter[*InboundGroupSession] {
rows, err := store.DB.Query(ctx, `
- SELECT room_id, sender_key, signing_key, session, forwarding_chains, ratchet_safety, received_at, max_age, max_messages, is_scheduled, key_backup_version, key_source
+ SELECT room_id, sender_key, signing_key, session, forwarding_chains, ratchet_safety, received_at, max_age, max_messages, is_scheduled, key_backup_version
FROM crypto_megolm_inbound_session WHERE account_id=$1 AND session IS NOT NULL`,
store.AccountID,
)
@@ -582,7 +577,7 @@ func (store *SQLCryptoStore) GetAllGroupSessions(ctx context.Context) dbutil.Row
func (store *SQLCryptoStore) GetGroupSessionsWithoutKeyBackupVersion(ctx context.Context, version id.KeyBackupVersion) dbutil.RowIter[*InboundGroupSession] {
rows, err := store.DB.Query(ctx, `
- SELECT room_id, sender_key, signing_key, session, forwarding_chains, ratchet_safety, received_at, max_age, max_messages, is_scheduled, key_backup_version, key_source
+ SELECT room_id, sender_key, signing_key, session, forwarding_chains, ratchet_safety, received_at, max_age, max_messages, is_scheduled, key_backup_version
FROM crypto_megolm_inbound_session WHERE account_id=$1 AND session IS NOT NULL AND key_backup_version != $2`,
store.AccountID, version,
)
diff --git a/crypto/sql_store_upgrade/00-latest-revision.sql b/crypto/sql_store_upgrade/00-latest-revision.sql
index 3709f1e5..af8ab5cc 100644
--- a/crypto/sql_store_upgrade/00-latest-revision.sql
+++ b/crypto/sql_store_upgrade/00-latest-revision.sql
@@ -1,4 +1,4 @@
--- v0 -> v19 (compatible with v15+): Latest revision
+-- v0 -> v18 (compatible with v15+): Latest revision
CREATE TABLE IF NOT EXISTS crypto_account (
account_id TEXT PRIMARY KEY,
device_id TEXT NOT NULL,
@@ -71,7 +71,6 @@ CREATE TABLE IF NOT EXISTS crypto_megolm_inbound_session (
max_messages INTEGER,
is_scheduled BOOLEAN NOT NULL DEFAULT false,
key_backup_version TEXT NOT NULL DEFAULT '',
- key_source TEXT NOT NULL DEFAULT '',
PRIMARY KEY (account_id, session_id)
);
-- Useful index to find keys that need backing up
diff --git a/crypto/sql_store_upgrade/19-megolm-session-source.sql b/crypto/sql_store_upgrade/19-megolm-session-source.sql
deleted file mode 100644
index f624222f..00000000
--- a/crypto/sql_store_upgrade/19-megolm-session-source.sql
+++ /dev/null
@@ -1,2 +0,0 @@
--- v19 (compatible with v15+): Store megolm session source
-ALTER TABLE crypto_megolm_inbound_session ADD COLUMN key_source TEXT NOT NULL DEFAULT '';
diff --git a/crypto/ssss/key.go b/crypto/ssss/key.go
index 78ebd8f3..cd8e3fce 100644
--- a/crypto/ssss/key.go
+++ b/crypto/ssss/key.go
@@ -59,12 +59,12 @@ func NewKey(passphrase string) (*Key, error) {
// We store a certain hash in the key metadata so that clients can check if the user entered the correct key.
ivBytes := random.Bytes(utils.AESCTRIVLength)
keyData.IV = base64.RawStdEncoding.EncodeToString(ivBytes)
- macBytes, err := keyData.calculateHash(ssssKey)
+ var err error
+ keyData.MAC, err = keyData.calculateHash(ssssKey)
if err != nil {
// This should never happen because we just generated the IV and key.
return nil, fmt.Errorf("failed to calculate hash: %w", err)
}
- keyData.MAC = base64.RawStdEncoding.EncodeToString(macBytes)
return &Key{
Key: ssssKey,
diff --git a/crypto/ssss/meta.go b/crypto/ssss/meta.go
index 34775fa7..474c85d8 100644
--- a/crypto/ssss/meta.go
+++ b/crypto/ssss/meta.go
@@ -7,10 +7,7 @@
package ssss
import (
- "crypto/hmac"
- "crypto/sha256"
"encoding/base64"
- "errors"
"fmt"
"strings"
@@ -36,9 +33,7 @@ func (kd *KeyMetadata) VerifyPassphrase(keyID, passphrase string) (*Key, error)
ssssKey, err := kd.Passphrase.GetKey(passphrase)
if err != nil {
return nil, err
- }
- err = kd.verifyKey(ssssKey)
- if err != nil && !errors.Is(err, ErrUnverifiableKey) {
+ } else if err = kd.verifyKey(ssssKey); err != nil {
return nil, err
}
@@ -54,9 +49,7 @@ func (kd *KeyMetadata) VerifyRecoveryKey(keyID, recoveryKey string) (*Key, error
ssssKey := utils.DecodeBase58RecoveryKey(recoveryKey)
if ssssKey == nil {
return nil, ErrInvalidRecoveryKey
- }
- err := kd.verifyKey(ssssKey)
- if err != nil && !errors.Is(err, ErrUnverifiableKey) {
+ } else if err := kd.verifyKey(ssssKey); err != nil {
return nil, err
}
@@ -64,28 +57,20 @@ func (kd *KeyMetadata) VerifyRecoveryKey(keyID, recoveryKey string) (*Key, error
ID: keyID,
Key: ssssKey,
Metadata: kd,
- }, err
+ }, nil
}
func (kd *KeyMetadata) verifyKey(key []byte) error {
- if kd.MAC == "" || kd.IV == "" {
- return ErrUnverifiableKey
- }
unpaddedMAC := strings.TrimRight(kd.MAC, "=")
expectedMACLength := base64.RawStdEncoding.EncodedLen(utils.SHAHashLength)
if len(unpaddedMAC) != expectedMACLength {
return fmt.Errorf("%w: invalid mac length %d (expected %d)", ErrCorruptedKeyMetadata, len(unpaddedMAC), expectedMACLength)
}
- expectedMAC, err := base64.RawStdEncoding.DecodeString(unpaddedMAC)
- if err != nil {
- return fmt.Errorf("%w: failed to decode mac: %w", ErrCorruptedKeyMetadata, err)
- }
- calculatedMAC, err := kd.calculateHash(key)
+ hash, err := kd.calculateHash(key)
if err != nil {
return err
}
- // This doesn't really need to be constant time since it's fully local, but might as well be.
- if !hmac.Equal(expectedMAC, calculatedMAC) {
+ if unpaddedMAC != hash {
return ErrIncorrectSSSSKey
}
return nil
@@ -98,26 +83,23 @@ func (kd *KeyMetadata) VerifyKey(key []byte) bool {
// calculateHash calculates the hash used for checking if the key is entered correctly as described
// in the spec: https://matrix.org/docs/spec/client_server/unstable#m-secret-storage-v1-aes-hmac-sha2
-func (kd *KeyMetadata) calculateHash(key []byte) ([]byte, error) {
+func (kd *KeyMetadata) calculateHash(key []byte) (string, error) {
aesKey, hmacKey := utils.DeriveKeysSHA256(key, "")
unpaddedIV := strings.TrimRight(kd.IV, "=")
expectedIVLength := base64.RawStdEncoding.EncodedLen(utils.AESCTRIVLength)
- if len(unpaddedIV) < expectedIVLength || len(unpaddedIV) > expectedIVLength*3 {
- return nil, fmt.Errorf("%w: invalid iv length %d (expected %d)", ErrCorruptedKeyMetadata, len(unpaddedIV), expectedIVLength)
+ if len(unpaddedIV) != expectedIVLength {
+ return "", fmt.Errorf("%w: invalid iv length %d (expected %d)", ErrCorruptedKeyMetadata, len(unpaddedIV), expectedIVLength)
}
- rawIVBytes, err := base64.RawStdEncoding.DecodeString(unpaddedIV)
- if err != nil {
- return nil, fmt.Errorf("%w: failed to decode iv: %w", ErrCorruptedKeyMetadata, err)
- }
- // TODO log a warning for non-16 byte IVs?
- // Certain broken clients like nheko generated 32-byte IVs where only the first 16 bytes were used.
- ivBytes := *(*[utils.AESCTRIVLength]byte)(rawIVBytes[:utils.AESCTRIVLength])
- zeroes := make([]byte, utils.AESCTRKeyLength)
- encryptedZeroes := utils.XorA256CTR(zeroes, aesKey, ivBytes)
- h := hmac.New(sha256.New, hmacKey[:])
- h.Write(encryptedZeroes)
- return h.Sum(nil), nil
+ var ivBytes [utils.AESCTRIVLength]byte
+ _, err := base64.RawStdEncoding.Decode(ivBytes[:], []byte(unpaddedIV))
+ if err != nil {
+ return "", fmt.Errorf("%w: failed to decode iv: %w", ErrCorruptedKeyMetadata, err)
+ }
+
+ cipher := utils.XorA256CTR(make([]byte, utils.AESCTRKeyLength), aesKey, ivBytes)
+
+ return utils.HMACSHA256B64(cipher, hmacKey), nil
}
// PassphraseMetadata represents server-side metadata about a SSSS key passphrase.
diff --git a/crypto/ssss/meta_test.go b/crypto/ssss/meta_test.go
index d59809c7..7a5ef8b9 100644
--- a/crypto/ssss/meta_test.go
+++ b/crypto/ssss/meta_test.go
@@ -8,6 +8,7 @@ package ssss_test
import (
"encoding/json"
+ "errors"
"testing"
"github.com/stretchr/testify/assert"
@@ -41,24 +42,10 @@ const key2Meta = `
}
`
-const key2MetaUnverified = `
-{
- "algorithm": "m.secret_storage.v1.aes-hmac-sha2"
-}
-`
-
-const key2MetaLongIV = `
-{
- "algorithm": "m.secret_storage.v1.aes-hmac-sha2",
- "iv": "O0BOvTqiIAYjC+RMcyHfW2f/gdxjceTxoYtNlpPduJ8=",
- "mac": "7k6OruQlWg0UmQjxGZ0ad4Q6DdwkgnoI7G6X3IjBYtI="
-}
-`
-
const key2MetaBrokenIV = `
{
"algorithm": "m.secret_storage.v1.aes-hmac-sha2",
- "iv": "MeowMeowMeow",
+ "iv": "O0BOvTqiIAYjC+RMcyHfWwMeowMeowMeow",
"mac": "7k6OruQlWg0UmQjxGZ0ad4Q6DdwkgnoI7G6X3IjBYtI="
}
`
@@ -107,33 +94,17 @@ func TestKeyMetadata_VerifyRecoveryKey_Correct2(t *testing.T) {
assert.Equal(t, key2RecoveryKey, key.RecoveryKey())
}
-func TestKeyMetadata_VerifyRecoveryKey_NonCompliant_LongIV(t *testing.T) {
- km := getKeyMeta(key2MetaLongIV)
- key, err := km.VerifyRecoveryKey(key2ID, key2RecoveryKey)
- assert.NoError(t, err)
- assert.NotNil(t, key)
- assert.Equal(t, key2RecoveryKey, key.RecoveryKey())
-}
-
-func TestKeyMetadata_VerifyRecoveryKey_Unverified(t *testing.T) {
- km := getKeyMeta(key2MetaUnverified)
- key, err := km.VerifyRecoveryKey(key2ID, key2RecoveryKey)
- assert.ErrorIs(t, err, ssss.ErrUnverifiableKey)
- assert.NotNil(t, key)
- assert.Equal(t, key2RecoveryKey, key.RecoveryKey())
-}
-
func TestKeyMetadata_VerifyRecoveryKey_Invalid(t *testing.T) {
km := getKeyMeta(key1Meta)
key, err := km.VerifyRecoveryKey(key1ID, "foo")
- assert.ErrorIs(t, err, ssss.ErrInvalidRecoveryKey)
+ assert.True(t, errors.Is(err, ssss.ErrInvalidRecoveryKey), "unexpected error: %v", err)
assert.Nil(t, key)
}
func TestKeyMetadata_VerifyRecoveryKey_Incorrect(t *testing.T) {
km := getKeyMeta(key1Meta)
key, err := km.VerifyRecoveryKey(key2ID, key2RecoveryKey)
- assert.ErrorIs(t, err, ssss.ErrIncorrectSSSSKey)
+ assert.True(t, errors.Is(err, ssss.ErrIncorrectSSSSKey), "unexpected error: %v", err)
assert.Nil(t, key)
}
@@ -148,27 +119,27 @@ func TestKeyMetadata_VerifyPassphrase_Correct(t *testing.T) {
func TestKeyMetadata_VerifyPassphrase_Incorrect(t *testing.T) {
km := getKeyMeta(key1Meta)
key, err := km.VerifyPassphrase(key1ID, "incorrect horse battery staple")
- assert.ErrorIs(t, err, ssss.ErrIncorrectSSSSKey)
+ assert.True(t, errors.Is(err, ssss.ErrIncorrectSSSSKey), "unexpected error %v", err)
assert.Nil(t, key)
}
func TestKeyMetadata_VerifyPassphrase_NotSet(t *testing.T) {
km := getKeyMeta(key2Meta)
key, err := km.VerifyPassphrase(key2ID, "hmm")
- assert.ErrorIs(t, err, ssss.ErrNoPassphrase)
+ assert.True(t, errors.Is(err, ssss.ErrNoPassphrase), "unexpected error %v", err)
assert.Nil(t, key)
}
func TestKeyMetadata_VerifyRecoveryKey_CorruptedIV(t *testing.T) {
km := getKeyMeta(key2MetaBrokenIV)
key, err := km.VerifyRecoveryKey(key2ID, key2RecoveryKey)
- assert.ErrorIs(t, err, ssss.ErrCorruptedKeyMetadata)
+ assert.True(t, errors.Is(err, ssss.ErrCorruptedKeyMetadata), "unexpected error %v", err)
assert.Nil(t, key)
}
func TestKeyMetadata_VerifyRecoveryKey_CorruptedMAC(t *testing.T) {
km := getKeyMeta(key2MetaBrokenMAC)
key, err := km.VerifyRecoveryKey(key2ID, key2RecoveryKey)
- assert.ErrorIs(t, err, ssss.ErrCorruptedKeyMetadata)
+ assert.True(t, errors.Is(err, ssss.ErrCorruptedKeyMetadata), "unexpected error %v", err)
assert.Nil(t, key)
}
diff --git a/crypto/ssss/types.go b/crypto/ssss/types.go
index b7465d3e..c08f107c 100644
--- a/crypto/ssss/types.go
+++ b/crypto/ssss/types.go
@@ -26,8 +26,7 @@ var (
ErrUnsupportedPassphraseAlgorithm = errors.New("unsupported passphrase KDF algorithm")
ErrIncorrectSSSSKey = errors.New("incorrect SSSS key")
ErrInvalidRecoveryKey = errors.New("invalid recovery key")
- ErrCorruptedKeyMetadata = errors.New("corrupted recovery key metadata")
- ErrUnverifiableKey = errors.New("cannot verify recovery key: missing MAC or IV in metadata")
+ ErrCorruptedKeyMetadata = errors.New("corrupted key metadata")
)
// Algorithm is the identifier for an SSSS encryption algorithm.
diff --git a/error.go b/error.go
index 4711b3dc..5ff671e0 100644
--- a/error.go
+++ b/error.go
@@ -140,10 +140,7 @@ type RespError struct {
Err string
ExtraData map[string]any
- StatusCode int
- ExtraHeader map[string]string
-
- CanRetry bool
+ StatusCode int
}
func (e *RespError) UnmarshalJSON(data []byte) error {
@@ -153,7 +150,6 @@ func (e *RespError) UnmarshalJSON(data []byte) error {
}
e.ErrCode, _ = e.ExtraData["errcode"].(string)
e.Err, _ = e.ExtraData["error"].(string)
- e.CanRetry, _ = e.ExtraData["com.beeper.can_retry"].(bool)
return nil
}
@@ -161,9 +157,6 @@ func (e *RespError) MarshalJSON() ([]byte, error) {
data := exmaps.NonNilClone(e.ExtraData)
data["errcode"] = e.ErrCode
data["error"] = e.Err
- if e.CanRetry {
- data["com.beeper.can_retry"] = e.CanRetry
- }
return json.Marshal(data)
}
@@ -175,9 +168,6 @@ func (e RespError) Write(w http.ResponseWriter) {
if statusCode == 0 {
statusCode = http.StatusInternalServerError
}
- for key, value := range e.ExtraHeader {
- w.Header().Set(key, value)
- }
exhttp.WriteJSONResponse(w, statusCode, &e)
}
@@ -194,29 +184,12 @@ func (e RespError) WithStatus(status int) RespError {
return e
}
-func (e RespError) WithCanRetry(canRetry bool) RespError {
- e.CanRetry = canRetry
- return e
-}
-
func (e RespError) WithExtraData(extraData map[string]any) RespError {
e.ExtraData = exmaps.NonNilClone(e.ExtraData)
maps.Copy(e.ExtraData, extraData)
return e
}
-func (e RespError) WithExtraHeader(key, value string) RespError {
- e.ExtraHeader = exmaps.NonNilClone(e.ExtraHeader)
- e.ExtraHeader[key] = value
- return e
-}
-
-func (e RespError) WithExtraHeaders(headers map[string]string) RespError {
- e.ExtraHeader = exmaps.NonNilClone(e.ExtraHeader)
- maps.Copy(e.ExtraHeader, headers)
- return e
-}
-
// Error returns the errcode and error message.
func (e RespError) Error() string {
return e.ErrCode + ": " + e.Err
diff --git a/event/beeper.go b/event/beeper.go
index a1a60b35..b46106ab 100644
--- a/event/beeper.go
+++ b/event/beeper.go
@@ -94,8 +94,6 @@ type BeeperChatDeleteEventContent struct {
}
type BeeperAcceptMessageRequestEventContent struct {
- // Whether this was triggered by a message rather than an explicit event
- IsImplicit bool `json:"-"`
}
type BeeperSendStateEventContent struct {
@@ -146,7 +144,7 @@ type BeeperLinkPreview struct {
MatchedURL string `json:"matched_url,omitempty"`
ImageEncryption *EncryptedFileInfo `json:"beeper:image:encryption,omitempty"`
- ImageBlurhash string `json:"matrix:image:blurhash,omitempty"`
+ ImageBlurhash string `json:"beeper:image:blurhash,omitempty"`
}
type BeeperProfileExtra struct {
@@ -166,24 +164,6 @@ type BeeperPerMessageProfile struct {
HasFallback bool `json:"has_fallback,omitempty"`
}
-type BeeperActionMessageType string
-
-const (
- BeeperActionMessageCall BeeperActionMessageType = "call"
-)
-
-type BeeperActionMessageCallType string
-
-const (
- BeeperActionMessageCallTypeVoice BeeperActionMessageCallType = "voice"
- BeeperActionMessageCallTypeVideo BeeperActionMessageCallType = "video"
-)
-
-type BeeperActionMessage struct {
- Type BeeperActionMessageType `json:"type"`
- CallType BeeperActionMessageCallType `json:"call_type,omitempty"`
-}
-
func (content *MessageEventContent) AddPerMessageProfileFallback() {
if content.BeeperPerMessageProfile == nil || content.BeeperPerMessageProfile.HasFallback || content.BeeperPerMessageProfile.Displayname == "" {
return
@@ -214,15 +194,6 @@ func (content *MessageEventContent) RemovePerMessageProfileFallback() {
}
}
-type BeeperAIStreamEventContent struct {
- TurnID string `json:"turn_id"`
- Seq int `json:"seq"`
- Part map[string]any `json:"part"`
- TargetEvent id.EventID `json:"target_event,omitempty"`
- AgentID string `json:"agent_id,omitempty"`
- RelatesTo *RelatesTo `json:"m.relates_to,omitempty"`
-}
-
type BeeperEncodedOrder struct {
order int64
suborder int16
diff --git a/event/content.go b/event/content.go
index 814aeec4..d1ced268 100644
--- a/event/content.go
+++ b/event/content.go
@@ -40,9 +40,6 @@ var TypeMap = map[Type]reflect.Type{
StateSpaceParent: reflect.TypeOf(SpaceParentEventContent{}),
StateSpaceChild: reflect.TypeOf(SpaceChildEventContent{}),
- StateRoomPolicy: reflect.TypeOf(RoomPolicyEventContent{}),
- StateUnstableRoomPolicy: reflect.TypeOf(RoomPolicyEventContent{}),
-
StateLegacyPolicyRoom: reflect.TypeOf(ModPolicyContent{}),
StateLegacyPolicyServer: reflect.TypeOf(ModPolicyContent{}),
StateLegacyPolicyUser: reflect.TypeOf(ModPolicyContent{}),
@@ -76,11 +73,9 @@ var TypeMap = map[Type]reflect.Type{
AccountDataMarkedUnread: reflect.TypeOf(MarkedUnreadEventContent{}),
AccountDataBeeperMute: reflect.TypeOf(BeeperMuteEventContent{}),
- EphemeralEventTyping: reflect.TypeOf(TypingEventContent{}),
- EphemeralEventReceipt: reflect.TypeOf(ReceiptEventContent{}),
- EphemeralEventPresence: reflect.TypeOf(PresenceEventContent{}),
- EphemeralEventEncrypted: reflect.TypeOf(EncryptedEventContent{}),
- BeeperEphemeralEventAIStream: reflect.TypeOf(BeeperAIStreamEventContent{}),
+ EphemeralEventTyping: reflect.TypeOf(TypingEventContent{}),
+ EphemeralEventReceipt: reflect.TypeOf(ReceiptEventContent{}),
+ EphemeralEventPresence: reflect.TypeOf(PresenceEventContent{}),
InRoomVerificationReady: reflect.TypeOf(VerificationReadyEventContent{}),
InRoomVerificationStart: reflect.TypeOf(VerificationStartEventContent{}),
diff --git a/event/encryption.go b/event/encryption.go
index c60cb91a..e07944af 100644
--- a/event/encryption.go
+++ b/event/encryption.go
@@ -63,7 +63,7 @@ func (content *EncryptedEventContent) UnmarshalJSON(data []byte) error {
return json.Unmarshal(content.Ciphertext, &content.OlmCiphertext)
case id.AlgorithmMegolmV1:
if len(content.Ciphertext) == 0 || content.Ciphertext[0] != '"' || content.Ciphertext[len(content.Ciphertext)-1] != '"' {
- return fmt.Errorf("ciphertext %w", id.ErrInputNotJSONString)
+ return id.ErrInputNotJSONString
}
content.MegolmCiphertext = content.Ciphertext[1 : len(content.Ciphertext)-1]
}
@@ -132,9 +132,8 @@ type RoomKeyRequestEventContent struct {
type RequestedKeyInfo struct {
Algorithm id.Algorithm `json:"algorithm"`
RoomID id.RoomID `json:"room_id"`
- SessionID id.SessionID `json:"session_id"`
- // Deprecated: Matrix v1.3
SenderKey id.SenderKey `json:"sender_key"`
+ SessionID id.SessionID `json:"session_id"`
}
type RoomKeyWithheldCode string
diff --git a/event/message.go b/event/message.go
index 3fb3dc82..5e80d2ef 100644
--- a/event/message.go
+++ b/event/message.go
@@ -135,7 +135,6 @@ type MessageEventContent struct {
BeeperGalleryCaption string `json:"com.beeper.gallery.caption,omitempty"`
BeeperGalleryCaptionHTML string `json:"com.beeper.gallery.caption_html,omitempty"`
BeeperPerMessageProfile *BeeperPerMessageProfile `json:"com.beeper.per_message_profile,omitempty"`
- BeeperActionMessage *BeeperActionMessage `json:"com.beeper.action_message,omitempty"`
BeeperLinkPreviews []*BeeperLinkPreview `json:"com.beeper.linkpreviews,omitempty"`
diff --git a/event/powerlevels.go b/event/powerlevels.go
index 668eb6d3..708721f9 100644
--- a/event/powerlevels.go
+++ b/event/powerlevels.go
@@ -28,9 +28,6 @@ type PowerLevelsEventContent struct {
Events map[string]int `json:"events,omitempty"`
EventsDefault int `json:"events_default,omitempty"`
- beeperEphemeralLock sync.RWMutex
- BeeperEphemeral map[string]int `json:"com.beeper.ephemeral,omitempty"`
-
Notifications *NotificationPowerLevels `json:"notifications,omitempty"`
StateDefaultPtr *int `json:"state_default,omitempty"`
@@ -40,8 +37,6 @@ type PowerLevelsEventContent struct {
BanPtr *int `json:"ban,omitempty"`
RedactPtr *int `json:"redact,omitempty"`
- BeeperEphemeralDefaultPtr *int `json:"com.beeper.ephemeral_default,omitempty"`
-
// This is not a part of power levels, it's added by mautrix-go internally in certain places
// in order to detect creator power accurately.
CreateEvent *Event `json:"-"`
@@ -56,7 +51,6 @@ func (pl *PowerLevelsEventContent) Clone() *PowerLevelsEventContent {
UsersDefault: pl.UsersDefault,
Events: maps.Clone(pl.Events),
EventsDefault: pl.EventsDefault,
- BeeperEphemeral: maps.Clone(pl.BeeperEphemeral),
StateDefaultPtr: ptr.Clone(pl.StateDefaultPtr),
Notifications: pl.Notifications.Clone(),
@@ -66,8 +60,6 @@ func (pl *PowerLevelsEventContent) Clone() *PowerLevelsEventContent {
BanPtr: ptr.Clone(pl.BanPtr),
RedactPtr: ptr.Clone(pl.RedactPtr),
- BeeperEphemeralDefaultPtr: ptr.Clone(pl.BeeperEphemeralDefaultPtr),
-
CreateEvent: pl.CreateEvent,
}
}
@@ -127,13 +119,6 @@ func (pl *PowerLevelsEventContent) StateDefault() int {
return 50
}
-func (pl *PowerLevelsEventContent) BeeperEphemeralDefault() int {
- if pl.BeeperEphemeralDefaultPtr != nil {
- return *pl.BeeperEphemeralDefaultPtr
- }
- return pl.EventsDefault
-}
-
func (pl *PowerLevelsEventContent) GetUserLevel(userID id.UserID) int {
if pl.isCreator(userID) {
return math.MaxInt
@@ -217,29 +202,6 @@ func (pl *PowerLevelsEventContent) GetEventLevel(eventType Type) int {
return level
}
-func (pl *PowerLevelsEventContent) GetBeeperEphemeralLevel(eventType Type) int {
- pl.beeperEphemeralLock.RLock()
- defer pl.beeperEphemeralLock.RUnlock()
- level, ok := pl.BeeperEphemeral[eventType.String()]
- if !ok {
- return pl.BeeperEphemeralDefault()
- }
- return level
-}
-
-func (pl *PowerLevelsEventContent) SetBeeperEphemeralLevel(eventType Type, level int) {
- pl.beeperEphemeralLock.Lock()
- defer pl.beeperEphemeralLock.Unlock()
- if level == pl.BeeperEphemeralDefault() {
- delete(pl.BeeperEphemeral, eventType.String())
- } else {
- if pl.BeeperEphemeral == nil {
- pl.BeeperEphemeral = make(map[string]int)
- }
- pl.BeeperEphemeral[eventType.String()] = level
- }
-}
-
func (pl *PowerLevelsEventContent) SetEventLevel(eventType Type, level int) {
pl.eventsLock.Lock()
defer pl.eventsLock.Unlock()
diff --git a/event/powerlevels_ephemeral_test.go b/event/powerlevels_ephemeral_test.go
deleted file mode 100644
index f5861583..00000000
--- a/event/powerlevels_ephemeral_test.go
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (c) 2026 Tulir Asokan
-//
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-package event_test
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "maunium.net/go/mautrix/event"
-)
-
-func TestPowerLevelsEventContent_BeeperEphemeralDefaultFallsBackToEventsDefault(t *testing.T) {
- pl := &event.PowerLevelsEventContent{
- EventsDefault: 45,
- }
-
- assert.Equal(t, 45, pl.BeeperEphemeralDefault())
-
- override := 60
- pl.BeeperEphemeralDefaultPtr = &override
- assert.Equal(t, 60, pl.BeeperEphemeralDefault())
-}
-
-func TestPowerLevelsEventContent_GetSetBeeperEphemeralLevel(t *testing.T) {
- pl := &event.PowerLevelsEventContent{
- EventsDefault: 25,
- }
- evtType := event.Type{Type: "com.example.ephemeral", Class: event.EphemeralEventType}
-
- assert.Equal(t, 25, pl.GetBeeperEphemeralLevel(evtType))
-
- pl.SetBeeperEphemeralLevel(evtType, 50)
- assert.Equal(t, 50, pl.GetBeeperEphemeralLevel(evtType))
- require.NotNil(t, pl.BeeperEphemeral)
- assert.Equal(t, 50, pl.BeeperEphemeral[evtType.String()])
-
- pl.SetBeeperEphemeralLevel(evtType, 25)
- _, exists := pl.BeeperEphemeral[evtType.String()]
- assert.False(t, exists)
-}
-
-func TestPowerLevelsEventContent_CloneCopiesBeeperEphemeralFields(t *testing.T) {
- override := 70
- pl := &event.PowerLevelsEventContent{
- EventsDefault: 35,
- BeeperEphemeral: map[string]int{"com.example.ephemeral": 90},
- BeeperEphemeralDefaultPtr: &override,
- }
-
- cloned := pl.Clone()
- require.NotNil(t, cloned)
- require.NotNil(t, cloned.BeeperEphemeralDefaultPtr)
- assert.Equal(t, 70, *cloned.BeeperEphemeralDefaultPtr)
- assert.Equal(t, 90, cloned.BeeperEphemeral["com.example.ephemeral"])
-
- cloned.BeeperEphemeral["com.example.ephemeral"] = 99
- *cloned.BeeperEphemeralDefaultPtr = 71
-
- assert.Equal(t, 90, pl.BeeperEphemeral["com.example.ephemeral"])
- assert.Equal(t, 70, *pl.BeeperEphemeralDefaultPtr)
-}
diff --git a/event/state.go b/event/state.go
index ace170a5..6d027e04 100644
--- a/event/state.go
+++ b/event/state.go
@@ -343,15 +343,3 @@ func (efmc *ElementFunctionalMembersContent) Add(mxid id.UserID) bool {
efmc.ServiceMembers = append(efmc.ServiceMembers, mxid)
return true
}
-
-type PolicyServerPublicKeys struct {
- Ed25519 id.Ed25519 `json:"ed25519,omitempty"`
-}
-
-type RoomPolicyEventContent struct {
- Via string `json:"via,omitempty"`
- PublicKeys *PolicyServerPublicKeys `json:"public_keys,omitempty"`
-
- // Deprecated, only for legacy use
- PublicKey id.Ed25519 `json:"public_key,omitempty"`
-}
diff --git a/event/type.go b/event/type.go
index 80b86728..b193dc59 100644
--- a/event/type.go
+++ b/event/type.go
@@ -113,9 +113,9 @@ func (et *Type) GuessClass() TypeClass {
StatePinnedEvents.Type, StateTombstone.Type, StateEncryption.Type, StateBridge.Type, StateHalfShotBridge.Type,
StateSpaceParent.Type, StateSpaceChild.Type, StatePolicyRoom.Type, StatePolicyServer.Type, StatePolicyUser.Type,
StateElementFunctionalMembers.Type, StateBeeperRoomFeatures.Type, StateBeeperDisappearingTimer.Type,
- StateMSC4391BotCommand.Type, StateRoomPolicy.Type, StateUnstableRoomPolicy.Type:
+ StateMSC4391BotCommand.Type:
return StateEventType
- case EphemeralEventReceipt.Type, EphemeralEventTyping.Type, EphemeralEventPresence.Type, BeeperEphemeralEventAIStream.Type:
+ case EphemeralEventReceipt.Type, EphemeralEventTyping.Type, EphemeralEventPresence.Type:
return EphemeralEventType
case AccountDataDirectChats.Type, AccountDataPushRules.Type, AccountDataRoomTags.Type,
AccountDataFullyRead.Type, AccountDataIgnoredUserList.Type, AccountDataMarkedUnread.Type,
@@ -195,9 +195,6 @@ var (
StateSpaceChild = Type{"m.space.child", StateEventType}
StateSpaceParent = Type{"m.space.parent", StateEventType}
- StateRoomPolicy = Type{"m.room.policy", StateEventType}
- StateUnstableRoomPolicy = Type{"org.matrix.msc4284.policy", StateEventType}
-
StateLegacyPolicyRoom = Type{"m.room.rule.room", StateEventType}
StateLegacyPolicyServer = Type{"m.room.rule.server", StateEventType}
StateLegacyPolicyUser = Type{"m.room.rule.user", StateEventType}
@@ -250,11 +247,9 @@ var (
// Ephemeral events
var (
- EphemeralEventReceipt = Type{"m.receipt", EphemeralEventType}
- EphemeralEventTyping = Type{"m.typing", EphemeralEventType}
- EphemeralEventPresence = Type{"m.presence", EphemeralEventType}
- EphemeralEventEncrypted = Type{"m.room.encrypted", EphemeralEventType}
- BeeperEphemeralEventAIStream = Type{"com.beeper.ai.stream_event", EphemeralEventType}
+ EphemeralEventReceipt = Type{"m.receipt", EphemeralEventType}
+ EphemeralEventTyping = Type{"m.typing", EphemeralEventType}
+ EphemeralEventPresence = Type{"m.presence", EphemeralEventType}
)
// Account data events
diff --git a/federation/eventauth/eventauth.go b/federation/eventauth/eventauth.go
index c72933c2..eac110a3 100644
--- a/federation/eventauth/eventauth.go
+++ b/federation/eventauth/eventauth.go
@@ -505,7 +505,7 @@ func authorizeMember(roomVersion id.RoomVersion, evt, createEvt *pdu.PDU, authEv
// 5.5.5. Otherwise, reject.
return ErrInsufficientPermissionForKick
case event.MembershipBan:
- if senderMembership != event.MembershipJoin {
+ if senderMembership != event.MembershipLeave {
// 5.6.1. If the sender’s current membership state is not join, reject.
return ErrCantBanWithoutBeingInRoom
}
diff --git a/federation/eventauth/eventauth_internal_test.go b/federation/eventauth/eventauth_internal_test.go
deleted file mode 100644
index d316f3c8..00000000
--- a/federation/eventauth/eventauth_internal_test.go
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright (c) 2026 Tulir Asokan
-//
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-//go:build goexperiment.jsonv2
-
-package eventauth
-
-import (
- "strings"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- "github.com/tidwall/gjson"
-)
-
-type pythonIntTest struct {
- Name string
- Input string
- Expected int64
-}
-
-var pythonIntTests = []pythonIntTest{
- {"True", `true`, 1},
- {"False", `false`, 0},
- {"SmallFloat", `3.1415`, 3},
- {"SmallFloatRoundDown", `10.999999999999999`, 10},
- {"SmallFloatRoundUp", `10.9999999999999999`, 11},
- {"BigFloatRoundDown", `1000000.9999999999`, 1000000},
- {"BigFloatRoundUp", `1000000.99999999999`, 1000001},
- {"BigFloatPrecisionError", `9007199254740993.0`, 9007199254740992},
- {"BigFloatPrecisionError2", `9007199254740993.123`, 9007199254740994},
- {"Int64", `9223372036854775807`, 9223372036854775807},
- {"Int64String", `"9223372036854775807"`, 9223372036854775807},
- {"String", `"123"`, 123},
- {"InvalidFloatInString", `"123.456"`, 0},
- {"StringWithPlusSign", `"+123"`, 123},
- {"StringWithMinusSign", `"-123"`, -123},
- {"StringWithSpaces", `" 123 "`, 123},
- {"StringWithSpacesAndSign", `" -123 "`, -123},
- //{"StringWithUnderscores", `"123_456"`, 123456},
- //{"StringWithUnderscores", `"123_456"`, 123456},
- {"InvalidStringWithTrailingUnderscore", `"123_456_"`, 0},
- {"InvalidStringWithMultipleUnderscores", `"123__456"`, 0},
- {"InvalidStringWithLeadingUnderscore", `"_123_456"`, 0},
- {"InvalidStringWithUnderscoreAfterSign", `"+_123_456"`, 0},
- {"InvalidStringWithUnderscoreAfterSpace", `" _123_456"`, 0},
- //{"StringWithUnderscoresAndSpaces", `" +1_2_3_4_5_6 "`, 123456},
-}
-
-func TestParsePythonInt(t *testing.T) {
- for _, test := range pythonIntTests {
- t.Run(test.Name, func(t *testing.T) {
- output := parsePythonInt(gjson.Parse(test.Input))
- if strings.HasPrefix(test.Name, "Invalid") {
- assert.Nil(t, output)
- } else {
- require.NotNil(t, output)
- assert.Equal(t, int(test.Expected), *output)
- }
- })
- }
-}
diff --git a/federation/pdu/pdu.go b/federation/pdu/pdu.go
index 17db6995..cecee5b9 100644
--- a/federation/pdu/pdu.go
+++ b/federation/pdu/pdu.go
@@ -123,19 +123,6 @@ func (pdu *PDU) ToClientEvent(roomVersion id.RoomVersion) (*event.Event, error)
return evt, nil
}
-func (pdu *PDU) AddSignature(serverName string, keyID id.KeyID, signature string) {
- if signature == "" {
- return
- }
- if pdu.Signatures == nil {
- pdu.Signatures = make(map[string]map[id.KeyID]string)
- }
- if _, ok := pdu.Signatures[serverName]; !ok {
- pdu.Signatures[serverName] = make(map[id.KeyID]string)
- }
- pdu.Signatures[serverName][keyID] = signature
-}
-
func marshalCanonical(data any) (jsontext.Value, error) {
marshaledBytes, err := json.Marshal(data)
if err != nil {
diff --git a/federation/pdu/signature.go b/federation/pdu/signature.go
index 04e7c5ef..a7685cc6 100644
--- a/federation/pdu/signature.go
+++ b/federation/pdu/signature.go
@@ -28,7 +28,13 @@ func (pdu *PDU) Sign(roomVersion id.RoomVersion, serverName string, keyID id.Key
return fmt.Errorf("failed to marshal redacted PDU to sign: %w", err)
}
signature := ed25519.Sign(privateKey, rawJSON)
- pdu.AddSignature(serverName, keyID, base64.RawStdEncoding.EncodeToString(signature))
+ if pdu.Signatures == nil {
+ pdu.Signatures = make(map[string]map[id.KeyID]string)
+ }
+ if _, ok := pdu.Signatures[serverName]; !ok {
+ pdu.Signatures[serverName] = make(map[id.KeyID]string)
+ }
+ pdu.Signatures[serverName][keyID] = base64.RawStdEncoding.EncodeToString(signature)
return nil
}
diff --git a/format/htmlparser.go b/format/htmlparser.go
index e0507d93..e5f92896 100644
--- a/format/htmlparser.go
+++ b/format/htmlparser.go
@@ -93,30 +93,6 @@ func DefaultPillConverter(displayname, mxid, eventID string, ctx Context) string
}
}
-func onlyBacktickCount(line string) (count int) {
- for i := 0; i < len(line); i++ {
- if line[i] != '`' {
- return -1
- }
- count++
- }
- return
-}
-
-func DefaultMonospaceBlockConverter(code, language string, ctx Context) string {
- if len(code) == 0 || code[len(code)-1] != '\n' {
- code += "\n"
- }
- fence := "```"
- for line := range strings.SplitSeq(code, "\n") {
- count := onlyBacktickCount(strings.TrimSpace(line))
- if count >= len(fence) {
- fence = strings.Repeat("`", count+1)
- }
- }
- return fmt.Sprintf("%s%s\n%s%s", fence, language, code, fence)
-}
-
// HTMLParser is a somewhat customizable Matrix HTML parser.
type HTMLParser struct {
PillConverter PillConverter
@@ -372,7 +348,10 @@ func (parser *HTMLParser) tagToString(node *html.Node, ctx Context) string {
if parser.MonospaceBlockConverter != nil {
return parser.MonospaceBlockConverter(preStr, language, ctx)
}
- return DefaultMonospaceBlockConverter(preStr, language, ctx)
+ if len(preStr) == 0 || preStr[len(preStr)-1] != '\n' {
+ preStr += "\n"
+ }
+ return fmt.Sprintf("```%s\n%s```", language, preStr)
default:
return parser.nodeToTagAwareString(node.FirstChild, ctx)
}
diff --git a/go.mod b/go.mod
index 49a1d4e4..27acd21b 100644
--- a/go.mod
+++ b/go.mod
@@ -1,15 +1,15 @@
module maunium.net/go/mautrix
-go 1.25.0
+go 1.24.0
-toolchain go1.26.0
+toolchain go1.25.6
require (
- filippo.io/edwards25519 v1.2.0
+ filippo.io/edwards25519 v1.1.0
github.com/chzyer/readline v1.5.1
github.com/coder/websocket v1.8.14
- github.com/lib/pq v1.11.2
- github.com/mattn/go-sqlite3 v1.14.34
+ github.com/lib/pq v1.10.9
+ github.com/mattn/go-sqlite3 v1.14.33
github.com/rs/xid v1.6.0
github.com/rs/zerolog v1.34.0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
@@ -17,11 +17,11 @@ require (
github.com/tidwall/gjson v1.18.0
github.com/tidwall/sjson v1.2.5
github.com/yuin/goldmark v1.7.16
- go.mau.fi/util v0.9.6
+ go.mau.fi/util v0.9.5
go.mau.fi/zeroconfig v0.2.0
- golang.org/x/crypto v0.48.0
- golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa
- golang.org/x/net v0.50.0
+ golang.org/x/crypto v0.47.0
+ golang.org/x/exp v0.0.0-20260112195511-716be5621a96
+ golang.org/x/net v0.49.0
golang.org/x/sync v0.19.0
gopkg.in/yaml.v3 v3.0.1
maunium.net/go/mauflag v1.0.0
@@ -36,7 +36,7 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
- golang.org/x/sys v0.41.0 // indirect
- golang.org/x/text v0.34.0 // indirect
+ golang.org/x/sys v0.40.0 // indirect
+ golang.org/x/text v0.33.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
)
diff --git a/go.sum b/go.sum
index 871a5156..9702337a 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,5 @@
-filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
-filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
+filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
+filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
@@ -16,8 +16,8 @@ github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
-github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs=
-github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
+github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
+github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
@@ -25,8 +25,8 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk=
-github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
+github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0=
+github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 h1:KPpdlQLZcHfTMQRi6bFQ7ogNO0ltFT4PmtwTLW4W+14=
github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -52,26 +52,26 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE=
github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
-go.mau.fi/util v0.9.6 h1:2nsvxm49KhI3wrFltr0+wSUBlnQ4CMtykuELjpIU+ts=
-go.mau.fi/util v0.9.6/go.mod h1:sIJpRH7Iy5Ad1SBuxQoatxtIeErgzxCtjd/2hCMkYMI=
+go.mau.fi/util v0.9.5 h1:7AoWPCIZJGv4jvtFEuCe3GhAbI7uF9ckIooaXvwlIR4=
+go.mau.fi/util v0.9.5/go.mod h1:g1uvZ03VQhtTt2BgaRGVytS/Zj67NV0YNIECch0sQCQ=
go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU=
go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w=
-golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
-golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
-golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
-golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
-golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
-golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
+golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
+golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
+golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
+golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
+golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
+golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
-golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
-golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
-golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
+golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
+golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
+golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
diff --git a/id/contenturi.go b/id/contenturi.go
index 67127b6c..be45eb2b 100644
--- a/id/contenturi.go
+++ b/id/contenturi.go
@@ -92,7 +92,7 @@ func (uri *ContentURI) UnmarshalJSON(raw []byte) (err error) {
*uri = ContentURI{}
return nil
} else if len(raw) < 2 || raw[0] != '"' || raw[len(raw)-1] != '"' {
- return fmt.Errorf("ContentURI: %w", ErrInputNotJSONString)
+ return ErrInputNotJSONString
}
parsed, err := ParseContentURIBytes(raw[1 : len(raw)-1])
if err != nil {
diff --git a/id/crypto.go b/id/crypto.go
index ee857f78..355a84a8 100644
--- a/id/crypto.go
+++ b/id/crypto.go
@@ -53,34 +53,6 @@ const (
KeyBackupAlgorithmMegolmBackupV1 KeyBackupAlgorithm = "m.megolm_backup.v1.curve25519-aes-sha2"
)
-type KeySource string
-
-func (source KeySource) String() string {
- return string(source)
-}
-
-func (source KeySource) Int() int {
- switch source {
- case KeySourceDirect:
- return 100
- case KeySourceBackup:
- return 90
- case KeySourceImport:
- return 80
- case KeySourceForward:
- return 50
- default:
- return 0
- }
-}
-
-const (
- KeySourceDirect KeySource = "direct"
- KeySourceBackup KeySource = "backup"
- KeySourceImport KeySource = "import"
- KeySourceForward KeySource = "forward"
-)
-
// BackupVersion is an arbitrary string that identifies a server side key backup.
type KeyBackupVersion string
diff --git a/id/trust.go b/id/trust.go
index 6255093e..04f6e36b 100644
--- a/id/trust.go
+++ b/id/trust.go
@@ -16,7 +16,6 @@ type TrustState int
const (
TrustStateBlacklisted TrustState = -100
- TrustStateDeviceKeyMismatch TrustState = -5
TrustStateUnset TrustState = 0
TrustStateUnknownDevice TrustState = 10
TrustStateForwarded TrustState = 20
@@ -24,7 +23,7 @@ const (
TrustStateCrossSignedTOFU TrustState = 100
TrustStateCrossSignedVerified TrustState = 200
TrustStateVerified TrustState = 300
- TrustStateInvalid TrustState = -2147483647
+ TrustStateInvalid TrustState = (1 << 31) - 1
)
func (ts *TrustState) UnmarshalText(data []byte) error {
@@ -45,8 +44,6 @@ func ParseTrustState(val string) TrustState {
switch strings.ToLower(val) {
case "blacklisted":
return TrustStateBlacklisted
- case "device-key-mismatch":
- return TrustStateDeviceKeyMismatch
case "unverified":
return TrustStateUnset
case "cross-signed-untrusted":
@@ -70,8 +67,6 @@ func (ts TrustState) String() string {
switch ts {
case TrustStateBlacklisted:
return "blacklisted"
- case TrustStateDeviceKeyMismatch:
- return "device-key-mismatch"
case TrustStateUnset:
return "unverified"
case TrustStateCrossSignedUntrusted:
diff --git a/mediaproxy/mediaproxy.go b/mediaproxy/mediaproxy.go
index 4d2bc7cf..2063675a 100644
--- a/mediaproxy/mediaproxy.go
+++ b/mediaproxy/mediaproxy.go
@@ -143,7 +143,6 @@ func New(serverName string, serverKey string, getMedia GetMediaFunc) (*MediaProx
}
mp.FederationRouter = http.NewServeMux()
mp.FederationRouter.HandleFunc("GET /v1/media/download/{mediaID}", mp.DownloadMediaFederation)
- mp.FederationRouter.HandleFunc("GET /v1/media/thumbnail/{mediaID}", mp.DownloadMediaFederation)
mp.FederationRouter.HandleFunc("GET /v1/version", mp.KeyServer.GetServerVersion)
mp.ClientMediaRouter = http.NewServeMux()
mp.ClientMediaRouter.HandleFunc("GET /download/{serverName}/{mediaID}", mp.DownloadMedia)
diff --git a/mockserver/mockserver.go b/mockserver/mockserver.go
index 507c24a5..e52c387a 100644
--- a/mockserver/mockserver.go
+++ b/mockserver/mockserver.go
@@ -231,7 +231,7 @@ func (ms *MockServer) postKeysUpload(w http.ResponseWriter, r *http.Request) {
}
func (ms *MockServer) postDeviceSigningUpload(w http.ResponseWriter, r *http.Request) {
- var req mautrix.UploadCrossSigningKeysReq[any]
+ var req mautrix.UploadCrossSigningKeysReq
mustDecode(r, &req)
userID := ms.getUserID(r).UserID
diff --git a/requests.go b/requests.go
index cc8b7266..f0287b3c 100644
--- a/requests.go
+++ b/requests.go
@@ -66,14 +66,14 @@ const (
)
// ReqRegister is the JSON request for https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3register
-type ReqRegister[UIAType any] struct {
+type ReqRegister struct {
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
DeviceID id.DeviceID `json:"device_id,omitempty"`
InitialDeviceDisplayName string `json:"initial_device_display_name,omitempty"`
InhibitLogin bool `json:"inhibit_login,omitempty"`
RefreshToken bool `json:"refresh_token,omitempty"`
- Auth UIAType `json:"auth,omitempty"`
+ Auth interface{} `json:"auth,omitempty"`
// Type for registration, only used for appservice user registrations
// https://spec.matrix.org/v1.2/application-service-api/#server-admin-style-permissions
@@ -320,11 +320,11 @@ func (csk *CrossSigningKeys) FirstKey() id.Ed25519 {
return ""
}
-type UploadCrossSigningKeysReq[UIAType any] struct {
+type UploadCrossSigningKeysReq struct {
Master CrossSigningKeys `json:"master_key"`
SelfSigning CrossSigningKeys `json:"self_signing_key"`
UserSigning CrossSigningKeys `json:"user_signing_key"`
- Auth UIAType `json:"auth,omitempty"`
+ Auth interface{} `json:"auth,omitempty"`
}
type KeyMap map[id.DeviceKeyID]string
@@ -367,12 +367,13 @@ type ReqSendToDevice struct {
}
type ReqSendEvent struct {
- Timestamp int64
- TransactionID string
- UnstableDelay time.Duration
- UnstableStickyDuration time.Duration
- DontEncrypt bool
- MeowEventID id.EventID
+ Timestamp int64
+ TransactionID string
+ UnstableDelay time.Duration
+
+ DontEncrypt bool
+
+ MeowEventID id.EventID
}
type ReqDelayedEvents struct {
@@ -392,14 +393,14 @@ type ReqDeviceInfo struct {
}
// ReqDeleteDevice is the JSON request for https://spec.matrix.org/v1.2/client-server-api/#delete_matrixclientv3devicesdeviceid
-type ReqDeleteDevice[UIAType any] struct {
- Auth UIAType `json:"auth,omitempty"`
+type ReqDeleteDevice struct {
+ Auth interface{} `json:"auth,omitempty"`
}
// ReqDeleteDevices is the JSON request for https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3delete_devices
-type ReqDeleteDevices[UIAType any] struct {
+type ReqDeleteDevices struct {
Devices []id.DeviceID `json:"devices"`
- Auth UIAType `json:"auth,omitempty"`
+ Auth interface{} `json:"auth,omitempty"`
}
type ReqPutPushRule struct {
diff --git a/responses.go b/responses.go
index 4fbe1fbc..d822c84b 100644
--- a/responses.go
+++ b/responses.go
@@ -258,7 +258,6 @@ func (r *UserDirectoryEntry) MarshalJSON() ([]byte, error) {
type RespMutualRooms struct {
Joined []id.RoomID `json:"joined"`
NextBatch string `json:"next_batch,omitempty"`
- Count int `json:"count,omitempty"`
}
type RespRoomSummary struct {
@@ -342,13 +341,6 @@ type LazyLoadSummary struct {
InvitedMemberCount *int `json:"m.invited_member_count,omitempty"`
}
-func (lls *LazyLoadSummary) MemberCount() int {
- if lls == nil {
- return 0
- }
- return ptr.Val(lls.JoinedMemberCount) + ptr.Val(lls.InvitedMemberCount)
-}
-
func (lls *LazyLoadSummary) Equal(other *LazyLoadSummary) bool {
if lls == other {
return true
diff --git a/version.go b/version.go
index f00bbf39..b0e31c7e 100644
--- a/version.go
+++ b/version.go
@@ -8,7 +8,7 @@ import (
"strings"
)
-const Version = "v0.26.3"
+const Version = "v0.26.2"
var GoModVersion = ""
var Commit = ""
diff --git a/versions.go b/versions.go
index 61b2e4ea..8ae82a06 100644
--- a/versions.go
+++ b/versions.go
@@ -63,8 +63,7 @@ var (
FeatureAsyncUploads = UnstableFeature{UnstableFlag: "fi.mau.msc2246.stable", SpecVersion: SpecV17}
FeatureAppservicePing = UnstableFeature{UnstableFlag: "fi.mau.msc2659.stable", SpecVersion: SpecV17}
FeatureAuthenticatedMedia = UnstableFeature{UnstableFlag: "org.matrix.msc3916.stable", SpecVersion: SpecV111}
- FeatureUnstableMutualRooms = UnstableFeature{UnstableFlag: "uk.half-shot.msc2666.query_mutual_rooms"}
- FeatureStableMutualRooms = UnstableFeature{UnstableFlag: "uk.half-shot.msc2666.query_mutual_rooms.stable" /*, SpecVersion: SpecV118*/}
+ FeatureMutualRooms = UnstableFeature{UnstableFlag: "uk.half-shot.msc2666.query_mutual_rooms"}
FeatureUserRedaction = UnstableFeature{UnstableFlag: "org.matrix.msc4194"}
FeatureViewRedactedContent = UnstableFeature{UnstableFlag: "fi.mau.msc2815"}
FeatureUnstableAccountModeration = UnstableFeature{UnstableFlag: "uk.timedout.msc4323"}
@@ -81,7 +80,6 @@ var (
BeeperFeatureAccountDataMute = UnstableFeature{UnstableFlag: "com.beeper.account_data_mute"}
BeeperFeatureInboxState = UnstableFeature{UnstableFlag: "com.beeper.inbox_state"}
BeeperFeatureArbitraryMemberChange = UnstableFeature{UnstableFlag: "com.beeper.arbitrary_member_change"}
- BeeperFeatureEphemeralEvents = UnstableFeature{UnstableFlag: "com.beeper.ephemeral"}
)
func (versions *RespVersions) Supports(feature UnstableFeature) bool {