Skip to content

Commit 9254a5d

Browse files
committed
Add base for v2 bridge architecture
1 parent 2195043 commit 9254a5d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+5097
-4
lines changed

appservice/http.go

+7
Original file line numberDiff line numberDiff line change
@@ -211,10 +211,17 @@ func (as *AppService) handleEvents(ctx context.Context, evts []*event.Event, def
211211
for _, evt := range evts {
212212
evt.Mautrix.ReceivedAt = time.Now()
213213
if defaultTypeClass != event.UnknownEventType {
214+
if defaultTypeClass == event.EphemeralEventType {
215+
evt.Mautrix.EventSource = event.SourceEphemeral
216+
} else if defaultTypeClass == event.ToDeviceEventType {
217+
evt.Mautrix.EventSource = event.SourceToDevice
218+
}
214219
evt.Type.Class = defaultTypeClass
215220
} else if evt.StateKey != nil {
221+
evt.Mautrix.EventSource = event.SourceTimeline & event.SourceJoin
216222
evt.Type.Class = event.StateEventType
217223
} else {
224+
evt.Mautrix.EventSource = event.SourceTimeline
218225
evt.Type.Class = event.MessageEventType
219226
}
220227
err := evt.Content.ParseRaw(evt.Type)

bridgev2/bridge.go

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright (c) 2024 Tulir Asokan
2+
//
3+
// This Source Code Form is subject to the terms of the Mozilla Public
4+
// License, v. 2.0. If a copy of the MPL was not distributed with this
5+
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
7+
package bridgev2
8+
9+
import (
10+
"context"
11+
"errors"
12+
"os"
13+
"os/signal"
14+
"sync"
15+
"syscall"
16+
17+
"github.com/rs/zerolog"
18+
"go.mau.fi/util/dbutil"
19+
"go.mau.fi/util/exerrors"
20+
21+
"maunium.net/go/mautrix/bridgev2/database"
22+
"maunium.net/go/mautrix/bridgev2/networkid"
23+
24+
"maunium.net/go/mautrix/id"
25+
)
26+
27+
var ErrNotLoggedIn = errors.New("not logged in")
28+
29+
type Bridge struct {
30+
ID networkid.BridgeID
31+
DB *database.Database
32+
Log zerolog.Logger
33+
34+
Matrix MatrixConnector
35+
Bot MatrixAPI
36+
Network NetworkConnector
37+
Commands *CommandProcessor
38+
39+
// TODO move to config
40+
CommandPrefix string
41+
42+
usersByMXID map[id.UserID]*User
43+
userLoginsByID map[networkid.UserLoginID]*UserLogin
44+
portalsByID map[networkid.PortalID]*Portal
45+
portalsByMXID map[id.RoomID]*Portal
46+
ghostsByID map[networkid.UserID]*Ghost
47+
cacheLock sync.Mutex
48+
}
49+
50+
func NewBridge(bridgeID networkid.BridgeID, db *dbutil.Database, log zerolog.Logger, matrix MatrixConnector, network NetworkConnector) *Bridge {
51+
br := &Bridge{
52+
ID: bridgeID,
53+
DB: database.New(bridgeID, db),
54+
Log: log,
55+
56+
Matrix: matrix,
57+
Network: network,
58+
59+
usersByMXID: make(map[id.UserID]*User),
60+
userLoginsByID: make(map[networkid.UserLoginID]*UserLogin),
61+
portalsByID: make(map[networkid.PortalID]*Portal),
62+
portalsByMXID: make(map[id.RoomID]*Portal),
63+
ghostsByID: make(map[networkid.UserID]*Ghost),
64+
}
65+
br.Commands = NewProcessor(br)
66+
br.Matrix.Init(br)
67+
br.Bot = br.Matrix.BotIntent()
68+
br.Network.Init(br)
69+
return br
70+
}
71+
72+
func (br *Bridge) Start() {
73+
br.Log.Info().Msg("Starting bridge")
74+
ctx := br.Log.WithContext(context.Background())
75+
76+
exerrors.PanicIfNotNil(br.DB.Upgrade(ctx))
77+
br.Log.Info().Msg("Starting Matrix connector")
78+
exerrors.PanicIfNotNil(br.Matrix.Start(ctx))
79+
br.Log.Info().Msg("Starting network connector")
80+
exerrors.PanicIfNotNil(br.Network.Start(ctx))
81+
82+
logins, err := br.GetAllUserLogins(ctx)
83+
if err != nil {
84+
br.Log.Fatal().Err(err).Msg("Failed to get user logins")
85+
}
86+
for _, login := range logins {
87+
br.Log.Info().Str("id", string(login.ID)).Msg("Starting user login")
88+
err = login.Client.Connect(login.Log.WithContext(ctx))
89+
if err != nil {
90+
br.Log.Err(err).Msg("Failed to connect existing client")
91+
}
92+
}
93+
if len(logins) == 0 {
94+
br.Log.Info().Msg("No user logins found")
95+
}
96+
97+
br.Log.Info().Msg("Bridge started")
98+
c := make(chan os.Signal, 1)
99+
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
100+
<-c
101+
br.Log.Info().Msg("Shutting down bridge")
102+
}

bridgev2/bridgeconfig/appservice.go

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Copyright (c) 2024 Tulir Asokan
2+
//
3+
// This Source Code Form is subject to the terms of the Mozilla Public
4+
// License, v. 2.0. If a copy of the MPL was not distributed with this
5+
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
7+
package bridgeconfig
8+
9+
import (
10+
"fmt"
11+
"html/template"
12+
"regexp"
13+
"strings"
14+
15+
"go.mau.fi/util/exerrors"
16+
"go.mau.fi/util/random"
17+
18+
"maunium.net/go/mautrix/appservice"
19+
"maunium.net/go/mautrix/id"
20+
)
21+
22+
type AppserviceConfig struct {
23+
Address string `yaml:"address"`
24+
Hostname string `yaml:"hostname"`
25+
Port uint16 `yaml:"port"`
26+
27+
ID string `yaml:"id"`
28+
Bot BotUserConfig `yaml:"bot"`
29+
30+
ASToken string `yaml:"as_token"`
31+
HSToken string `yaml:"hs_token"`
32+
33+
EphemeralEvents bool `yaml:"ephemeral_events"`
34+
AsyncTransactions bool `yaml:"async_transactions"`
35+
36+
UsernameTemplate string `yaml:"username_template"`
37+
usernameTemplate *template.Template `yaml:"-"`
38+
}
39+
40+
func (asc *AppserviceConfig) FormatUsername(username string) string {
41+
if asc.usernameTemplate == nil {
42+
asc.usernameTemplate = exerrors.Must(template.New("username").Parse(asc.UsernameTemplate))
43+
}
44+
var buf strings.Builder
45+
_ = asc.usernameTemplate.Execute(&buf, username)
46+
return buf.String()
47+
}
48+
49+
func (config *Config) MakeUserIDRegex(matcher string) *regexp.Regexp {
50+
usernamePlaceholder := strings.ToLower(random.String(16))
51+
usernameTemplate := fmt.Sprintf("@%s:%s",
52+
config.AppService.FormatUsername(usernamePlaceholder),
53+
config.Homeserver.Domain)
54+
usernameTemplate = regexp.QuoteMeta(usernameTemplate)
55+
usernameTemplate = strings.Replace(usernameTemplate, usernamePlaceholder, matcher, 1)
56+
usernameTemplate = fmt.Sprintf("^%s$", usernameTemplate)
57+
return regexp.MustCompile(usernameTemplate)
58+
}
59+
60+
// GetRegistration copies the data from the bridge config into an *appservice.Registration struct.
61+
// This can't be used with the homeserver, see GenerateRegistration for generating files for the homeserver.
62+
func (asc *AppserviceConfig) GetRegistration() *appservice.Registration {
63+
reg := &appservice.Registration{}
64+
asc.copyToRegistration(reg)
65+
reg.SenderLocalpart = asc.Bot.Username
66+
reg.ServerToken = asc.HSToken
67+
reg.AppToken = asc.ASToken
68+
return reg
69+
}
70+
71+
func (asc *AppserviceConfig) copyToRegistration(registration *appservice.Registration) {
72+
registration.ID = asc.ID
73+
registration.URL = asc.Address
74+
falseVal := false
75+
registration.RateLimited = &falseVal
76+
registration.EphemeralEvents = asc.EphemeralEvents
77+
registration.SoruEphemeralEvents = asc.EphemeralEvents
78+
}
79+
80+
// GenerateRegistration generates a registration file for the homeserver.
81+
func (config *Config) GenerateRegistration() *appservice.Registration {
82+
registration := appservice.CreateRegistration()
83+
config.AppService.HSToken = registration.ServerToken
84+
config.AppService.ASToken = registration.AppToken
85+
config.AppService.copyToRegistration(registration)
86+
87+
registration.SenderLocalpart = random.String(32)
88+
botRegex := regexp.MustCompile(fmt.Sprintf("^@%s:%s$",
89+
regexp.QuoteMeta(config.AppService.Bot.Username),
90+
regexp.QuoteMeta(config.Homeserver.Domain)))
91+
registration.Namespaces.UserIDs.Register(botRegex, true)
92+
registration.Namespaces.UserIDs.Register(config.MakeUserIDRegex(".*"), true)
93+
94+
return registration
95+
}
96+
97+
func (config *Config) MakeAppService() *appservice.AppService {
98+
as := appservice.Create()
99+
as.HomeserverDomain = config.Homeserver.Domain
100+
_ = as.SetHomeserverURL(config.Homeserver.Address)
101+
as.Host.Hostname = config.AppService.Hostname
102+
as.Host.Port = config.AppService.Port
103+
as.Registration = config.AppService.GetRegistration()
104+
return as
105+
}
106+
107+
type BotUserConfig struct {
108+
Username string `yaml:"username"`
109+
Displayname string `yaml:"displayname"`
110+
Avatar string `yaml:"avatar"`
111+
112+
ParsedAvatar id.ContentURI `yaml:"-"`
113+
}
114+
115+
type serializableBUC BotUserConfig
116+
117+
func (buc *BotUserConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
118+
var sbuc serializableBUC
119+
err := unmarshal(&sbuc)
120+
if err != nil {
121+
return err
122+
}
123+
*buc = (BotUserConfig)(sbuc)
124+
if buc.Avatar != "" && buc.Avatar != "remove" {
125+
buc.ParsedAvatar, err = id.ParseContentURI(buc.Avatar)
126+
if err != nil {
127+
return fmt.Errorf("%w in bot avatar", err)
128+
}
129+
}
130+
return nil
131+
}

bridgev2/bridgeconfig/config.go

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright (c) 2024 Tulir Asokan
2+
//
3+
// This Source Code Form is subject to the terms of the Mozilla Public
4+
// License, v. 2.0. If a copy of the MPL was not distributed with this
5+
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
7+
package bridgeconfig
8+
9+
import (
10+
"go.mau.fi/util/dbutil"
11+
"go.mau.fi/zeroconfig"
12+
)
13+
14+
type Config struct {
15+
Homeserver HomeserverConfig `yaml:"homeserver"`
16+
AppService AppserviceConfig `yaml:"appservice"`
17+
Database dbutil.Config `yaml:"database"`
18+
Bridge BridgeConfig `yaml:"bridge"` // TODO this is more like matrix than bridge
19+
Provisioning ProvisioningConfig `yaml:"provisioning"`
20+
DoublePuppet DoublePuppetConfig `yaml:"double_puppet"`
21+
Encryption EncryptionConfig `yaml:"encryption"`
22+
Permissions PermissionConfig `yaml:"permissions"`
23+
ManagementRoomTexts ManagementRoomTexts `yaml:"management_room_texts"`
24+
Logging zeroconfig.Config `yaml:"logging"`
25+
}
26+
27+
type BridgeConfig struct {
28+
MessageStatusEvents bool `yaml:"message_status_events"`
29+
DeliveryReceipts bool `yaml:"delivery_receipts"`
30+
MessageErrorNotices bool `yaml:"message_error_notices"`
31+
SyncDirectChatList bool `yaml:"sync_direct_chat_list"`
32+
FederateRooms bool `yaml:"federate_rooms"`
33+
}
34+
35+
type ProvisioningConfig struct {
36+
Prefix string `yaml:"prefix"`
37+
SharedSecret string `yaml:"shared_secret"`
38+
DebugEndpoints bool `yaml:"debug_endpoints"`
39+
}
40+
41+
type DoublePuppetConfig struct {
42+
Servers map[string]string `yaml:"servers"`
43+
AllowDiscovery bool `yaml:"allow_discovery"`
44+
Secrets map[string]string `yaml:"secrets"`
45+
}
46+
47+
type ManagementRoomTexts struct {
48+
Welcome string `yaml:"welcome"`
49+
WelcomeConnected string `yaml:"welcome_connected"`
50+
WelcomeUnconnected string `yaml:"welcome_unconnected"`
51+
AdditionalHelp string `yaml:"additional_help"`
52+
}

bridgev2/bridgeconfig/encryption.go

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright (c) 2024 Tulir Asokan
2+
//
3+
// This Source Code Form is subject to the terms of the Mozilla Public
4+
// License, v. 2.0. If a copy of the MPL was not distributed with this
5+
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
7+
package bridgeconfig
8+
9+
import (
10+
"maunium.net/go/mautrix/id"
11+
)
12+
13+
type EncryptionConfig struct {
14+
Allow bool `yaml:"allow"`
15+
Default bool `yaml:"default"`
16+
Require bool `yaml:"require"`
17+
Appservice bool `yaml:"appservice"`
18+
19+
PlaintextMentions bool `yaml:"plaintext_mentions"`
20+
21+
PickleKey string `yaml:"pickle_key"`
22+
23+
DeleteKeys struct {
24+
DeleteOutboundOnAck bool `yaml:"delete_outbound_on_ack"`
25+
DontStoreOutbound bool `yaml:"dont_store_outbound"`
26+
RatchetOnDecrypt bool `yaml:"ratchet_on_decrypt"`
27+
DeleteFullyUsedOnDecrypt bool `yaml:"delete_fully_used_on_decrypt"`
28+
DeletePrevOnNewSession bool `yaml:"delete_prev_on_new_session"`
29+
DeleteOnDeviceDelete bool `yaml:"delete_on_device_delete"`
30+
PeriodicallyDeleteExpired bool `yaml:"periodically_delete_expired"`
31+
DeleteOutdatedInbound bool `yaml:"delete_outdated_inbound"`
32+
} `yaml:"delete_keys"`
33+
34+
VerificationLevels struct {
35+
Receive id.TrustState `yaml:"receive"`
36+
Send id.TrustState `yaml:"send"`
37+
Share id.TrustState `yaml:"share"`
38+
} `yaml:"verification_levels"`
39+
AllowKeySharing bool `yaml:"allow_key_sharing"`
40+
41+
Rotation struct {
42+
EnableCustom bool `yaml:"enable_custom"`
43+
Milliseconds int64 `yaml:"milliseconds"`
44+
Messages int `yaml:"messages"`
45+
46+
DisableDeviceChangeKeyRotation bool `yaml:"disable_device_change_key_rotation"`
47+
} `yaml:"rotation"`
48+
}

bridgev2/bridgeconfig/homeserver.go

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright (c) 2024 Tulir Asokan
2+
//
3+
// This Source Code Form is subject to the terms of the Mozilla Public
4+
// License, v. 2.0. If a copy of the MPL was not distributed with this
5+
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
7+
package bridgeconfig
8+
9+
type HomeserverSoftware string
10+
11+
const (
12+
SoftwareStandard HomeserverSoftware = "standard"
13+
SoftwareAsmux HomeserverSoftware = "asmux"
14+
SoftwareHungry HomeserverSoftware = "hungry"
15+
)
16+
17+
var AllowedHomeserverSoftware = map[HomeserverSoftware]bool{
18+
SoftwareStandard: true,
19+
SoftwareAsmux: true,
20+
SoftwareHungry: true,
21+
}
22+
23+
type HomeserverConfig struct {
24+
Address string `yaml:"address"`
25+
Domain string `yaml:"domain"`
26+
AsyncMedia bool `yaml:"async_media"`
27+
28+
PublicAddress string `yaml:"public_address,omitempty"`
29+
30+
Software HomeserverSoftware `yaml:"software"`
31+
32+
StatusEndpoint string `yaml:"status_endpoint"`
33+
MessageSendCheckpointEndpoint string `yaml:"message_send_checkpoint_endpoint"`
34+
35+
Websocket bool `yaml:"websocket"`
36+
WSProxy string `yaml:"websocket_proxy"`
37+
WSPingInterval int `yaml:"ping_interval_seconds"`
38+
}

0 commit comments

Comments
 (0)