tinyrelayfile
tinyrelay/internal/daemon/git_payload.go/

internal/daemon/git_payload.go raw
1package daemon
2
3import (
4 "crypto/sha256"
5 "encoding/hex"
6 "errors"
7 "fmt"
8 "io"
9 "net/http"
10 "os"
11 "sync"
12
13 "github.com/FelineStateMachine/tinyrelay/internal/event"
14)
15
16// A NIP-98 payload hash must match before Git can mutate the repository.
17// Spooling keeps that ordering without allocating memory proportional to a
18// 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 nil
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 := false
28 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 n == 0 {
40 return nil
41 }
42 if event.Tag(proof, "payload") != hex.EncodeToString(hash.Sum(nil)) {
43 return errors.New("auth-required: token payload hash does not match the body")
44 }
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 = true
50 return nil
51}
52
53type gitPayloadBody struct {
54 *os.File
55 original io.ReadCloser
56 once sync.Once
57 err error
58}
59
60func (b *gitPayloadBody) Close() error {
61 b.once.Do(func() {
62 b.err = errors.Join(b.File.Close(), os.Remove(b.Name()), b.original.Close())
63 })
64 return b.err
65}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs