auth.go raw

   1  package blossom
   2  
   3  import (
   4  	"crypto/sha256"
   5  	"encoding/base64"
   6  	"errors"
   7  	"strconv"
   8  	"strings"
   9  
  10  	"git.smesh.lol/torly/pkg/nostr"
  11  )
  12  
  13  // sha256Bytes returns the blob hash.
  14  func sha256Bytes(b []byte) [32]byte { return sha256.Sum256(b) }
  15  
  16  // verifyAuth validates a BUD-01 Authorization header: "Nostr <base64 event>".
  17  // The event must be kind 24242, unexpired, signed by its claimed author, carry
  18  // the requested action in a t tag, and (when hash is non-empty) name the blob
  19  // in an x tag.
  20  func verifyAuth(header, action, hash string, now int64) ([32]byte, error) {
  21  	var zero [32]byte
  22  	if header == "" {
  23  		return zero, errors.New("missing Authorization header")
  24  	}
  25  	fields := strings.Fields(header)
  26  	if len(fields) != 2 || !strings.EqualFold(fields[0], "Nostr") {
  27  		return zero, errors.New("Authorization must be 'Nostr <event>'")
  28  	}
  29  	raw, err := decodeBase64(fields[1])
  30  	if err != nil {
  31  		return zero, errors.New("Authorization is not valid base64")
  32  	}
  33  	ev, err := nostr.ParseEvent(raw)
  34  	if err != nil {
  35  		return zero, errors.New("Authorization event is malformed")
  36  	}
  37  	if ev.Kind != 24242 {
  38  		return zero, errors.New("Authorization event must be kind 24242")
  39  	}
  40  	if ev.CreatedAt < now-600 || ev.CreatedAt > now+600 {
  41  		return zero, errors.New("Authorization event timestamp is out of range")
  42  	}
  43  	exp, ok := ev.Tags.First("expiration")
  44  	if !ok {
  45  		return zero, errors.New("Authorization event has no expiration tag")
  46  	}
  47  	expN, err := strconv.ParseInt(exp, 10, 64)
  48  	if err != nil || expN <= now {
  49  		return zero, errors.New("Authorization event has expired")
  50  	}
  51  	actionOK := false
  52  	for _, t := range ev.Tags.Values("t") {
  53  		if t == action {
  54  			actionOK = true
  55  			break
  56  		}
  57  	}
  58  	if !actionOK {
  59  		return zero, errors.New("Authorization event does not grant " + action)
  60  	}
  61  	if hash != "" {
  62  		x, ok := ev.Tags.First("x")
  63  		if !ok || !strings.EqualFold(x, hash) {
  64  			return zero, errors.New("Authorization event does not match the blob hash")
  65  		}
  66  	}
  67  	if err := ev.Verify(); err != nil {
  68  		return zero, err
  69  	}
  70  	return ev.PubKey, nil
  71  }
  72  
  73  func decodeBase64(s string) ([]byte, error) {
  74  	encodings := []*base64.Encoding{
  75  		base64.StdEncoding,
  76  		base64.RawStdEncoding,
  77  		base64.URLEncoding,
  78  		base64.RawURLEncoding,
  79  	}
  80  	var last error
  81  	for _, enc := range encodings {
  82  		b, err := enc.DecodeString(s)
  83  		if err == nil {
  84  			return b, nil
  85  		}
  86  		last = err
  87  	}
  88  	return nil, last
  89  }
  90