clone

a self-hosted multitenant nostr relay with sites, file storage, git hosting, and inbox and outbox delivery

feat/ui-updates
tinyrelay / internal / daemon / hosting_routes_acceptance_test.go
hosting_routes_acceptance_test.go 4.2K
1package daemon23import (4 "context"5 "crypto/sha256"6 "encoding/hex"7 "encoding/json"8 "net/http"9 "net/http/httptest"10 "strings"11 "testing"1213 "github.com/FelineStateMachine/tinyrelay/internal/blob"14 "github.com/FelineStateMachine/tinyrelay/internal/event"15 "github.com/FelineStateMachine/tinyrelay/internal/relay"16 "github.com/FelineStateMachine/tinyrelay/internal/sites"17)1819func TestMountedSiteHostIsolatedPerTenant(t *testing.T) {20 ctx := context.Background()21 ownerSecret := strings.Repeat("0", 63) + "1"22 owner, err := event.PublicKey(ownerSecret)23 if err != nil {24 t.Fatal(err)25 }26 app, err := New(ctx, Config{DataDir: t.TempDir(), DefaultTenant: "alice", PublicURL: "https://relay.test"})27 if err != nil {28 t.Fatal(err)29 }30 defer app.Close(ctx)31 var tenants []*Tenant32 for _, name := range []string{"alice", "bob"} {33 meta, err := app.Create(ctx, CreateOptions{Name: name, Owner: owner, Template: "default"})34 if err != nil {35 t.Fatal(err)36 }37 tenant, err := app.tenant(ctx, meta, "https://relay.test")38 if err != nil {39 t.Fatal(err)40 }41 tenants = append(tenants, tenant)42 }43 content := []string{"alice site", "bob site"}44 label := ""45 for i, tenant := range tenants {46 hashBytes := sha256.Sum256([]byte(content[i]))47 hash := hex.EncodeToString(hashBytes[:])48 if _, err := tenant.blobs.Put(ctx, blobPut(content[i], owner)); err != nil {49 t.Fatal(err)50 }51 e := event.Event{Kind: sites.KindSite, CreatedAt: 1, Tags: [][]string{{"path", "/index.html", hash}}}52 if err := event.Sign(&e, ownerSecret); err != nil {53 t.Fatal(err)54 }55 if _, err := tenant.Publish(ctx, e, relay.Session{PubKeys: []string{owner}}); err != nil {56 t.Fatal(err)57 }58 if label == "" {59 label = sites.SiteLabel(e)60 }61 }62 for i, name := range []string{"alice", "bob"} {63 req := httptest.NewRequest(http.MethodGet, "https://"+label+"."+name+".relay.test/", nil)64 res := httptest.NewRecorder()65 app.ServeHTTP(res, req)66 if res.Code != http.StatusOK || res.Body.String() != content[i] {67 t.Fatalf("tenant %s site status=%d body=%q", name, res.Code, res.Body.String())68 }69 }70 // NIP-AD discovery is mounted through the tenant router and must only71 // reveal an event held by that tenant.72 note := event.Event{Kind: 1, CreatedAt: 2, Content: "alice-only"}73 if err := event.Sign(&note, ownerSecret); err != nil {74 t.Fatal(err)75 }76 if _, err := tenants[0].Publish(ctx, note, relay.Session{PubKeys: []string{owner}}); err != nil {77 t.Fatal(err)78 }79 for i, name := range []string{"alice", "bob"} {80 requestURL := "https://relay.test/.well-known/nostr.json?path=/e/" + note.ID81 if name == "bob" {82 requestURL = "https://relay.test/r/bob/.well-known/nostr.json?path=/e/" + note.ID83 }84 req := httptest.NewRequest(http.MethodGet, requestURL, nil)85 signRequest(t, req, "")86 res := httptest.NewRecorder()87 app.ServeHTTP(res, req)88 if res.Code != http.StatusOK {89 t.Fatalf("NIP-AD %s status=%d body=%s", name, res.Code, res.Body.String())90 }91 var doc map[string]struct {92 Filter map[string]any `json:"filter"`93 }94 if err := json.Unmarshal(res.Body.Bytes(), &doc); err != nil {95 t.Fatal(err)96 }97 _, found := doc["/e/"+note.ID]98 if (i == 0) != found {99 t.Fatalf("NIP-AD %s found=%v document=%s", name, found, res.Body.String())100 }101 if i == 0 && len(doc["/e/"+note.ID].Filter) == 0 {102 t.Fatal("NIP-AD positive response omitted filter")103 }104 }105 meta, err := app.Catalog().GetByName(ctx, "alice")106 if err != nil {107 t.Fatal(err)108 }109 if _, err := app.Catalog().AddHost(ctx, meta.ID, "sites.example.test", label); err != nil {110 t.Fatal(err)111 }112 custom := httptest.NewRequest(http.MethodGet, "https://sites.example.test/", nil)113 customRes := httptest.NewRecorder()114 app.ServeHTTP(customRes, custom)115 if customRes.Code != http.StatusOK || customRes.Body.String() != content[0] {116 t.Fatalf("custom host status=%d body=%q", customRes.Code, customRes.Body.String())117 }118 unknown := httptest.NewRequest(http.MethodGet, "https://"+label+".unknown.relay.test/", nil)119 res := httptest.NewRecorder()120 app.ServeHTTP(res, unknown)121 if res.Code == http.StatusOK && strings.Contains(res.Body.String(), "alice site") {122 t.Fatal("unknown tenant served another tenant site")123 }124}125126func blobPut(content, uploader string) blob.PutOptions {127 return blob.PutOptions{Reader: strings.NewReader(content), Type: "text/html", Uploader: uploader}128}