well-goknown/nostr/nostr.go

239 lines
6.1 KiB
Go
Raw Normal View History

package nostr
import (
2023-02-05 02:07:26 +00:00
"context"
2023-04-29 19:25:28 +00:00
b64 "encoding/base64"
2023-02-03 04:56:15 +00:00
"encoding/json"
"errors"
"fmt"
2023-05-05 05:15:09 +00:00
"html/template"
"net/http"
"strings"
"git.minhas.io/asara/well-goknown/lnd"
2023-04-29 18:01:40 +00:00
"git.minhas.io/asara/well-goknown/logger"
2023-02-03 04:56:15 +00:00
"git.minhas.io/asara/well-goknown/redis"
"github.com/nbd-wtf/go-nostr/nip19"
"github.com/skip2/go-qrcode"
)
type nostrWellKnown struct {
Names map[string]string `json:"names"`
Relays map[string][]string `json:"relays,omitempty"`
}
2023-05-03 02:11:53 +00:00
type jsonErrorMessage struct {
Error string `json:"error"`
}
type NostrRequest struct {
Name string `json:"name"`
Key string `json:"key"`
Hostname string
2023-02-03 04:56:15 +00:00
Relays []string `json:"relays,omitempty"`
}
2023-05-05 05:15:09 +00:00
type NostrResponse struct {
RequestKey string `json:"request_key"`
QRCode string `json:"QRCode"`
}
func RequestNostrAddr(w http.ResponseWriter, r *http.Request) {
2023-02-05 02:07:26 +00:00
ctx := context.TODO()
2023-04-29 18:01:40 +00:00
l := logger.Get()
switch r.Method {
case "GET":
http.ServeFile(w, r, "html/nostr_form.html")
case "POST":
r.ParseForm()
2023-12-31 00:51:27 +00:00
name := strings.ToLower(r.FormValue("Name"))
2023-05-01 22:44:05 +00:00
redisCli := redis.NostrRedisConn.Client
2023-04-29 18:01:40 +00:00
// check if the user already exists
2023-12-31 00:51:27 +00:00
rKey := getRkey("verified", name, getHostname(r.Host))
2023-05-01 22:44:05 +00:00
exists := redisCli.Exists(ctx, rKey)
2023-02-04 23:52:50 +00:00
if exists.Val() == 1 {
w.WriteHeader(http.StatusConflict)
2023-05-03 02:11:53 +00:00
json.NewEncoder(w).Encode(&jsonErrorMessage{
Error: "username already registered",
})
2023-02-04 23:52:50 +00:00
return
}
2023-12-31 00:51:27 +00:00
rKey = getRkey("requested", name, getHostname(r.Host))
2023-05-01 22:44:05 +00:00
exists = redisCli.Exists(ctx, rKey)
2023-05-01 22:01:46 +00:00
if exists.Val() == 1 {
w.WriteHeader(http.StatusConflict)
2023-05-03 02:11:53 +00:00
json.NewEncoder(w).Encode(&jsonErrorMessage{
Error: "username already requested, try again in a few minutes",
})
2023-05-01 22:01:46 +00:00
return
}
2023-02-04 23:52:50 +00:00
// get the hexkey
hexKey, err := convertNpubToHex(r.FormValue("Key"))
if err != nil {
w.WriteHeader(http.StatusBadRequest)
2023-05-03 02:11:53 +00:00
json.NewEncoder(w).Encode(&jsonErrorMessage{
Error: err.Error(),
})
return
}
2023-02-04 23:52:50 +00:00
// create the struct
relays := make(map[string][]string)
2023-12-31 00:51:27 +00:00
names := map[string]string{name: hexKey}
user := nostrWellKnown{}
if r.FormValue("Relays") != "" {
relays = map[string][]string{
hexKey: strings.Split(r.FormValue("Relays"), ","),
}
user = nostrWellKnown{Names: names, Relays: relays}
} else {
user = nostrWellKnown{Names: names}
}
jsonUser, _ := json.Marshal(user)
2023-04-29 19:25:28 +00:00
enc := b64.StdEncoding.EncodeToString([]byte(jsonUser))
2023-02-04 23:52:50 +00:00
// generate the payment request
paymentReq, err := lnd.Request(rKey)
if err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
2023-05-03 02:11:53 +00:00
json.NewEncoder(w).Encode(&jsonErrorMessage{
Error: "service unavailable",
})
return
}
// generate qr png
png, err := qrcode.Encode(paymentReq, qrcode.Medium, 256)
2023-05-05 05:15:09 +00:00
pngB64 := b64.StdEncoding.EncodeToString(png)
// write request to redis
err = redisCli.Set(ctx, rKey, enc, 0).Err()
2023-02-05 01:43:38 +00:00
if err != nil {
2023-04-29 18:01:40 +00:00
l.Error().Msg("unable to connect to redis")
w.WriteHeader(http.StatusServiceUnavailable)
json.NewEncoder(w).Encode(&jsonErrorMessage{
Error: "service unavailable",
})
return
2023-02-05 01:43:38 +00:00
}
// return qr code
2023-05-05 05:15:09 +00:00
response := NostrResponse{
RequestKey: rKey,
QRCode: pngB64,
}
tmplt, err := template.ParseFiles("html/nostr_qr_response.html")
if err != nil {
l.Error().Msg("unable to parse qr response template")
w.WriteHeader(http.StatusServiceUnavailable)
json.NewEncoder(w).Encode(&jsonErrorMessage{
Error: "service unavailable",
})
return
2023-05-05 05:15:09 +00:00
}
err = tmplt.Execute(w, response)
if err != nil {
l.Error().Msg("unable to connect to render template")
w.WriteHeader(http.StatusServiceUnavailable)
json.NewEncoder(w).Encode(&jsonErrorMessage{
Error: "service unavailable",
})
return
2023-05-05 05:15:09 +00:00
}
return
}
}
func GetNostrAddr(w http.ResponseWriter, r *http.Request) {
2023-02-05 02:07:26 +00:00
ctx := context.TODO()
l := logger.Get()
// get query string for username
r.ParseForm()
2023-12-31 00:51:27 +00:00
requestedName := strings.ToLower(r.FormValue("name"))
// connect to redis
2023-05-01 22:44:05 +00:00
redisCli := redis.NostrRedisConn.Client
// search for user@domain
2023-02-05 01:43:38 +00:00
search := getRkey("verified", requestedName, getHostname(r.Host))
2023-05-01 22:44:05 +00:00
addr, err := redisCli.Get(ctx, search).Result()
if err != nil {
l.Info().Msg(fmt.Sprintf("get %s: not found", search))
w.WriteHeader(http.StatusNotFound)
2023-05-03 02:11:53 +00:00
json.NewEncoder(w).Encode(&jsonErrorMessage{
Error: "username not registered",
})
return
}
2023-04-29 19:25:28 +00:00
dec, err := b64.StdEncoding.DecodeString(addr)
if err != nil {
l.Error().Msg(fmt.Sprintf("get %s: unable to decode key", search))
2023-04-29 19:25:28 +00:00
w.WriteHeader(http.StatusInternalServerError)
2023-05-03 02:11:53 +00:00
json.NewEncoder(w).Encode(&jsonErrorMessage{
Error: "internal server error",
})
2023-04-29 19:25:28 +00:00
return
}
l.Info().Msg(fmt.Sprintf("get %s: found and returned", search))
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
2023-04-29 19:25:28 +00:00
w.Write(dec)
}
func AddNostrAddr(n NostrRequest) {
2023-02-05 02:07:26 +00:00
ctx := context.TODO()
2023-04-29 18:01:40 +00:00
l := logger.Get()
// transform request to well known struct
2023-02-03 04:56:15 +00:00
relays := make(map[string][]string)
user := nostrWellKnown{}
2023-02-04 22:46:13 +00:00
hexKey, err := convertNpubToHex(n.Key)
if err != nil {
2023-04-29 18:01:40 +00:00
l.Error().Msg("unable to convert npub to hex")
2023-02-04 22:46:13 +00:00
}
names := map[string]string{n.Name: hexKey}
2023-02-04 05:32:25 +00:00
2023-02-03 04:56:15 +00:00
if n.Relays != nil {
2023-02-04 22:46:13 +00:00
relays = map[string][]string{hexKey: n.Relays}
2023-02-03 04:56:15 +00:00
user = nostrWellKnown{Names: names, Relays: relays}
} else {
user = nostrWellKnown{Names: names}
}
jsonUser, _ := json.Marshal(user)
2023-05-01 22:44:05 +00:00
redisCli := redis.NostrRedisConn.Client
2023-02-05 01:43:38 +00:00
nameKey := getRkey("verified", n.Name, n.Hostname)
2023-04-29 19:25:28 +00:00
enc := b64.StdEncoding.EncodeToString([]byte(jsonUser))
2023-05-01 22:44:05 +00:00
err = redisCli.Set(ctx, nameKey, enc, 0).Err()
if err != nil {
2023-04-29 18:01:40 +00:00
l.Error().Msg("unable to connect to redis")
}
}
2023-02-03 04:56:15 +00:00
func getHostname(hostname string) string {
// remove port for local testing
return hostname
2023-02-03 04:56:15 +00:00
}
2023-02-04 22:46:13 +00:00
func convertNpubToHex(npub string) (string, error) {
if !strings.HasPrefix(npub, "npub1") {
2023-05-03 02:11:53 +00:00
return "", errors.New("key does not start with npub1 prefix")
2023-02-03 04:56:15 +00:00
}
_, hex, err := nip19.Decode(npub)
if err != nil {
2023-05-03 02:11:53 +00:00
return "", errors.New("unable to decode npub key")
}
return hex.(string), nil
}
2023-02-04 23:52:50 +00:00
2023-02-05 01:43:38 +00:00
func getRkey(prefix string, user string, hostname string) string {
2023-02-04 23:52:50 +00:00
if strings.Contains(hostname, ":") {
hostname = strings.Split(hostname, ":")[0]
}
2023-02-05 01:43:38 +00:00
return fmt.Sprintf("%s:%s@%s", prefix, user, hostname)
2023-02-04 23:52:50 +00:00
}