2023-01-25 05:47:06 +00:00
|
|
|
package nostr
|
|
|
|
|
|
|
|
import (
|
2024-08-14 03:09:59 +00:00
|
|
|
"context"
|
2023-02-03 04:56:15 +00:00
|
|
|
"encoding/json"
|
2024-08-14 03:09:59 +00:00
|
|
|
"fmt"
|
2024-08-10 23:55:16 +00:00
|
|
|
"net"
|
2023-01-25 05:47:06 +00:00
|
|
|
"net/http"
|
|
|
|
"strings"
|
|
|
|
|
2024-08-10 23:55:16 +00:00
|
|
|
"git.minhas.io/asara/gologger"
|
|
|
|
"git.minhas.io/asara/well-goknown/config"
|
2024-08-14 03:09:59 +00:00
|
|
|
"github.com/fiatjaf/eventstore/postgresql"
|
|
|
|
"github.com/fiatjaf/khatru"
|
|
|
|
"github.com/fiatjaf/khatru/policies"
|
2024-08-10 23:55:16 +00:00
|
|
|
"github.com/jmoiron/sqlx"
|
2024-08-14 03:09:59 +00:00
|
|
|
"github.com/nbd-wtf/go-nostr"
|
2023-01-25 05:47:06 +00:00
|
|
|
)
|
|
|
|
|
2024-08-10 23:55:16 +00:00
|
|
|
var (
|
2024-08-14 03:09:59 +00:00
|
|
|
DB *sqlx.DB
|
|
|
|
RelayDb postgresql.PostgresBackend
|
|
|
|
relay *khatru.Relay
|
2024-08-10 23:55:16 +00:00
|
|
|
)
|
2023-01-25 05:47:06 +00:00
|
|
|
|
2024-08-10 23:55:16 +00:00
|
|
|
type nostrUser struct {
|
|
|
|
Pubkey string `db:"pubkey"`
|
|
|
|
Relays *string `db:"relays,omitempty"`
|
2023-05-05 05:15:09 +00:00
|
|
|
}
|
|
|
|
|
2024-08-10 23:55:16 +00:00
|
|
|
type nostrWellKnown struct {
|
|
|
|
Names map[string]string `json:"names"`
|
|
|
|
Relays map[string][]string `json:"relays,omitempty"`
|
2023-02-04 22:40:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func GetNostrAddr(w http.ResponseWriter, r *http.Request) {
|
2024-08-10 23:55:16 +00:00
|
|
|
l := gologger.Get(config.GetConfig().LogLevel).With().Str("context", "nostr").Logger()
|
|
|
|
|
2023-02-04 22:40:45 +00:00
|
|
|
// get query string for username
|
|
|
|
r.ParseForm()
|
2024-08-10 23:55:16 +00:00
|
|
|
name := strings.ToLower(r.FormValue("name"))
|
2023-02-04 22:40:45 +00:00
|
|
|
|
2024-08-10 23:55:16 +00:00
|
|
|
// normalize domain
|
|
|
|
domain, _, err := net.SplitHostPort(r.Host)
|
2023-02-04 22:40:45 +00:00
|
|
|
if err != nil {
|
2024-08-10 23:55:16 +00:00
|
|
|
domain = r.Host
|
2023-02-04 22:40:45 +00:00
|
|
|
}
|
2024-08-10 23:55:16 +00:00
|
|
|
|
|
|
|
// get owner id for nip05 request
|
|
|
|
var uid int
|
2024-08-11 04:16:31 +00:00
|
|
|
err = DB.QueryRow("SELECT owner_id FROM nip05s WHERE name=$1 AND domain=$2", name, domain).Scan(&uid)
|
|
|
|
if err != nil {
|
|
|
|
l.Error().Msgf("user (%s@%s) doesn't exist: %s", name, domain, err.Error())
|
|
|
|
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
|
|
|
return
|
|
|
|
}
|
2024-08-10 23:55:16 +00:00
|
|
|
|
|
|
|
// get the pubkey and relays associated with the nip05
|
|
|
|
user := nostrUser{}
|
|
|
|
err = DB.QueryRow("SELECT pubkey, relays FROM users WHERE id=$1", uid).Scan(&user.Pubkey, &user.Relays)
|
2023-04-29 19:25:28 +00:00
|
|
|
if err != nil {
|
2024-08-11 04:16:31 +00:00
|
|
|
l.Error().Msgf("unable to get user info for uid %v: %s", uid, err.Error())
|
2024-08-11 04:03:19 +00:00
|
|
|
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
2023-04-29 19:25:28 +00:00
|
|
|
return
|
|
|
|
}
|
2023-02-04 22:40:45 +00:00
|
|
|
|
2024-08-10 23:55:16 +00:00
|
|
|
// get all names associated with the pubkey
|
|
|
|
names := []string{}
|
|
|
|
err = DB.Select(&names, "SELECT nip05s.name FROM nip05s JOIN users ON nip05s.owner_id = users.id WHERE nip05s.owner_id = $1", uid)
|
2023-02-04 22:46:13 +00:00
|
|
|
if err != nil {
|
2024-08-11 04:16:31 +00:00
|
|
|
l.Error().Msgf("unable to get nip05 names for uid %v: %s", uid, err.Error())
|
2024-08-11 04:03:19 +00:00
|
|
|
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
2024-08-10 23:55:16 +00:00
|
|
|
return
|
2023-02-04 22:46:13 +00:00
|
|
|
}
|
2023-02-04 05:32:25 +00:00
|
|
|
|
2024-08-10 23:55:16 +00:00
|
|
|
// map of names
|
|
|
|
n := make(map[string]string)
|
|
|
|
for _, name := range names {
|
|
|
|
n[name] = user.Pubkey
|
2023-02-03 04:56:15 +00:00
|
|
|
}
|
2023-01-25 05:47:06 +00:00
|
|
|
|
2024-08-10 23:55:16 +00:00
|
|
|
// map of relays
|
|
|
|
s := make(map[string][]string)
|
|
|
|
if user.Relays != nil {
|
|
|
|
if strings.Contains(*user.Relays, ",") {
|
|
|
|
s[user.Pubkey] = strings.Split(*user.Relays, ",")
|
|
|
|
} else {
|
|
|
|
s[user.Pubkey] = []string{*user.Relays}
|
|
|
|
}
|
2023-01-25 05:47:06 +00:00
|
|
|
}
|
2023-02-03 04:56:15 +00:00
|
|
|
|
2024-08-10 23:55:16 +00:00
|
|
|
ret := nostrWellKnown{
|
|
|
|
Names: n,
|
|
|
|
Relays: s,
|
2023-02-03 04:56:15 +00:00
|
|
|
}
|
2024-08-10 23:55:16 +00:00
|
|
|
|
|
|
|
j, err := json.Marshal(ret)
|
2023-01-25 05:47:06 +00:00
|
|
|
if err != nil {
|
2024-08-10 23:55:16 +00:00
|
|
|
l.Error().Msg(err.Error())
|
2024-08-11 04:03:19 +00:00
|
|
|
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
2024-08-10 23:55:16 +00:00
|
|
|
return
|
2023-01-25 05:47:06 +00:00
|
|
|
}
|
|
|
|
|
2024-08-11 04:16:31 +00:00
|
|
|
l.Debug().Msgf("returning nip05 for %s@%s", name, domain)
|
2024-08-10 23:55:16 +00:00
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Write(j)
|
|
|
|
return
|
2023-02-04 23:52:50 +00:00
|
|
|
}
|
2024-08-14 03:09:59 +00:00
|
|
|
|
|
|
|
func NewRelay() *khatru.Relay {
|
|
|
|
// relay configuration
|
|
|
|
relay = khatru.NewRelay()
|
|
|
|
relay.Info.Name = "Devvul Nostr Relay"
|
|
|
|
relay.Info.PubKey = "ac7d66c7065e211b27759ffe2b333fde34e307cead79ce7c00f401d8fa6c08d1"
|
|
|
|
relay.Info.Description = "nostr relay for devvul users"
|
|
|
|
relay.Info.Icon = "https://pfp.nostr.build/ee442b3c54143099fb483b5e3a6e1e41144df5c7457313a30586a082baa1babc.png"
|
|
|
|
relay.Info.Version = "0.0.1"
|
|
|
|
relay.Info.Contact = "asara@devvul.com"
|
|
|
|
relay.Info.Software = "https://git.devvul.com/asara/well-goknown"
|
|
|
|
|
|
|
|
// storage
|
|
|
|
relay.StoreEvent = append(relay.StoreEvent, RelayDb.SaveEvent)
|
|
|
|
relay.QueryEvents = append(relay.QueryEvents, RelayDb.QueryEvents)
|
|
|
|
relay.CountEvents = append(relay.CountEvents, RelayDb.CountEvents)
|
|
|
|
relay.DeleteEvent = append(relay.DeleteEvent, RelayDb.DeleteEvent)
|
|
|
|
|
|
|
|
// apply policies
|
|
|
|
policies.ApplySaneDefaults(relay)
|
|
|
|
|
|
|
|
relay.RejectEvent = append(
|
|
|
|
relay.RejectEvent,
|
|
|
|
RejectUnregisteredNpubs,
|
|
|
|
policies.PreventLargeTags(80),
|
|
|
|
policies.RejectEventsWithBase64Media,
|
|
|
|
policies.ValidateKind,
|
|
|
|
)
|
|
|
|
|
|
|
|
relay.RejectFilter = append(
|
|
|
|
relay.RejectFilter,
|
|
|
|
policies.RejectKind04Snoopers,
|
|
|
|
policies.AntiSyncBots,
|
|
|
|
policies.NoComplexFilters,
|
|
|
|
policies.NoEmptyFilters,
|
|
|
|
)
|
|
|
|
return relay
|
|
|
|
}
|
|
|
|
|
|
|
|
func RejectUnregisteredNpubs(ctx context.Context, event *nostr.Event) (reject bool, msg string) {
|
|
|
|
l := gologger.Get(config.GetConfig().LogLevel).With().Str("context", "nostr-reject-unregistered").Logger()
|
|
|
|
|
|
|
|
// reject nip-04 messages to users who aren't registered
|
|
|
|
if event.Kind == 4 {
|
|
|
|
receiver := event.Tags.GetFirst([]string{"p"}).Value()
|
|
|
|
var uid int
|
|
|
|
err := DB.QueryRow("SELECT id FROM users WHERE pubkey=$1", receiver).Scan(&uid)
|
|
|
|
if err != nil {
|
|
|
|
l.Debug().Msgf("recipient pubkey %s not found: %s", receiver, err.Error())
|
|
|
|
return true, fmt.Sprintf("recipient pubkey %s is not registered to any users", receiver)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if user is registered
|
|
|
|
var uid int
|
|
|
|
err := DB.QueryRow("SELECT id FROM users WHERE pubkey=$1", event.PubKey).Scan(&uid)
|
|
|
|
if err != nil {
|
|
|
|
l.Debug().Msgf("poster pubkey %s not found: %s", event.PubKey, err.Error())
|
|
|
|
return true, fmt.Sprintf("pubkey %s is not registered to any users", event.PubKey)
|
|
|
|
}
|
|
|
|
return false, ""
|
|
|
|
}
|