Skip to content

Commit ec59b61

Browse files
committed
Experiment with JWT-based worker redirection
1 parent eb774d8 commit ec59b61

File tree

7 files changed

+147
-14
lines changed

7 files changed

+147
-14
lines changed

api/_responses/redirect.go

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,91 @@
11
package _responses
22

3+
import (
4+
"crypto/rand"
5+
"crypto/rsa"
6+
"crypto/x509"
7+
"encoding/pem"
8+
"net/url"
9+
"os"
10+
"time"
11+
12+
"github.com/go-jose/go-jose/v4"
13+
"github.com/go-jose/go-jose/v4/jwt"
14+
"github.com/t2bot/matrix-media-repo/api/_apimeta"
15+
"github.com/t2bot/matrix-media-repo/common/rcontext"
16+
)
17+
18+
// TODO: Persist and store key (or use some other source of information)
19+
var jwtKey, keyErr = rsa.GenerateKey(rand.Reader, 2048)
20+
var jwtSig, jwtErr = jose.NewSigner(jose.SigningKey{Algorithm: jose.HS256, Key: []byte("0102030405060708090A0B0C0D0E0F10")}, (&jose.SignerOptions{}).WithType("JWT"))
21+
var jweEnc, jweErr = jose.NewEncrypter(jose.A128GCM, jose.Recipient{Algorithm: jose.RSA_OAEP, Key: &jwtKey.PublicKey}, nil)
22+
23+
func init() {
24+
// We don't expect these to happen, so just panic
25+
if keyErr != nil {
26+
panic(keyErr)
27+
}
28+
if jwtErr != nil {
29+
panic(jwtErr)
30+
}
31+
if jweErr != nil {
32+
panic(jweErr)
33+
}
34+
35+
f1, _ := os.Create("./gdpr-data/jwe.rsa")
36+
f2, _ := os.Create("./gdpr-data/jwe.rsa.pub")
37+
defer f1.Close()
38+
defer f2.Close()
39+
keyPem := pem.EncodeToMemory(&pem.Block{
40+
Type: "RSA PRIVATE KEY",
41+
Bytes: x509.MarshalPKCS1PrivateKey(jwtKey),
42+
})
43+
pubPem := pem.EncodeToMemory(&pem.Block{
44+
Type: "RSA PUBLIC KEY",
45+
Bytes: x509.MarshalPKCS1PublicKey(&jwtKey.PublicKey),
46+
})
47+
f1.Write(keyPem)
48+
f2.Write(pubPem)
49+
}
50+
351
type RedirectResponse struct {
452
ToUrl string
553
}
654

7-
func Redirect(url string) *RedirectResponse {
8-
return &RedirectResponse{ToUrl: url}
55+
func Redirect(ctx rcontext.RequestContext, toUrl string, auth _apimeta.AuthContext) *RedirectResponse {
56+
if auth.IsAuthenticated() {
57+
// Figure out who we're authenticating as, and add that as JWT claims
58+
claims := jwt.Claims{
59+
Issuer: ctx.Request.Host,
60+
}
61+
moreClaims := struct {
62+
Admin bool `json:"adm,omitempty"`
63+
AccessToken string `json:"tok,omitempty"`
64+
}{}
65+
if auth.Server.ServerName != "" {
66+
claims.Subject = auth.Server.ServerName
67+
68+
// The server won't necessarily re-auth itself with the redirect, so we just put an expiration on it instead
69+
claims.Expiry = jwt.NewNumericDate(time.Now().Add(2 * time.Minute))
70+
} else {
71+
claims.Subject = auth.User.UserId
72+
moreClaims.Admin = auth.User.IsShared
73+
moreClaims.AccessToken = auth.User.AccessToken
74+
}
75+
raw, err := jwt.Encrypted(jweEnc).Claims(claims).Claims(moreClaims).Serialize()
76+
if err != nil {
77+
panic(err) // should never happen if we've done things correctly
78+
}
79+
80+
// Now add the query string
81+
parsedUrl, err := url.Parse(toUrl)
82+
if err != nil {
83+
panic(err) // it wouldn't have worked anyways
84+
}
85+
qs := parsedUrl.Query()
86+
qs.Set("org.matrix.media_auth", raw)
87+
parsedUrl.RawQuery = qs.Encode()
88+
toUrl = parsedUrl.String()
89+
}
90+
return &RedirectResponse{ToUrl: toUrl}
991
}

api/custom/byid.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package custom
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/t2bot/matrix-media-repo/api/_apimeta"
7+
"github.com/t2bot/matrix-media-repo/api/_responses"
8+
"github.com/t2bot/matrix-media-repo/api/_routers"
9+
"github.com/t2bot/matrix-media-repo/common/rcontext"
10+
"github.com/t2bot/matrix-media-repo/database"
11+
"github.com/t2bot/matrix-media-repo/datastores"
12+
"github.com/t2bot/matrix-media-repo/pipelines/_steps/download"
13+
)
14+
15+
func GetMediaById(r *http.Request, rctx rcontext.RequestContext, user _apimeta.UserInfo) interface{} {
16+
if !user.IsShared {
17+
return _responses.AuthFailed()
18+
}
19+
20+
// TODO: This is beyond dangerous and needs proper filtering
21+
22+
db := database.GetInstance().Media.Prepare(rctx)
23+
ds, err := datastores.Pick(rctx, datastores.LocalMediaKind)
24+
if err != nil {
25+
panic(err)
26+
}
27+
objectId := _routers.GetParam("objectId", r)
28+
medias, err := db.GetByLocation(ds.Id, objectId)
29+
if err != nil {
30+
panic(err)
31+
}
32+
33+
media := medias[0]
34+
stream, err := download.OpenStream(rctx, media.Locatable)
35+
if err != nil {
36+
panic(err)
37+
}
38+
39+
return &_responses.DownloadResponse{
40+
ContentType: media.ContentType,
41+
Filename: media.UploadName,
42+
SizeBytes: media.SizeBytes,
43+
Data: stream,
44+
TargetDisposition: "infer",
45+
}
46+
}

