mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-01 20:16:36 +08:00
refactor: split modules and harden scraping workflows
This commit is contained in:
@@ -84,6 +84,137 @@ func TestExternalURLUsesMediaScopedPlaybackToken(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestExternalURLPrefersBrowserPublicOriginOverForwardedSource(t *testing.T) {
|
||||
router, _, secret := newPlaybackScopeTestRouter(t)
|
||||
loginToken := signedTestToken(t, secret)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "http://origin.internal/api/playback/media-1/external-url", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+loginToken)
|
||||
req.Header.Set("X-Forwarded-Proto", "https")
|
||||
req.Header.Set("X-Forwarded-Host", "media.v6.agonyz.dpdns.org")
|
||||
req.Header.Set("X-MediaStation-Public-Origin", "https://media.agonyz.dpdns.org")
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d body=%s", w.Code, w.Body.String())
|
||||
}
|
||||
var payload struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &payload); err != nil {
|
||||
t.Fatalf("decode: %v", err)
|
||||
}
|
||||
streamURL, err := url.Parse(payload.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("parse stream url: %v", err)
|
||||
}
|
||||
if got, want := streamURL.Scheme+"://"+streamURL.Host, "https://media.agonyz.dpdns.org"; got != want {
|
||||
t.Fatalf("external url origin = %q, want %q; full url=%s", got, want, payload.URL)
|
||||
}
|
||||
if strings.Contains(payload.URL, "media.v6.agonyz.dpdns.org") {
|
||||
t.Fatalf("external url should not use forwarded source host: %s", payload.URL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExternalPlayersSanitizeBrowserPublicOrigin(t *testing.T) {
|
||||
router, _, secret := newPlaybackScopeTestRouter(t)
|
||||
loginToken := signedTestToken(t, secret)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "http://origin.internal/api/playback/media-1/external-players", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+loginToken)
|
||||
req.Header.Set("X-MediaStation-Public-Origin", "https://user:pass@media.agonyz.dpdns.org/sneaky/path?x=1#frag")
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d body=%s", w.Code, w.Body.String())
|
||||
}
|
||||
var payload struct {
|
||||
URL string `json:"url"`
|
||||
Players []struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
} `json:"players"`
|
||||
}
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &payload); err != nil {
|
||||
t.Fatalf("decode: %v", err)
|
||||
}
|
||||
if !strings.HasPrefix(payload.URL, "https://media.agonyz.dpdns.org/api/stream/media-1?") {
|
||||
t.Fatalf("sanitized stream url = %q", payload.URL)
|
||||
}
|
||||
if strings.Contains(payload.URL, "user:pass") || strings.Contains(payload.URL, "sneaky") || strings.Contains(payload.URL, "x=1") || strings.Contains(payload.URL, "#frag") {
|
||||
t.Fatalf("stream url contains unsafe origin components: %s", payload.URL)
|
||||
}
|
||||
for _, player := range payload.Players {
|
||||
if !strings.Contains(player.URL, "media.agonyz.dpdns.org") {
|
||||
t.Fatalf("%s player url does not include sanitized public host: %s", player.Name, player.URL)
|
||||
}
|
||||
if strings.Contains(player.URL, "user:pass") || strings.Contains(player.URL, "sneaky") {
|
||||
t.Fatalf("%s player url contains unsafe origin components: %s", player.Name, player.URL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExternalURLFallsBackToConfiguredPublicServerURL(t *testing.T) {
|
||||
router, svc, secret := newPlaybackScopeTestRouter(t)
|
||||
loginToken := signedTestToken(t, secret)
|
||||
if err := svc.Repo.Setting.Set(t.Context(), "app.server_url", "https://public.example.test"); err != nil {
|
||||
t.Fatalf("set public url: %v", err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "http://origin.internal/api/playback/media-1/external-url", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+loginToken)
|
||||
req.Header.Set("X-Forwarded-Proto", "https")
|
||||
req.Header.Set("X-Forwarded-Host", "source.example.test")
|
||||
req.Header.Set("X-MediaStation-Public-Origin", "javascript:alert(1)")
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d body=%s", w.Code, w.Body.String())
|
||||
}
|
||||
var payload struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &payload); err != nil {
|
||||
t.Fatalf("decode: %v", err)
|
||||
}
|
||||
if !strings.HasPrefix(payload.URL, "https://public.example.test/api/stream/media-1?") {
|
||||
t.Fatalf("external url = %q, want configured public origin", payload.URL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExternalURLPrefersConfiguredPublicServerURLOverLocalBrowserOrigin(t *testing.T) {
|
||||
router, svc, secret := newPlaybackScopeTestRouter(t)
|
||||
loginToken := signedTestToken(t, secret)
|
||||
if err := svc.Repo.Setting.Set(t.Context(), "app.server_url", "https://media.example.test"); err != nil {
|
||||
t.Fatalf("set public url: %v", err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "http://127.0.0.1:8080/api/playback/media-1/external-url", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+loginToken)
|
||||
req.Header.Set("X-MediaStation-Public-Origin", "http://127.0.0.1:8080")
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d body=%s", w.Code, w.Body.String())
|
||||
}
|
||||
var payload struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &payload); err != nil {
|
||||
t.Fatalf("decode: %v", err)
|
||||
}
|
||||
if !strings.HasPrefix(payload.URL, "https://media.example.test/api/stream/media-1?") {
|
||||
t.Fatalf("external url = %q, want configured public origin instead of localhost", payload.URL)
|
||||
}
|
||||
if strings.Contains(payload.URL, "127.0.0.1:8080") {
|
||||
t.Fatalf("external url should not keep local browser origin when public url is configured: %s", payload.URL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestScopedPlaybackTokenCannotStreamAnotherMedia(t *testing.T) {
|
||||
router, svc, _ := newPlaybackScopeTestRouter(t)
|
||||
user, err := svc.Repo.User.FindByID(t.Context(), "user-1")
|
||||
@@ -248,6 +379,7 @@ func newPlaybackScopeTestRouter(t *testing.T) (*gin.Engine, *service.Container,
|
||||
api := router.Group("/api")
|
||||
api.Use(middleware.AuthRequired(cfg.Secrets.JWTSecret))
|
||||
api.GET("/playback/:id/external-url", externalURLHandler(svc))
|
||||
api.GET("/playback/:id/external-players", externalPlayersHandler(svc))
|
||||
api.GET("/stream/:id", streamHandler(svc))
|
||||
api.GET("/cloud/play/:type", cloudPlayHandler(svc))
|
||||
return router, svc, cfg.Secrets.JWTSecret
|
||||
|
||||
Reference in New Issue
Block a user