package nostr import ( "context" "encoding/json" "fmt" "net" "net/http" "strings" "git.minhas.io/asara/gologger" "git.minhas.io/asara/well-goknown/config" "github.com/fiatjaf/eventstore/postgresql" "github.com/fiatjaf/khatru" "github.com/fiatjaf/khatru/policies" "github.com/jmoiron/sqlx" "github.com/nbd-wtf/go-nostr" ) var ( DB *sqlx.DB RelayDb postgresql.PostgresBackend relay *khatru.Relay ) type nostrUser struct { Pubkey string `db:"pubkey"` Relays *string `db:"relays,omitempty"` } type nostrWellKnown struct { Names map[string]string `json:"names"` Relays map[string][]string `json:"relays,omitempty"` } func GetNostrAddr(w http.ResponseWriter, r *http.Request) { l := gologger.Get(config.GetConfig().LogLevel).With().Str("context", "nostr").Logger() // get query string for username r.ParseForm() name := strings.ToLower(r.FormValue("name")) // normalize domain domain, _, err := net.SplitHostPort(r.Host) if err != nil { domain = r.Host } // get owner id for nip05 request var uid int 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 } // 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) if err != nil { l.Error().Msgf("unable to get user info for uid %v: %s", uid, err.Error()) http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } // 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) if err != nil { l.Error().Msgf("unable to get nip05 names for uid %v: %s", uid, err.Error()) http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } // map of names n := make(map[string]string) for _, name := range names { n[name] = user.Pubkey } // 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} } } ret := nostrWellKnown{ Names: n, Relays: s, } j, err := json.Marshal(ret) if err != nil { l.Error().Msg(err.Error()) http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } l.Debug().Msgf("returning nip05 for %s@%s", name, domain) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write(j) return } 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, "" }