diff --git a/api/_responses/redirect.go b/api/_responses/redirect.go index 1e8c66c4..13a7303d 100644 --- a/api/_responses/redirect.go +++ b/api/_responses/redirect.go @@ -1,9 +1,58 @@ package _responses +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "net/url" + "strconv" + "time" + + "github.com/t2bot/matrix-media-repo/api/_apimeta" + "github.com/t2bot/matrix-media-repo/common/rcontext" +) + type RedirectResponse struct { ToUrl string } -func Redirect(url string) *RedirectResponse { - return &RedirectResponse{ToUrl: url} +func Redirect(ctx rcontext.RequestContext, toUrl string, auth _apimeta.AuthContext) *RedirectResponse { + if auth.IsAuthenticated() { + // Figure out who is authenticated here, as that affects the expiration time + var expirationTime time.Time + if auth.Server.ServerName != "" { + expirationTime = time.Now().Add(time.Minute) + } else { + expirationTime = time.Now().Add(time.Minute * 5) + } + + // Append the expiration time to the URL + toUrl = appendQueryParam(toUrl, "matrix_exp", strconv.FormatInt(expirationTime.UnixMilli(), 10)) + + // Prepare our HMAC message contents as a JSON object + hmacMessage := toUrl + "||" + if auth.User.UserId != "" { + hmacMessage += auth.User.AccessToken + } + + // Actually do the HMAC + mac := hmac.New(sha256.New, []byte("THIS_IS_A_SECRET_KEY")) // TODO: @@ Actual secret key + mac.Write([]byte(hmacMessage)) + verifyHmac := mac.Sum(nil) + + // Append the HMAC to the URL + toUrl = appendQueryParam(toUrl, "matrix_verify", hex.EncodeToString(verifyHmac)) + } + return &RedirectResponse{ToUrl: toUrl} +} + +func appendQueryParam(toUrl string, key string, val string) string { + parsedUrl, err := url.Parse(toUrl) + if err != nil { + panic(err) // it wouldn't have worked anyways + } + qs := parsedUrl.Query() + qs.Set(key, val) + parsedUrl.RawQuery = qs.Encode() + return parsedUrl.String() } diff --git a/api/custom/byid.go b/api/custom/byid.go new file mode 100644 index 00000000..8b64f25f --- /dev/null +++ b/api/custom/byid.go @@ -0,0 +1,46 @@ +package custom + +import ( + "net/http" + + "github.com/t2bot/matrix-media-repo/api/_apimeta" + "github.com/t2bot/matrix-media-repo/api/_responses" + "github.com/t2bot/matrix-media-repo/api/_routers" + "github.com/t2bot/matrix-media-repo/common/rcontext" + "github.com/t2bot/matrix-media-repo/database" + "github.com/t2bot/matrix-media-repo/datastores" + "github.com/t2bot/matrix-media-repo/pipelines/_steps/download" +) + +func GetMediaById(r *http.Request, rctx rcontext.RequestContext, user _apimeta.UserInfo) interface{} { + if !user.IsShared { + return _responses.AuthFailed() + } + + // TODO: This is beyond dangerous and needs proper filtering + + db := database.GetInstance().Media.Prepare(rctx) + ds, err := datastores.Pick(rctx, datastores.LocalMediaKind) + if err != nil { + panic(err) + } + objectId := _routers.GetParam("objectId", r) + medias, err := db.GetByLocation(ds.Id, objectId) + if err != nil { + panic(err) + } + + media := medias[0] + stream, err := download.OpenStream(rctx, media.Locatable) + if err != nil { + panic(err) + } + + return &_responses.DownloadResponse{ + ContentType: media.ContentType, + Filename: media.UploadName, + SizeBytes: media.SizeBytes, + Data: stream, + TargetDisposition: "infer", + } +} diff --git a/api/r0/download.go b/api/r0/download.go index a4908025..1ddf9460 100644 --- a/api/r0/download.go +++ b/api/r0/download.go @@ -112,7 +112,7 @@ func DownloadMedia(r *http.Request, rctx rcontext.RequestContext, auth _apimeta. } else if errors.Is(err, common.ErrMediaNotYetUploaded) { return _responses.NotYetUploaded() } else if errors.As(err, &redirect) { - return _responses.Redirect(redirect.RedirectUrl) + return _responses.Redirect(rctx, redirect.RedirectUrl, auth) } rctx.Log.Error("Unexpected error locating media: ", err) sentry.CaptureException(err) diff --git a/api/r0/thumbnail.go b/api/r0/thumbnail.go index 322b3ea8..e343ae87 100644 --- a/api/r0/thumbnail.go +++ b/api/r0/thumbnail.go @@ -184,7 +184,7 @@ func ThumbnailMedia(r *http.Request, rctx rcontext.RequestContext, auth _apimeta } } } else if errors.As(err, &redirect) { - return _responses.Redirect(redirect.RedirectUrl) + return _responses.Redirect(rctx, redirect.RedirectUrl, auth) } rctx.Log.Error("Unexpected error locating media: ", err) sentry.CaptureException(err) diff --git a/api/routes.go b/api/routes.go index c5fd4cdb..affcd8ef 100644 --- a/api/routes.go +++ b/api/routes.go @@ -19,6 +19,7 @@ import ( const PrefixMedia = "/_matrix/media" const PrefixClient = "/_matrix/client" const PrefixFederation = "/_matrix/federation" +const PrefixMMR = "/_mmr" func buildRoutes() http.Handler { counter := &_routers.RequestCounter{} @@ -61,6 +62,7 @@ func buildRoutes() http.Handler { purgeOneRoute := makeRoute(_routers.RequireAccessToken(custom.PurgeIndividualRecord, false), "purge_individual_media", counter) register([]string{"DELETE"}, PrefixMedia, "download/:server/:mediaId", mxUnstable, router, purgeOneRoute) register([]string{"GET"}, PrefixMedia, "usage", msc4034, router, makeRoute(_routers.RequireAccessToken(unstable.PublicUsage, false), "usage", counter)) + register([]string{"GET"}, PrefixMMR, "byid/:objectId", mxNoVersion, router, makeRoute(_routers.RequireRepoAdmin(custom.GetMediaById), "byid", counter)) // Custom and top-level features router.Handler("GET", fmt.Sprintf("%s/version", PrefixMedia), makeRoute(_routers.OptionalAccessToken(custom.GetVersion), "get_version", counter))