Skip to content

Commit

Permalink
wip avatar proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
tulir committed Jun 29, 2024
1 parent a126a36 commit b9efb4d
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 16 deletions.
3 changes: 3 additions & 0 deletions config/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ type BridgeConfig struct {

PortalMessageBuffer int `yaml:"portal_message_buffer"`

PublicAddress string `yaml:"public_address"`
AvatarProxyKey string `yaml:"avatar_proxy_key"`

DeliveryReceipts bool `yaml:"delivery_receipts"`
MessageStatusEvents bool `yaml:"message_status_events"`
MessageErrorNotices bool `yaml:"message_error_notices"`
Expand Down
8 changes: 6 additions & 2 deletions config/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ import (
func DoUpgrade(helper *up.Helper) {
bridgeconfig.Upgrader.DoUpgrade(helper)

helper.Copy(up.Str|up.Null, "homeserver", "public_address")

helper.Copy(up.Str, "bridge", "username_template")
helper.Copy(up.Str, "bridge", "displayname_template")
helper.Copy(up.Str, "bridge", "channel_name_template")
Expand All @@ -42,6 +40,12 @@ func DoUpgrade(helper *up.Helper) {
helper.Copy(up.Str, "bridge", "private_chat_portal_meta")
}
helper.Copy(up.Int, "bridge", "startup_private_channel_create_limit")
helper.Copy(up.Str|up.Null, "bridge", "public_address")
if apkey, ok := helper.Get(up.Str, "bridge", "avatar_proxy_key"); !ok || apkey == "generate" {
helper.Set(up.Str, random.String(32), "bridge", "avatar_proxy_key")
} else {
helper.Copy(up.Str, "bridge", "avatar_proxy_key")
}
helper.Copy(up.Int, "bridge", "portal_message_buffer")
helper.Copy(up.Bool, "bridge", "delivery_receipts")
helper.Copy(up.Bool, "bridge", "message_status_events")
Expand Down
10 changes: 7 additions & 3 deletions example-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
homeserver:
# The address that this appservice can use to connect to the homeserver.
address: https://matrix.example.com
# Publicly accessible base URL for media, used for avatars in relay mode.
# If not set, the connection address above will be used.
public_address: null
# The domain of the homeserver (also known as server_name, used for MXIDs, etc).
domain: example.com

Expand Down Expand Up @@ -113,6 +110,13 @@ bridge:
# If set to `never`, DM rooms will never have names and avatars set.
private_chat_portal_meta: default

# Publicly accessible base URL that Discord can use to reach the bridge, used for avatars in relay mode.
# If not set, avatars will not be bridged. Only the /mautrix-discord/avatar/{server}/{id}/{hash} endpoint is used on this address.
# This should not have a trailing slash, the endpoint above will be appended to the provided address.
public_address: null
# A random key used to sign the avatar URLs. The bridge will only accept requests with a valid signature.
avatar_proxy_key: generate

portal_message_buffer: 128

# Number of private channel portals to create on bridge startup.
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ require (
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/stretchr/testify v1.8.4
github.com/yuin/goldmark v1.6.0
go.mau.fi/util v0.2.1
go.mau.fi/util v0.2.2-0.20231228160422-22fdd4bbddeb
golang.org/x/exp v0.0.0-20231219180239-dc181d75b848
golang.org/x/sync v0.5.0
maunium.net/go/maulogger/v2 v2.4.1
maunium.net/go/mautrix v0.16.3-0.20240218195727-4ceb1123b660
maunium.net/go/mautrix v0.16.3-0.20240620210949-e596c781a493
)

require (
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ 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.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68=
github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mau.fi/util v0.2.1 h1:eazulhFE/UmjOFtPrGg6zkF5YfAyiDzQb8ihLMbsPWw=
go.mau.fi/util v0.2.1/go.mod h1:MjlzCQEMzJ+G8RsPawHzpLB8rwTo3aPIjG5FzBvQT/c=
go.mau.fi/util v0.2.2-0.20231228160422-22fdd4bbddeb h1:Is+6vDKgINRy9KHodvi7NElxoDaWA8sc2S3cF3+QWjs=
go.mau.fi/util v0.2.2-0.20231228160422-22fdd4bbddeb/go.mod h1:tiBX6nxVSOjU89jVQ7wBh3P8KjM26Lv1k7/I5QdSvBw=
go.mau.fi/zeroconfig v0.1.2 h1:DKOydWnhPMn65GbXZOafgkPm11BvFashZWLct0dGFto=
go.mau.fi/zeroconfig v0.1.2/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70=
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
Expand All @@ -72,5 +72,5 @@ maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M=
maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA=
maunium.net/go/maulogger/v2 v2.4.1 h1:N7zSdd0mZkB2m2JtFUsiGTQQAdP0YeFWT7YMc80yAL8=
maunium.net/go/maulogger/v2 v2.4.1/go.mod h1:omPuYwYBILeVQobz8uO3XC8DIRuEb5rXYlQSuqrbCho=
maunium.net/go/mautrix v0.16.3-0.20240218195727-4ceb1123b660 h1:ZPg1i0wsyEs5ee7z6Gn4mSRsq9BtDV//rfYTGs82l8c=
maunium.net/go/mautrix v0.16.3-0.20240218195727-4ceb1123b660/go.mod h1:YL4l4rZB46/vj/ifRMEjcibbvHjgxHftOF1SgmruLu4=
maunium.net/go/mautrix v0.16.3-0.20240620210949-e596c781a493 h1:EEctvWgZUPJp0Py302kcorQ4TYjUhR0L58z8O2SV8Mk=
maunium.net/go/mautrix v0.16.3-0.20240620210949-e596c781a493/go.mod h1:gCgLw/4c1a8QsiOWTdUdXlt5cYdE0rJ9wLeZQKPD58Q=
4 changes: 4 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package main

import (
_ "embed"
"net/http"
"sync"

"go.mau.fi/util/configupgrade"
Expand Down Expand Up @@ -105,6 +106,9 @@ func (br *DiscordBridge) Start() {
if br.Config.Bridge.Provisioning.SharedSecret != "disable" {
br.provisioning = newProvisioningAPI(br)
}
if br.Config.Bridge.PublicAddress != "" {
br.AS.Router.HandleFunc("/mautrix-discord/avatar/{server}/{mediaID}/{checksum}", br.serveMediaProxy).Methods(http.MethodGet)
}
br.DMA = newDirectMediaAPI(br)
br.WaitWebsocketConnected()
go br.startUsers()
Expand Down
68 changes: 63 additions & 5 deletions portal.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ package main
import (
"bytes"
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"reflect"
"regexp"
Expand All @@ -17,6 +21,7 @@ import (

"github.com/bwmarrin/discordgo"
"github.com/gabriel-vasile/mimetype"
"github.com/gorilla/mux"
"github.com/rs/zerolog"
"go.mau.fi/util/exsync"
"go.mau.fi/util/variationselector"
Expand Down Expand Up @@ -1370,18 +1375,71 @@ func (portal *Portal) sendMessageMetrics(evt *event.Event, err error, part strin
}
}

func (br *DiscordBridge) serveMediaProxy(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
mxc := id.ContentURI{
Homeserver: vars["server"],
FileID: vars["mediaID"],
}
checksum, err := base64.RawURLEncoding.DecodeString(vars["checksum"])
if err != nil || len(checksum) != 32 {
w.WriteHeader(http.StatusNotFound)
return
}
_, expectedChecksum := br.hashMediaProxyURL(mxc)
if !hmac.Equal(checksum, expectedChecksum) {
w.WriteHeader(http.StatusNotFound)
return
}
reader, err := br.Bot.Download(mxc)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
buf := make([]byte, 32*1024)
n, err := io.ReadFull(reader, buf)
if err != nil && !errors.Is(err, io.EOF) {
w.WriteHeader(http.StatusBadGateway)
return
}
w.Header().Add("Content-Type", http.DetectContentType(buf[:n]))
if n < len(buf) {
w.Header().Add("Content-Length", strconv.Itoa(n))
}
w.WriteHeader(http.StatusOK)
_, err = w.Write(buf[:n])
if err != nil {
return
}
if n >= len(buf) {
_, _ = io.CopyBuffer(w, reader, buf)
}
}

func (br *DiscordBridge) hashMediaProxyURL(mxc id.ContentURI) (string, []byte) {
path := fmt.Sprintf("/mautrix-discord/avatar/%s/%s/", mxc.Homeserver, mxc.FileID)
checksum := hmac.New(sha256.New, []byte(br.Config.Bridge.AvatarProxyKey))
checksum.Write([]byte(path))
return path, checksum.Sum(nil)
}

func (br *DiscordBridge) makeMediaProxyURL(mxc id.ContentURI) string {
if br.Config.Bridge.PublicAddress == "" {
return ""
}
path, checksum := br.hashMediaProxyURL(mxc)
return br.Config.Bridge.PublicAddress + path + base64.RawURLEncoding.EncodeToString(checksum)
}

func (portal *Portal) getRelayUserMeta(sender *User) (name, avatarURL string) {
member := portal.bridge.StateStore.GetMember(portal.MXID, sender.MXID)
name = member.Displayname
if name == "" {
name = sender.MXID.String()
}
mxc := member.AvatarURL.ParseOrIgnore()
if !mxc.IsEmpty() {
avatarURL = mautrix.BuildURL(
portal.bridge.PublicHSAddress,
"_matrix", "media", "v3", "download", mxc.Homeserver, mxc.FileID,
).String()
if !mxc.IsEmpty() && portal.bridge.Config.Bridge.PublicAddress != "" {
avatarURL = portal.bridge.makeMediaProxyURL(mxc)
}
return
}
Expand Down

0 comments on commit b9efb4d

Please sign in to comment.