Compare commits

..

6 Commits

Author SHA1 Message Date
fiatjaf
c5f7926471 bunker: repeat connection info every now and then. 2024-02-17 17:56:57 -03:00
fiatjaf
e008e08105 bunker: send responses to relays concurrently. 2024-02-16 11:08:48 -03:00
fiatjaf
5dd5a7c699 bunker: better colors and prompts. 2024-02-12 15:39:13 -03:00
fiatjaf
347a82eaa9 verify: accept event to be verified as json argument. 2024-02-12 15:38:43 -03:00
fiatjaf
e89823b10e ensure at least one blank line will be emitted when piped. 2024-02-06 12:47:58 -03:00
fiatjaf
6626001dd2 --connect-as to specify client pubkey when using --connect to bunker 2024-02-06 12:47:46 -03:00
7 changed files with 102 additions and 39 deletions

102
bunker.go
View File

@@ -1,12 +1,15 @@
package main package main
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/url" "net/url"
"os" "os"
"sync"
"time"
"github.com/manifoldco/promptui" "github.com/fatih/color"
"github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip19" "github.com/nbd-wtf/go-nostr/nip19"
"github.com/nbd-wtf/go-nostr/nip46" "github.com/nbd-wtf/go-nostr/nip46"
@@ -65,13 +68,18 @@ var bunker = &cli.Command{
return err return err
} }
npub, _ := nip19.EncodePublicKey(pubkey) npub, _ := nip19.EncodePublicKey(pubkey)
log("listening at %s%v%s:\n %spubkey:%s %s\n %snpub:%s %s\n %sconnection code:%s %s\n %sbunker:%s %s\n\n", bunkerURI := fmt.Sprintf("bunker://%s?%s", pubkey, qs.Encode())
BOLD_ON, relayURLs, BOLD_OFF, bold := color.New(color.Bold).Sprint
BOLD_ON, BOLD_OFF, pubkey,
BOLD_ON, BOLD_OFF, npub, printBunkerInfo := func() {
BOLD_ON, BOLD_OFF, fmt.Sprintf("%s#secret?%s", npub, qs.Encode()), log("listening at %v:\n pubkey: %s \n npub: %s\n bunker: %s\n\n",
BOLD_ON, BOLD_OFF, fmt.Sprintf("bunker://%s?%s", pubkey, qs.Encode()), bold(relayURLs),
) bold(pubkey),
bold(npub),
bold(bunkerURI),
)
}
printBunkerInfo()
alwaysYes := c.Bool("yes") alwaysYes := c.Bool("yes")
@@ -85,25 +93,63 @@ var bunker = &cli.Command{
}) })
signer := nip46.NewStaticKeySigner(sec) signer := nip46.NewStaticKeySigner(sec)
handlerWg := sync.WaitGroup{}
printLock := sync.Mutex{}
// just a gimmick
var cancelPreviousBunkerInfoPrint context.CancelFunc
_, cancel := context.WithCancel(c.Context)
cancelPreviousBunkerInfoPrint = cancel
for ie := range events { for ie := range events {
cancelPreviousBunkerInfoPrint() // this prevents us from printing a million bunker info blocks
// handle the NIP-46 request event
req, resp, eventResponse, harmless, err := signer.HandleRequest(ie.Event) req, resp, eventResponse, harmless, err := signer.HandleRequest(ie.Event)
if err != nil { if err != nil {
log("< failed to handle request from %s: %s", ie.Event.PubKey, err.Error()) log("< failed to handle request from %s: %s\n", ie.Event.PubKey, err.Error())
continue continue
} }
jreq, _ := json.MarshalIndent(req, " ", " ") jreq, _ := json.MarshalIndent(req, " ", " ")
log("- got request from '%s': %s\n", ie.Event.PubKey, string(jreq)) log("- got request from '%s': %s\n", color.New(color.Bold, color.FgBlue).Sprint(ie.Event.PubKey), string(jreq))
jresp, _ := json.MarshalIndent(resp, " ", " ") jresp, _ := json.MarshalIndent(resp, " ", " ")
log("~ responding with %s\n", string(jresp)) log("~ responding with %s\n", string(jresp))
if alwaysYes || harmless || askProceed(ie.Event.PubKey) { if alwaysYes || harmless || askProceed(ie.Event.PubKey) {
if err := ie.Relay.Publish(c.Context, eventResponse); err == nil { handlerWg.Add(len(relayURLs))
log("* sent response!\n") for _, relayURL := range relayURLs {
} else { go func(relayURL string) {
log("* failed to send response: %s\n", err) if relay, _ := pool.EnsureRelay(relayURL); relay != nil {
err := relay.Publish(c.Context, eventResponse)
printLock.Lock()
if err == nil {
log("* sent response through %s\n", relay.URL)
} else {
log("* failed to send response: %s\n", err)
}
printLock.Unlock()
handlerWg.Done()
}
}(relayURL)
} }
handlerWg.Wait()
} }
// just after handling one request we trigger this
go func() {
ctx, cancel := context.WithCancel(c.Context)
defer cancel()
cancelPreviousBunkerInfoPrint = cancel
// the idea is that we will print the bunker URL again so it is easier to copy-paste by users
// but we will only do if the bunker is inactive for more than 5 minutes
select {
case <-ctx.Done():
case <-time.After(time.Minute * 5):
fmt.Fprintf(os.Stderr, "\n")
printBunkerInfo()
}
}()
} }
return nil return nil
@@ -117,21 +163,23 @@ func askProceed(source string) bool {
return true return true
} }
prompt := promptui.Select{ fmt.Fprintf(os.Stderr, "request from %s:\n", color.New(color.Bold, color.FgBlue).Sprint(source))
Label: "proceed?", res, err := ask(" proceed to fulfill this request? (yes/no/always from this) (y/n/a): ", "",
Items: []string{ func(answer string) bool {
"no", if answer != "y" && answer != "n" && answer != "a" {
"yes", return true
"always from this source", }
}, return false
} })
n, _, _ := prompt.Run() if err != nil {
switch n {
case 0:
return false return false
case 1: }
switch res {
case "n":
return false
case "y":
return true return true
case 2: case "a":
allowedSources = append(allowedSources, source) allowedSources = append(allowedSources, source)
return true return true
} }