api/r0/download.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ func DownloadMedia(r *http.Request, rctx rcontext.RequestContext, auth _apimeta.
112112
} else if errors.Is(err, common.ErrMediaNotYetUploaded) {
113113
return _responses.NotYetUploaded()
114114
} else if errors.As(err, &redirect) {
115-
return _responses.Redirect(redirect.RedirectUrl)
115+
return _responses.Redirect(rctx, redirect.RedirectUrl, auth)
116116
}
117117
rctx.Log.Error("Unexpected error locating media: ", err)
118118
sentry.CaptureException(err)

api/r0/thumbnail.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ func ThumbnailMedia(r *http.Request, rctx rcontext.RequestContext, auth _apimeta
184184
}
185185
}
186186
} else if errors.As(err, &redirect) {
187-
return _responses.Redirect(redirect.RedirectUrl)
187+
return _responses.Redirect(rctx, redirect.RedirectUrl, auth)
188188
}
189189
rctx.Log.Error("Unexpected error locating media: ", err)
190190
sentry.CaptureException(err)

api/routes.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
const PrefixMedia = "/_matrix/media"
2020
const PrefixClient = "/_matrix/client"
2121
const PrefixFederation = "/_matrix/federation"
22+
const PrefixMMR = "/_mmr"
2223

2324
func buildRoutes() http.Handler {
2425
counter := &_routers.RequestCounter{}
@@ -61,6 +62,7 @@ func buildRoutes() http.Handler {
6162
purgeOneRoute := makeRoute(_routers.RequireAccessToken(custom.PurgeIndividualRecord), "purge_individual_media", counter)
6263
register([]string{"DELETE"}, PrefixMedia, "download/:server/:mediaId", mxUnstable, router, purgeOneRoute)
6364
register([]string{"GET"}, PrefixMedia, "usage", msc4034, router, makeRoute(_routers.RequireAccessToken(unstable.PublicUsage), "usage", counter))
65+
register([]string{"GET"}, PrefixMMR, "byid/:objectId", mxNoVersion, router, makeRoute(_routers.RequireRepoAdmin(custom.GetMediaById), "byid", counter))
6466

6567
// Custom and top-level features
6668
router.Handler("GET", fmt.Sprintf("%s/version", PrefixMedia), makeRoute(_routers.OptionalAccessToken(custom.GetVersion), "get_version", counter))

go.mod

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,16 @@ require (
3838
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d
3939
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a
4040
github.com/sirupsen/logrus v1.9.3
41-
golang.org/x/crypto v0.23.0 // indirect
41+
golang.org/x/crypto v0.25.0 // indirect
4242
golang.org/x/image v0.18.0
4343
golang.org/x/net v0.25.0
4444
)
4545

4646
require (
4747
github.com/didip/tollbooth/v7 v7.0.1
48+
github.com/docker/docker v25.0.5+incompatible
4849
github.com/docker/go-connections v0.5.0
50+
github.com/go-jose/go-jose/v4 v4.0.4
4951
github.com/go-redsync/redsync/v4 v4.12.1
5052
github.com/julienschmidt/httprouter v1.3.0
5153
github.com/minio/minio-go/v7 v7.0.69
@@ -62,7 +64,7 @@ require (
6264
github.com/testcontainers/testcontainers-go v0.26.0
6365
github.com/testcontainers/testcontainers-go/modules/postgres v0.26.0
6466
golang.org/x/sync v0.7.0
65-
golang.org/x/term v0.20.0
67+
golang.org/x/term v0.22.0
6668
)
6769

6870
require (
@@ -81,7 +83,6 @@ require (
8183
github.com/davecgh/go-spew v1.1.1 // indirect
8284
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
8385
github.com/distribution/reference v0.5.0 // indirect
84-
github.com/docker/docker v25.0.5+incompatible // indirect
8586
github.com/docker/go-units v0.5.0 // indirect
8687
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect
8788
github.com/fatih/color v1.16.0 // indirect
@@ -139,7 +140,7 @@ require (
139140
go.opentelemetry.io/otel/trace v1.24.0 // indirect
140141
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f // indirect
141142
golang.org/x/mod v0.17.0 // indirect
142-
golang.org/x/sys v0.20.0 // indirect
143+
golang.org/x/sys v0.22.0 // indirect
143144
golang.org/x/text v0.16.0 // indirect
144145
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
145146
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c // indirect

go.sum

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWE
126126
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
127127
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
128128
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
129+
github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E=
130+
github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc=
129131
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
130132
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
131133
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -415,8 +417,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
415417
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
416418
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
417419
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
418-
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
419-
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
420+
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
421+
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
420422
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
421423
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f h1:3CW0unweImhOzd5FmYuRsD4Y4oQFKZIjAnKbjV4WIrw=
422424
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
@@ -498,16 +500,16 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
498500
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
499501
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
500502
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
501-
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
502-
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
503+
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
504+
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
503505
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
504506
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
505507
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
506508
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
507509
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
508510
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
509-
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
510-
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
511+
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
512+
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
511513
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
512514
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
513515
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=

0 commit comments

Comments
 (0)