2023-01-25 05:47:06 +00:00
|
|
|
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"
|
2023-02-04 22:40:45 +00:00
|
|
|
"errors"
|
2023-01-25 05:47:06 +00:00
|
|
|
"fmt"
|
2023-05-05 05:15:09 +00:00
|
|
|
"html/template"
|
2023-01-25 05:47:06 +00:00
|
|
|
"net/http"
|
|
|
|
"strings"
|
2023-05-05 04:37:27 +00:00
|
|
|
"time"
|
2023-01-25 05:47:06 +00:00
|
|
|
|
2023-02-04 22:40:45 +00:00
|
|
|
"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"
|
2023-02-04 22:40:45 +00:00
|
|
|
"github.com/nbd-wtf/go-nostr/nip19"
|
2023-05-05 04:37:27 +00:00
|
|
|
"github.com/skip2/go-qrcode"
|
2023-01-25 05:47:06 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
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"`
|
|
|
|
}
|
|
|
|
|
2023-01-25 05:47:06 +00:00
|
|
|
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-01-25 05:47:06 +00:00
|
|
|
}
|
|
|
|
|
2023-05-05 05:15:09 +00:00
|
|
|
type NostrResponse struct {
|
|
|
|
RequestKey string `json:"request_key"`
|
|
|
|
QRCode string `json:"QRCode"`
|
|
|
|
}
|
|
|
|
|
2023-02-04 22:40:45 +00:00
|
|
|
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()
|
2023-02-04 22:40:45 +00:00
|
|
|
switch r.Method {
|
|
|
|
case "GET":
|
2023-02-05 00:11:50 +00:00
|
|
|
http.ServeFile(w, r, "html/nostr_form.html")
|
2023-02-04 22:40:45 +00:00
|
|
|
case "POST":
|
|
|
|
r.ParseForm()
|
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
|
|
|
|
rKey := getRkey("verified", r.FormValue("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-05-01 22:01:46 +00:00
|
|
|
rKey = getRkey("requested", r.FormValue("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"))
|
2023-02-04 22:40:45 +00:00
|
|
|
if err != nil {
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
2023-05-03 02:11:53 +00:00
|
|
|
json.NewEncoder(w).Encode(&jsonErrorMessage{
|
|
|
|
Error: err.Error(),
|
|
|
|
})
|
2023-02-04 22:40:45 +00:00
|
|
|
return
|
|
|
|
}
|
2023-02-04 23:52:50 +00:00
|
|
|
|
|
|
|
// create the struct
|
|
|
|
relays := make(map[string][]string)
|
2023-02-04 22:40:45 +00:00
|
|
|
names := map[string]string{r.FormValue("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
|
2023-05-05 04:37:27 +00:00
|
|
|
paymentReq, err := lnd.Request(rKey)
|
2023-02-04 22:40:45 +00:00
|
|
|
if err != nil {
|
|
|
|
w.WriteHeader(http.StatusServiceUnavailable)
|
2023-05-03 02:11:53 +00:00
|
|
|
json.NewEncoder(w).Encode(&jsonErrorMessage{
|
|
|
|
Error: "service unavailable",
|
|
|
|
})
|
2023-02-04 22:40:45 +00:00
|
|
|
return
|
|
|
|
}
|
2023-05-05 04:37:27 +00:00
|
|
|
|
|
|
|
// generate qr png
|
|
|
|
png, err := qrcode.Encode(paymentReq, qrcode.Medium, 256)
|
2023-05-05 05:15:09 +00:00
|
|
|
pngB64 := b64.StdEncoding.EncodeToString(png)
|
2023-05-05 04:37:27 +00:00
|
|
|
|
|
|
|
// write request to redis
|
|
|
|
err = redisCli.Set(ctx, rKey, enc, 15*time.Minute).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")
|
2023-02-05 01:43:38 +00:00
|
|
|
}
|
2023-05-05 04:37:27 +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 template")
|
|
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "text/html")
|
2023-05-05 04:37:27 +00:00
|
|
|
w.WriteHeader(http.StatusOK)
|
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")
|
|
|
|
}
|
|
|
|
return
|
2023-02-04 22:40:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetNostrAddr(w http.ResponseWriter, r *http.Request) {
|
2023-02-05 02:07:26 +00:00
|
|
|
ctx := context.TODO()
|
2023-02-04 22:40:45 +00:00
|
|
|
// get query string for username
|
|
|
|
r.ParseForm()
|
2023-05-01 22:41:28 +00:00
|
|
|
requestedName := r.FormValue("name")
|
2023-02-04 22:40:45 +00:00
|
|
|
|
|
|
|
// connect to redis
|
2023-05-01 22:44:05 +00:00
|
|
|
redisCli := redis.NostrRedisConn.Client
|
2023-02-04 22:40:45 +00:00
|
|
|
|
|
|
|
// 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()
|
2023-02-04 22:40:45 +00:00
|
|
|
if err != nil {
|
|
|
|
w.WriteHeader(http.StatusNotFound)
|
2023-05-03 02:11:53 +00:00
|
|
|
json.NewEncoder(w).Encode(&jsonErrorMessage{
|
|
|
|
Error: "username not registered",
|
|
|
|
})
|
2023-02-04 22:40:45 +00:00
|
|
|
return
|
|
|
|
}
|
2023-04-29 19:25:28 +00:00
|
|
|
dec, err := b64.StdEncoding.DecodeString(addr)
|
|
|
|
if err != nil {
|
|
|
|
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
|
|
|
|
}
|
2023-02-04 22:40:45 +00:00
|
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
w.WriteHeader(http.StatusOK)
|
2023-04-29 19:25:28 +00:00
|
|
|
w.Write(dec)
|
2023-02-04 22:40:45 +00:00
|
|
|
}
|
|
|
|
|
2023-01-25 05:47:06 +00:00
|
|
|
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()
|
2023-01-25 05:47:06 +00:00
|
|
|
// 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}
|
|
|
|
}
|
2023-01-25 05:47:06 +00:00
|
|
|
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()
|
2023-01-25 05:47:06 +00:00
|
|
|
if err != nil {
|
2023-04-29 18:01:40 +00:00
|
|
|
l.Error().Msg("unable to connect to redis")
|
2023-01-25 05:47:06 +00:00
|
|
|
}
|
|
|
|
}
|
2023-02-03 04:56:15 +00:00
|
|
|
|
2023-02-04 22:40:45 +00:00
|
|
|
func getHostname(hostname string) string {
|
|
|
|
// remove port for local testing
|
|
|
|
return hostname
|
2023-02-03 04:56:15 +00:00
|
|
|
}
|
2023-01-25 05:47:06 +00:00
|
|
|
|
2023-02-04 22:46:13 +00:00
|
|
|
func convertNpubToHex(npub string) (string, error) {
|
2023-02-04 22:40:45 +00:00
|
|
|
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
|
|
|
}
|
2023-02-04 22:40:45 +00:00
|
|
|
_, hex, err := nip19.Decode(npub)
|
2023-01-25 05:47:06 +00:00
|
|
|
if err != nil {
|
2023-05-03 02:11:53 +00:00
|
|
|
return "", errors.New("unable to decode npub key")
|
2023-01-25 05:47:06 +00:00
|
|
|
}
|
|
|
|
|
2023-02-04 22:40:45 +00:00
|
|
|
return hex.(string), nil
|
2023-01-25 05:47:06 +00:00
|
|
|
|
|
|
|
}
|
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
|
|
|
}
|