clone

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

feat/standalone-relay
tinyrelay / internal / daemon / git_private_restart_test.go
git_private_restart_test.go 4.1K
1package daemon23import (4 "context"5 "net/http"6 "net/http/httptest"7 "strings"8 "testing"910 "github.com/FelineStateMachine/tinyrelay/internal/event"11 "github.com/FelineStateMachine/tinyrelay/internal/relay"12)1314func TestPrivateSignedGitStateRemainsHiddenAfterRestart(t *testing.T) {15 ctx := context.Background()16 dir := t.TempDir()17 secret := strings.Repeat("0", 63) + "1"18 owner, err := event.PublicKey(secret)19 if err != nil {20 t.Fatal(err)21 }22 app, err := New(ctx, Config{DataDir: dir, DefaultTenant: "main"})23 if err != nil {24 t.Fatal(err)25 }26 meta, err := app.Create(ctx, CreateOptions{Name: "main", Owner: owner, Template: "default"})27 if err != nil {28 t.Fatal(err)29 }30 tenant, err := app.tenant(ctx, meta, "http://relay.test")31 if err != nil {32 t.Fatal(err)33 }34 p := tenant.Policy()35 p.Features.Grasp = true36 p.Features.Grasp08 = true37 p.Reads = "members"38 if err := tenant.applyPolicy(ctx, p); err != nil {39 t.Fatal(err)40 }41 announcement := event.Event{Kind: event.KIND_REPO, CreatedAt: 1, Tags: [][]string{{"d", "private"}, {"private", "true"}, {"clone", "http://relay.test/" + "npub10xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqpkge6d/private.git"}, {"relays", "ws://relay.test"}}}42 if err := event.Sign(&announcement, secret); err != nil {43 t.Fatal(err)44 }45 if _, err := tenant.router.Publish(ctx, announcement, relay.Session{PubKeys: []string{owner}}); err != nil {46 t.Fatal(err)47 }48 state := event.Event{Kind: event.KIND_REPO_STATE, CreatedAt: 2, Tags: [][]string{{"d", "private"}, {"HEAD", "ref: refs/heads/main"}, {"refs/heads/main", strings.Repeat("a", 40)}}}49 if err := event.Sign(&state, secret); err != nil {50 t.Fatal(err)51 }52 if _, err := tenant.router.Publish(ctx, state, relay.Session{PubKeys: []string{owner}}); err != nil {53 t.Fatal(err)54 }55 if !tenant.git.IsPending(state.ID) {56 t.Fatal("missing-object state was not held pending")57 }58 if err := app.Close(ctx); err != nil {59 t.Fatal(err)60 }6162 app, err = New(ctx, Config{DataDir: dir, DefaultTenant: "main"})63 if err != nil {64 t.Fatal(err)65 }66 if err := app.Start(ctx, "http://relay.test"); err != nil {67 t.Fatal(err)68 }69 t.Cleanup(func() { _ = app.Close(ctx) })70 restarted := app.tenants[meta.ID]71 if restarted == nil || !restarted.git.IsPending(state.ID) {72 t.Fatal("restart did not restore pending signed state")73 }74 path := "/npub10xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqpkge6d/private.git/info/refs?service=git-upload-pack"75 req := httptest.NewRequest(http.MethodGet, "http://relay.test/r/main"+path, nil)76 denied := httptest.NewRecorder()77 app.ServeHTTP(denied, req)78 if denied.Code != http.StatusUnauthorized || denied.Body.Len() != 0 {79 t.Fatalf("unsigned private Git read status=%d body=%q", denied.Code, denied.Body.String())80 }81 req = httptest.NewRequest(http.MethodGet, "http://relay.test/r/main"+path, nil)82 signRequest(t, req, "")83 res := httptest.NewRecorder()84 app.ServeHTTP(res, req)85 if res.Code != http.StatusOK || !strings.Contains(res.Body.String(), "# service=git-upload-pack") {86 t.Fatalf("authorized private Git read after restart status = %d, body=%q", res.Code, res.Body.String())87 }88 pushPath := "/npub10xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqpkge6d/private.git/git-receive-pack"89 body := "0000"90 push := httptest.NewRequest(http.MethodPost, "http://relay.test/r/main"+pushPath, strings.NewReader(body))91 push.Header.Set("Content-Type", "application/x-git-receive-pack-request")92 signRequest(t, push, body)93 pushResult := httptest.NewRecorder()94 app.ServeHTTP(pushResult, push)95 if pushResult.Code != http.StatusOK || pushResult.Header().Get("Content-Type") != "application/x-git-receive-pack-result" {96 t.Fatalf("signed private Git negotiation failed: %d %q", pushResult.Code, pushResult.Body.String())97 }98 tampered := httptest.NewRequest(http.MethodPost, "http://relay.test/r/main"+pushPath, strings.NewReader("0002"))99 tampered.Header.Set("Content-Type", "application/x-git-receive-pack-request")100 signRequest(t, tampered, "0001")101 rejected := httptest.NewRecorder()102 app.ServeHTTP(rejected, tampered)103 if rejected.Code != http.StatusOK {104 t.Fatalf("GRASP-08 reusable proof rejected body variation: %d %q", rejected.Code, rejected.Body.String())105 }106}