diff --git a/nip86.go b/nip86.go index 8f137ed..de8da78 100644 --- a/nip86.go +++ b/nip86.go @@ -1,41 +1,78 @@ package khatru import ( + "context" + "crypto/sha256" + "encoding/base64" + "encoding/hex" "encoding/json" "fmt" + "io" "net" "net/http" "reflect" "strings" + "github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr/nip86" ) type RelayManagementAPI struct { - BanPubKey func(pubkey string, reason string) error - ListBannedPubKeys func() ([]nip86.PubKeyReason, error) - AllowPubKey func(pubkey string, reason string) error - ListAllowedPubKeys func() ([]nip86.PubKeyReason, error) - ListEventsNeedingModeration func() ([]nip86.IDReason, error) - AllowEvent func(id string, reason string) error - BanEvent func(id string, reason string) error - ListBannedEvents func() ([]nip86.IDReason, error) - ChangeRelayName func(name string) error - ChangeRelayDescription func(desc string) error - ChangeRelayIcon func(icon string) error - AllowKind func(kind int) error - DisallowKind func(kind int) error - ListAllowedKinds func() ([]int, error) - BlockIP func(ip net.IP, reason string) error - UnblockIP func(ip net.IP, reason string) error - ListBlockedIPs func() ([]nip86.IPReason, error) + RejectAPICall []func(ctx context.Context, mp nip86.MethodParams) (reject bool, msg string) + + BanPubKey func(ctx context.Context, pubkey string, reason string) error + ListBannedPubKeys func(ctx context.Context) ([]nip86.PubKeyReason, error) + AllowPubKey func(ctx context.Context, pubkey string, reason string) error + ListAllowedPubKeys func(ctx context.Context) ([]nip86.PubKeyReason, error) + ListEventsNeedingModeration func(ctx context.Context) ([]nip86.IDReason, error) + AllowEvent func(ctx context.Context, id string, reason string) error + BanEvent func(ctx context.Context, id string, reason string) error + ListBannedEvents func(ctx context.Context) ([]nip86.IDReason, error) + ChangeRelayName func(ctx context.Context, name string) error + ChangeRelayDescription func(ctx context.Context, desc string) error + ChangeRelayIcon func(ctx context.Context, icon string) error + AllowKind func(ctx context.Context, kind int) error + DisallowKind func(ctx context.Context, kind int) error + ListAllowedKinds func(ctx context.Context) ([]int, error) + BlockIP func(ctx context.Context, ip net.IP, reason string) error + UnblockIP func(ctx context.Context, ip net.IP, reason string) error + ListBlockedIPs func(ctx context.Context) ([]nip86.IPReason, error) } func (rl *Relay) HandleNIP86(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/nostr+json+rpc") + payload, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, "empty request", 400) + return + } + payloadHash := sha256.Sum256(nil) + + auth := r.Header.Get("Authorization") + spl := strings.Split(auth, "Nostr ") + if len(spl) != 2 { + http.Error(w, "missing auth", 403) + return + } + + var evt nostr.Event + if evtj, err := base64.StdEncoding.DecodeString(spl[1]); err != nil { + http.Error(w, "invalid base64 auth", 403) + return + } else if err := json.Unmarshal(evtj, &evt); err != nil { + http.Error(w, "invalid auth event json", 403) + return + } else if ok, _ := evt.CheckSignature(); !ok { + http.Error(w, "invalid auth event", 403) + return + } else if pht := evt.Tags.GetFirst([]string{"payload", hex.EncodeToString(payloadHash[:])}); pht == nil { + http.Error(w, "invalid auth event payload hash", 403) + return + } + var req nip86.Request - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + if err := json.Unmarshal(payload, &req); err != nil { http.Error(w, "invalid json body", 400) return } @@ -47,6 +84,15 @@ func (rl *Relay) HandleNIP86(w http.ResponseWriter, r *http.Request) { } var resp nip86.Response + + ctx := context.WithValue(r.Context(), nip86HeaderAuthKey, evt.PubKey) + for _, rac := range rl.ManagementAPI.RejectAPICall { + if reject, msg := rac(ctx, mp); reject { + resp.Error = msg + goto respond + } + } + if _, ok := mp.(nip86.SupportedMethods); ok { mat := reflect.TypeOf(rl.ManagementAPI) mav := reflect.ValueOf(rl.ManagementAPI) @@ -69,7 +115,7 @@ func (rl *Relay) HandleNIP86(w http.ResponseWriter, r *http.Request) { case nip86.BanPubKey: if rl.ManagementAPI.BanPubKey == nil { resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName()) - } else if err := rl.ManagementAPI.BanPubKey(thing.PubKey, thing.Reason); err != nil { + } else if err := rl.ManagementAPI.BanPubKey(ctx, thing.PubKey, thing.Reason); err != nil { resp.Error = err.Error() } else { resp.Result = true @@ -77,7 +123,7 @@ func (rl *Relay) HandleNIP86(w http.ResponseWriter, r *http.Request) { case nip86.ListBannedPubKeys: if rl.ManagementAPI.ListBannedPubKeys == nil { resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName()) - } else if result, err := rl.ManagementAPI.ListBannedPubKeys(); err != nil { + } else if result, err := rl.ManagementAPI.ListBannedPubKeys(ctx); err != nil { resp.Error = err.Error() } else { resp.Result = result @@ -85,7 +131,7 @@ func (rl *Relay) HandleNIP86(w http.ResponseWriter, r *http.Request) { case nip86.AllowPubKey: if rl.ManagementAPI.AllowPubKey == nil { resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName()) - } else if err := rl.ManagementAPI.AllowPubKey(thing.PubKey, thing.Reason); err != nil { + } else if err := rl.ManagementAPI.AllowPubKey(ctx, thing.PubKey, thing.Reason); err != nil { resp.Error = err.Error() } else { resp.Result = true @@ -93,7 +139,7 @@ func (rl *Relay) HandleNIP86(w http.ResponseWriter, r *http.Request) { case nip86.ListAllowedPubKeys: if rl.ManagementAPI.ListAllowedPubKeys == nil { resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName()) - } else if result, err := rl.ManagementAPI.ListAllowedPubKeys(); err != nil { + } else if result, err := rl.ManagementAPI.ListAllowedPubKeys(ctx); err != nil { resp.Error = err.Error() } else { resp.Result = result @@ -101,7 +147,7 @@ func (rl *Relay) HandleNIP86(w http.ResponseWriter, r *http.Request) { case nip86.BanEvent: if rl.ManagementAPI.BanEvent == nil { resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName()) - } else if err := rl.ManagementAPI.BanEvent(thing.ID, thing.Reason); err != nil { + } else if err := rl.ManagementAPI.BanEvent(ctx, thing.ID, thing.Reason); err != nil { resp.Error = err.Error() } else { resp.Result = true @@ -109,7 +155,7 @@ func (rl *Relay) HandleNIP86(w http.ResponseWriter, r *http.Request) { case nip86.AllowEvent: if rl.ManagementAPI.AllowEvent == nil { resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName()) - } else if err := rl.ManagementAPI.AllowEvent(thing.ID, thing.Reason); err != nil { + } else if err := rl.ManagementAPI.AllowEvent(ctx, thing.ID, thing.Reason); err != nil { resp.Error = err.Error() } else { resp.Result = true @@ -117,7 +163,7 @@ func (rl *Relay) HandleNIP86(w http.ResponseWriter, r *http.Request) { case nip86.ListEventsNeedingModeration: if rl.ManagementAPI.ListEventsNeedingModeration == nil { resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName()) - } else if result, err := rl.ManagementAPI.ListEventsNeedingModeration(); err != nil { + } else if result, err := rl.ManagementAPI.ListEventsNeedingModeration(ctx); err != nil { resp.Error = err.Error() } else { resp.Result = result @@ -125,7 +171,7 @@ func (rl *Relay) HandleNIP86(w http.ResponseWriter, r *http.Request) { case nip86.ListBannedEvents: if rl.ManagementAPI.ListBannedEvents == nil { resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName()) - } else if result, err := rl.ManagementAPI.ListEventsNeedingModeration(); err != nil { + } else if result, err := rl.ManagementAPI.ListEventsNeedingModeration(ctx); err != nil { resp.Error = err.Error() } else { resp.Result = result @@ -133,7 +179,7 @@ func (rl *Relay) HandleNIP86(w http.ResponseWriter, r *http.Request) { case nip86.ChangeRelayName: if rl.ManagementAPI.ChangeRelayName == nil { resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName()) - } else if err := rl.ManagementAPI.ChangeRelayName(thing.Name); err != nil { + } else if err := rl.ManagementAPI.ChangeRelayName(ctx, thing.Name); err != nil { resp.Error = err.Error() } else { resp.Result = true @@ -141,7 +187,7 @@ func (rl *Relay) HandleNIP86(w http.ResponseWriter, r *http.Request) { case nip86.ChangeRelayDescription: if rl.ManagementAPI.ChangeRelayDescription == nil { resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName()) - } else if err := rl.ManagementAPI.ChangeRelayDescription(thing.Description); err != nil { + } else if err := rl.ManagementAPI.ChangeRelayDescription(ctx, thing.Description); err != nil { resp.Error = err.Error() } else { resp.Result = true @@ -149,7 +195,7 @@ func (rl *Relay) HandleNIP86(w http.ResponseWriter, r *http.Request) { case nip86.ChangeRelayIcon: if rl.ManagementAPI.ChangeRelayIcon == nil { resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName()) - } else if err := rl.ManagementAPI.ChangeRelayIcon(thing.IconURL); err != nil { + } else if err := rl.ManagementAPI.ChangeRelayIcon(ctx, thing.IconURL); err != nil { resp.Error = err.Error() } else { resp.Result = true @@ -157,7 +203,7 @@ func (rl *Relay) HandleNIP86(w http.ResponseWriter, r *http.Request) { case nip86.AllowKind: if rl.ManagementAPI.AllowKind == nil { resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName()) - } else if err := rl.ManagementAPI.AllowKind(thing.Kind); err != nil { + } else if err := rl.ManagementAPI.AllowKind(ctx, thing.Kind); err != nil { resp.Error = err.Error() } else { resp.Result = true @@ -165,7 +211,7 @@ func (rl *Relay) HandleNIP86(w http.ResponseWriter, r *http.Request) { case nip86.DisallowKind: if rl.ManagementAPI.DisallowKind == nil { resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName()) - } else if err := rl.ManagementAPI.DisallowKind(thing.Kind); err != nil { + } else if err := rl.ManagementAPI.DisallowKind(ctx, thing.Kind); err != nil { resp.Error = err.Error() } else { resp.Result = true @@ -173,7 +219,7 @@ func (rl *Relay) HandleNIP86(w http.ResponseWriter, r *http.Request) { case nip86.ListAllowedKinds: if rl.ManagementAPI.ListAllowedKinds == nil { resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName()) - } else if result, err := rl.ManagementAPI.ListAllowedKinds(); err != nil { + } else if result, err := rl.ManagementAPI.ListAllowedKinds(ctx); err != nil { resp.Error = err.Error() } else { resp.Result = result @@ -181,7 +227,7 @@ func (rl *Relay) HandleNIP86(w http.ResponseWriter, r *http.Request) { case nip86.BlockIP: if rl.ManagementAPI.BlockIP == nil { resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName()) - } else if err := rl.ManagementAPI.BlockIP(thing.IP, thing.Reason); err != nil { + } else if err := rl.ManagementAPI.BlockIP(ctx, thing.IP, thing.Reason); err != nil { resp.Error = err.Error() } else { resp.Result = true @@ -189,7 +235,7 @@ func (rl *Relay) HandleNIP86(w http.ResponseWriter, r *http.Request) { case nip86.UnblockIP: if rl.ManagementAPI.UnblockIP == nil { resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName()) - } else if err := rl.ManagementAPI.UnblockIP(thing.IP, thing.Reason); err != nil { + } else if err := rl.ManagementAPI.UnblockIP(ctx, thing.IP, thing.Reason); err != nil { resp.Error = err.Error() } else { resp.Result = true @@ -197,7 +243,7 @@ func (rl *Relay) HandleNIP86(w http.ResponseWriter, r *http.Request) { case nip86.ListBlockedIPs: if rl.ManagementAPI.ListBlockedIPs == nil { resp.Error = fmt.Sprintf("method %s not supported", thing.MethodName()) - } else if result, err := rl.ManagementAPI.ListBlockedIPs(); err != nil { + } else if result, err := rl.ManagementAPI.ListBlockedIPs(ctx); err != nil { resp.Error = err.Error() } else { resp.Result = result @@ -207,5 +253,6 @@ func (rl *Relay) HandleNIP86(w http.ResponseWriter, r *http.Request) { } } +respond: json.NewEncoder(w).Encode(resp) } diff --git a/utils.go b/utils.go index 880111d..8c6265b 100644 --- a/utils.go +++ b/utils.go @@ -9,6 +9,7 @@ import ( const ( wsKey = iota subscriptionIdKey + nip86HeaderAuthKey ) func RequestAuth(ctx context.Context) { @@ -30,10 +31,12 @@ func GetConnection(ctx context.Context) *WebSocket { } func GetAuthed(ctx context.Context) string { - conn := GetConnection(ctx) - if conn != nil { + if conn := GetConnection(ctx); conn != nil { return conn.AuthedPublicKey } + if nip86Auth := ctx.Value(nip86HeaderAuthKey); nip86Auth != nil { + return nip86Auth.(string) + } return "" }