View File

@@ -48,6 +48,11 @@ example:
Name: "connect", Name: "connect",
Usage: "sign event using NIP-46, expects a bunker://... URL", Usage: "sign event using NIP-46, expects a bunker://... URL",
}, },
&cli.StringFlag{
Name: "connect-as",
Usage: "private key to when communicating with the bunker given on --connect",
DefaultText: "a random key",
},
&cli.BoolFlag{ &cli.BoolFlag{
Name: "envelope", Name: "envelope",
Usage: "print the event enveloped in a [\"EVENT\", ...] message ready to be sent to a relay", Usage: "print the event enveloped in a [\"EVENT\", ...] message ready to be sent to a relay",

5
go.mod
View File

@@ -8,8 +8,7 @@ require (
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
github.com/fatih/color v1.16.0 github.com/fatih/color v1.16.0
github.com/mailru/easyjson v0.7.7 github.com/mailru/easyjson v0.7.7
github.com/manifoldco/promptui v0.9.0 github.com/nbd-wtf/go-nostr v0.28.6
github.com/nbd-wtf/go-nostr v0.28.4
github.com/nbd-wtf/nostr-sdk v0.0.5 github.com/nbd-wtf/nostr-sdk v0.0.5
github.com/urfave/cli/v2 v2.25.7 github.com/urfave/cli/v2 v2.25.7
golang.org/x/exp v0.0.0-20231006140011-7918f672742d golang.org/x/exp v0.0.0-20231006140011-7918f672742d
@@ -19,6 +18,8 @@ require (
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
github.com/btcsuite/btcd/btcutil v1.1.3 // indirect github.com/btcsuite/btcd/btcutil v1.1.3 // indirect
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 // indirect
github.com/chzyer/logex v1.1.10 // indirect
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect

7
go.sum
View File

@@ -74,15 +74,13 @@ github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlT
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/nbd-wtf/go-nostr v0.28.4 h1:chGBpdCQvM9aInf/vVDishY8GHapgqFc/RLl2WDnHQM= github.com/nbd-wtf/go-nostr v0.28.6 h1:iOyzk+6ReG0lvyRAar7w7omFmUk5mnXDyFYkJ+zEjiw=
github.com/nbd-wtf/go-nostr v0.28.4/go.mod h1:l9NRRaHPN+QwkqrjNKhnfYjQ0+nKP1xZrVxePPGUs+A= github.com/nbd-wtf/go-nostr v0.28.6/go.mod h1:aFcp8NO3erHg+glzBfh4wpaMrV1/ahcUPAgITdptxwA=
github.com/nbd-wtf/nostr-sdk v0.0.5 h1:rec+FcDizDVO0W25PX0lgYMXvP7zNNOgI3Fu9UCm4BY= github.com/nbd-wtf/nostr-sdk v0.0.5 h1:rec+FcDizDVO0W25PX0lgYMXvP7zNNOgI3Fu9UCm4BY=
github.com/nbd-wtf/nostr-sdk v0.0.5/go.mod h1:iJJsikesCGLNFZ9dLqhLPDzdt924EagUmdQxT3w2Lmk= github.com/nbd-wtf/nostr-sdk v0.0.5/go.mod h1:iJJsikesCGLNFZ9dLqhLPDzdt924EagUmdQxT3w2Lmk=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
@@ -129,7 +127,6 @@ golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@@ -20,9 +20,6 @@ import (
const ( const (
LINE_PROCESSING_ERROR = iota LINE_PROCESSING_ERROR = iota
BOLD_ON = "\033[1m"
BOLD_OFF = "\033[21m"
) )
var log = func(msg string, args ...any) { var log = func(msg string, args ...any) {
@@ -69,8 +66,13 @@ func writeStdinLinesOrNothing(ch chan string) (hasStdinLines bool) {
go func() { go func() {
scanner := bufio.NewScanner(os.Stdin) scanner := bufio.NewScanner(os.Stdin)
scanner.Buffer(make([]byte, 16*1024), 256*1024) scanner.Buffer(make([]byte, 16*1024), 256*1024)
hasEmittedAtLeastOne := false
for scanner.Scan() { for scanner.Scan() {
ch <- strings.TrimSpace(scanner.Text()) ch <- strings.TrimSpace(scanner.Text())
hasEmittedAtLeastOne = true
}
if !hasEmittedAtLeastOne {
ch <- ""
} }
close(ch) close(ch)
}() }()
@@ -134,7 +136,12 @@ func gatherSecretKeyOrBunkerFromArguments(c *cli.Context) (string, *nip46.Bunker
var err error var err error
if bunkerURL := c.String("connect"); bunkerURL != "" { if bunkerURL := c.String("connect"); bunkerURL != "" {
clientKey := nostr.GeneratePrivateKey() clientKey := c.String("connect-as")
if clientKey != "" {
clientKey = strings.Repeat("0", 64-len(clientKey)) + clientKey
} else {
clientKey = nostr.GeneratePrivateKey()
}
bunker, err := nip46.ConnectBunker(c.Context, clientKey, bunkerURL, nil) bunker, err := nip46.ConnectBunker(c.Context, clientKey, bunkerURL, nil)
return "", bunker, err return "", bunker, err
} }

5
req.go
View File

@@ -117,6 +117,11 @@ example:
Name: "connect", Name: "connect",
Usage: "sign AUTH using NIP-46, expects a bunker://... URL", Usage: "sign AUTH using NIP-46, expects a bunker://... URL",
}, },
&cli.StringFlag{
Name: "connect-as",
Usage: "private key to when communicating with the bunker given on --connect",
DefaultText: "a random key",
},
}, },
ArgsUsage: "[relay...]", ArgsUsage: "[relay...]",
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {

View File

@@ -15,7 +15,7 @@ var verify = &cli.Command{
it outputs nothing if the verification is successful.`, it outputs nothing if the verification is successful.`,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
for stdinEvent := range getStdinLinesOrBlank() { for stdinEvent := range getStdinLinesOrFirstArgument(c.Args().First()) {
evt := nostr.Event{} evt := nostr.Event{}
if stdinEvent != "" { if stdinEvent != "" {
if err := json.Unmarshal([]byte(stdinEvent), &evt); err != nil { if err := json.Unmarshal([]byte(stdinEvent), &evt); err != nil {