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