nip98auth.go raw
1 package httpauth
2
3 import (
4 "encoding/base64"
5 "net/http"
6 "net/url"
7 "strings"
8
9 "next.orly.dev/pkg/nostr/encoders/event"
10 "next.orly.dev/pkg/nostr/encoders/kind"
11 "next.orly.dev/pkg/nostr/encoders/tag"
12 "next.orly.dev/pkg/nostr/encoders/timestamp"
13 "next.orly.dev/pkg/nostr/interfaces/signer"
14 "next.orly.dev/pkg/lol/chk"
15 )
16
17 const (
18 HeaderKey = "Authorization"
19 NIP98Prefix = "Nostr"
20 )
21
22 // MakeNIP98Event creates a new NIP-98 event. If expiry is given, method is
23 // ignored; otherwise either option is the same.
24 func MakeNIP98Event(u, method, hash string, expiry int64) (ev *event.E) {
25 var t []*tag.T
26 t = append(t, tag.NewFromAny("u", u))
27 if expiry > 0 {
28 t = append(
29 t,
30 tag.NewFromAny("expiration", timestamp.FromUnix(expiry).String()),
31 )
32 } else {
33 t = append(
34 t,
35 tag.NewFromAny("method", strings.ToUpper(method)),
36 )
37 }
38 if hash != "" {
39 t = append(t, tag.NewFromAny("payload", hash))
40 }
41 ev = &event.E{
42 CreatedAt: timestamp.Now().V,
43 Kind: kind.HTTPAuth.K,
44 Tags: tag.NewS(t...),
45 }
46 return
47 }
48
49 func CreateNIP98Blob(
50 ur, method, hash string, expiry int64, sign signer.I,
51 ) (blob string, err error) {
52 ev := MakeNIP98Event(ur, method, hash, expiry)
53 if err = ev.Sign(sign); chk.E(err) {
54 return
55 }
56 // log.T.F("nip-98 http auth event:\n%s\n", ev.SerializeIndented())
57 blob = base64.URLEncoding.EncodeToString(ev.Serialize())
58 return
59 }
60
61 // AddNIP98Header creates a NIP-98 http auth event and adds the standard header to a provided
62 // http.Request.
63 func AddNIP98Header(
64 r *http.Request, ur *url.URL, method, hash string,
65 sign signer.I, expiry int64,
66 ) (err error) {
67 var b64 string
68 if b64, err = CreateNIP98Blob(
69 ur.String(), method, hash, expiry, sign,
70 ); chk.E(err) {
71 return
72 }
73 r.Header.Add(HeaderKey, "Nostr "+b64)
74 return
75 }
76