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_payload.go
git_payload.go 1.8K
1package daemon23import (4 "crypto/sha256"5 "encoding/hex"6 "errors"7 "fmt"8 "io"9 "net/http"10 "os"11 "sync"1213 "github.com/FelineStateMachine/tinyrelay/internal/event"14)1516// A NIP-98 payload hash must match before Git can mutate the repository.17// Spooling keeps that ordering without allocating memory proportional to a18// pack's size. GitRelay closes the replacement body on every request exit.19func spoolGitPayload(r *http.Request, proof event.Event) error {20 if r.Body == nil || r.Body == http.NoBody {21 return validateGitPayloadHash(proof, emptyPayloadHash(), 0)22 }23 file, err := os.CreateTemp("", "tiny-git-authorized-*")24 if err != nil {25 return fmt.Errorf("spool Git authorization: %w", err)26 }27 keep := false28 defer func() {29 if !keep {30 _ = file.Close()31 _ = os.Remove(file.Name())32 }33 }()34 hash := sha256.New()35 n, err := io.Copy(io.MultiWriter(file, hash), r.Body)36 if err != nil {37 return fmt.Errorf("read Git authorization payload: %w", err)38 }39 if err := validateGitPayloadHash(proof, hex.EncodeToString(hash.Sum(nil)), n); err != nil {40 return err41 }42 if n == 0 {43 return nil44 }45 if _, err := file.Seek(0, io.SeekStart); err != nil {46 return fmt.Errorf("rewind Git authorization payload: %w", err)47 }48 r.Body = &gitPayloadBody{File: file, original: r.Body}49 keep = true50 return nil51}5253func validateGitPayloadHash(proof event.Event, actual string, size int64) error {54 payload := event.Tag(proof, "payload")55 if (size > 0 || payload != "") && payload != actual {56 return errors.New("auth-required: token payload hash does not match the body")57 }58 return nil59}6061type gitPayloadBody struct {62 *os.File63 original io.ReadCloser64 once sync.Once65 err error66}6768func (b *gitPayloadBody) Close() error {69 b.once.Do(func() {70 b.err = errors.Join(b.File.Close(), os.Remove(b.Name()), b.original.Close())71 })72 return b.err73}