actually blocking writes from non-whitelisted people, fixes in eventstore and some other UI tweaks.

This commit is contained in:
fiatjaf 2023-11-02 16:08:53 -03:00
parent 6565b4dcf5
commit 19bc00da24
No known key found for this signature in database
GPG Key ID: BAD43C4BE5C1A3A1
7 changed files with 83 additions and 48 deletions

View File

@ -32,16 +32,20 @@ func userRowComponent(ctx context.Context, profile sdk.ProfileMetadata, loggedUs
} }
return Li( return Li(
A().Href("nostr:"+profile.Npub()).Children( userNameComponent(ctx, profile),
Span(profile.ShortName()).Attr(
"npub", profile.Npub(),
"name", profile.ShortName(),
"_", `
on mouseenter set my innerText to @npub then hide the next <button />
on mouseleave set my innerText to @name then show the next <button />`,
),
).Class("font-mono py-1"),
button, button,
inviteTreeComponent(ctx, profile.PubKey, loggedUser), inviteTreeComponent(ctx, profile.PubKey, loggedUser),
).Class("ml-6") ).Class("ml-6")
} }
func userNameComponent(ctx context.Context, profile sdk.ProfileMetadata) HTMLComponent {
return A().Href("nostr:" + profile.Npub()).Children(
Span(profile.ShortName()).Attr(
"npub", profile.Npub(),
"name", profile.ShortName(),
"_", `
on mouseenter set my innerText to @npub then hide the next <button />
on mouseleave set my innerText to @name then show the next <button />`,
),
).Class("font-mono py-1")
}

2
go.mod
View File

@ -3,7 +3,7 @@ module github.com/github-tijlxyz/khatru-invite
go 1.21.0 go 1.21.0
require ( require (
github.com/fiatjaf/eventstore v0.0.2 github.com/fiatjaf/eventstore v0.1.0
github.com/fiatjaf/khatru v0.1.0 github.com/fiatjaf/khatru v0.1.0
github.com/kelseyhightower/envconfig v1.4.0 github.com/kelseyhightower/envconfig v1.4.0
github.com/nbd-wtf/go-nostr v0.25.0 github.com/nbd-wtf/go-nostr v0.25.0

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"context"
"net/http" "net/http"
"github.com/nbd-wtf/go-nostr/nip19" "github.com/nbd-wtf/go-nostr/nip19"
@ -10,18 +9,11 @@ import (
func inviteTreeHandler(w http.ResponseWriter, r *http.Request) { func inviteTreeHandler(w http.ResponseWriter, r *http.Request) {
content := inviteTreePageHTML(r.Context(), InviteTreePageParams{ content := inviteTreePageHTML(r.Context(), InviteTreePageParams{
LoggedUser: getLoggedUser(r), loggedUser: getLoggedUser(r),
}) })
htmlgo.Fprint(w, baseHTML(content), r.Context()) htmlgo.Fprint(w, baseHTML(content), r.Context())
} }
func getUserRowHandler(w http.ResponseWriter, r *http.Request) {
pubkey := r.PostFormValue("pubkey")
profile := fetchAndStoreProfile(r.Context(), pubkey)
content := userRowComponent(r.Context(), profile, getLoggedUser(r))
htmlgo.Fprint(w, content, r.Context())
}
func addToWhitelistHandler(w http.ResponseWriter, r *http.Request) { func addToWhitelistHandler(w http.ResponseWriter, r *http.Request) {
loggedUser := getLoggedUser(r) loggedUser := getLoggedUser(r)
@ -34,6 +26,7 @@ func addToWhitelistHandler(w http.ResponseWriter, r *http.Request) {
http.Error(w, "failed to add to whitelist: "+err.Error(), 500) http.Error(w, "failed to add to whitelist: "+err.Error(), 500)
return return
} }
content := inviteTreeComponent(r.Context(), "", loggedUser) content := inviteTreeComponent(r.Context(), "", loggedUser)
htmlgo.Fprint(w, content, r.Context()) htmlgo.Fprint(w, content, r.Context())
} }
@ -162,7 +155,7 @@ func reportsViewerHandler(w http.ResponseWriter, r *http.Request) {
func homePageHandler(w http.ResponseWriter, r *http.Request) { func homePageHandler(w http.ResponseWriter, r *http.Request) {
content := homePageHTML(r.Context(), HomePageParams{ content := homePageHTML(r.Context(), HomePageParams{
RelayOwnerInfo: fetchAndStoreProfile(context.Background(), s.RelayPubkey), relayOwnerInfo: fetchAndStoreProfile(r.Context(), s.RelayPubkey),
}) })
htmlgo.Fprint(w, baseHTML(content), r.Context()) htmlgo.Fprint(w, baseHTML(content), r.Context())
} }

19
main.go
View File

@ -6,7 +6,7 @@ import (
"net/url" "net/url"
"os" "os"
"github.com/fiatjaf/eventstore/badgern" "github.com/fiatjaf/eventstore/badger"
"github.com/fiatjaf/khatru" "github.com/fiatjaf/khatru"
"github.com/kelseyhightower/envconfig" "github.com/kelseyhightower/envconfig"
"github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr"
@ -25,7 +25,7 @@ type Settings struct {
var ( var (
s Settings s Settings
db = badgern.BadgerBackend{} db = badger.BadgerBackend{}
log = zerolog.New(os.Stderr).Output(zerolog.ConsoleWriter{Out: os.Stdout}).With().Timestamp().Logger() log = zerolog.New(os.Stderr).Output(zerolog.ConsoleWriter{Out: os.Stdout}).With().Timestamp().Logger()
whitelist = make(Whitelist) whitelist = make(Whitelist)
relay = khatru.NewRelay() relay = khatru.NewRelay()
@ -52,18 +52,19 @@ func main() {
relay.Description = s.RelayDescription relay.Description = s.RelayDescription
relay.Contact = s.RelayContact relay.Contact = s.RelayContact
// load whitelist storage relay.StoreEvent = append(relay.StoreEvent, db.SaveEvent)
relay.QueryEvents = append(relay.QueryEvents, db.QueryEvents)
relay.CountEvents = append(relay.CountEvents, db.CountEvents)
relay.DeleteEvent = append(relay.DeleteEvent, db.DeleteEvent)
relay.RejectEvent = append(relay.RejectEvent, rejectEventsFromUsersNotInWhitelist)
// load users registry
if err := loadWhitelist(); err != nil { if err := loadWhitelist(); err != nil {
log.Fatal().Err(err).Msg("failed to load whitelist") log.Fatal().Err(err).Msg("failed to load whitelist")
return return
} }
relay.StoreEvent = append(relay.StoreEvent, db.SaveEvent) // http routes
relay.QueryEvents = append(relay.QueryEvents, db.QueryEvents)
relay.CountEvents = append(relay.CountEvents, db.CountEvents)
relay.DeleteEvent = append(relay.DeleteEvent, db.DeleteEvent)
relay.Router().HandleFunc("/get-user-row", getUserRowHandler)
relay.Router().HandleFunc("/add-to-whitelist", addToWhitelistHandler) relay.Router().HandleFunc("/add-to-whitelist", addToWhitelistHandler)
relay.Router().HandleFunc("/remove-from-whitelist", removeFromWhitelistHandler) relay.Router().HandleFunc("/remove-from-whitelist", removeFromWhitelistHandler)
relay.Router().HandleFunc("/reports", reportsViewerHandler) relay.Router().HandleFunc("/reports", reportsViewerHandler)

View File

@ -30,8 +30,11 @@ func baseHTML(inside HTMLComponent) HTMLComponent {
A().Text("information").Href("/").Class(navItemClass).Attr("hx-boost", "true", "hx-target", "main", "hx-select", "main"), A().Text("information").Href("/").Class(navItemClass).Attr("hx-boost", "true", "hx-target", "main", "hx-select", "main"),
A().Text("invite tree").Href("/users").Class(navItemClass).Attr("hx-boost", "true", "hx-target", "main", "hx-select", "main"), A().Text("invite tree").Href("/users").Class(navItemClass).Attr("hx-boost", "true", "hx-target", "main", "hx-select", "main"),
A().Text("reports").Href("/reports").Class(navItemClass).Attr("hx-boost", "true", "hx-target", "main", "hx-select", "main"), A().Text("reports").Href("/reports").Class(navItemClass).Attr("hx-boost", "true", "hx-target", "main", "hx-select", "main"),
A().Text("login").Href("#").Class(navItemClass). A().Text("").Href("#").Class(navItemClass).
Attr("_", "on click get window.nostr.signEvent({created_at: Math.round(Date.now()/1000), kind: 27235, tags: [['domain', "+s.Domain+"]], content: ''}) then get JSON.stringify(it) then set cookies['nip98'] to it"), Attr("_", `
on click if my innerText is equal to "login" get window.nostr.signEvent({created_at: Math.round(Date.now()/1000), kind: 27235, tags: [['domain', "`+s.Domain+`"]], content: ''}) then get JSON.stringify(it) then set cookies['nip98'] to it otherwise call cookies.clear('nip98') end then trigger load on me
on load get cookies['nip98'] then if it is undefined set my innerText to "login" otherwise set my innerText to "logout"`),
).Class("flex flex-1 items-center justify-center"), ).Class("flex flex-1 items-center justify-center"),
Main(inside).Class("m-4"), Main(inside).Class("m-4"),
).Class("mx-4 my-6"), ).Class("mx-4 my-6"),
@ -39,7 +42,7 @@ func baseHTML(inside HTMLComponent) HTMLComponent {
} }
type HomePageParams struct { type HomePageParams struct {
RelayOwnerInfo sdk.ProfileMetadata relayOwnerInfo sdk.ProfileMetadata
} }
func homePageHTML(ctx context.Context, params HomePageParams) HTMLComponent { func homePageHTML(ctx context.Context, params HomePageParams) HTMLComponent {
@ -59,33 +62,43 @@ func homePageHTML(ctx context.Context, params HomePageParams) HTMLComponent {
contact, contact,
Div( Div(
Text("relay master: "), Text("relay master: "),
A().Text(params.RelayOwnerInfo.Name).Href("nostr:"+params.RelayOwnerInfo.Npub()), userNameComponent(ctx, params.relayOwnerInfo),
), ),
Br(), Br(),
Div( Div(
Text("this relay uses"), Text("this relay uses"),
A().Target("_blank").Href("https://github.com/github-tijlxyz/khatru-invite").Text("Khatru Invite"), A().Target("_blank").Href("https://github.com/github-tijlxyz/khatru-invite").Text("Khatru Invite").
Class("underline"),
Text(" which is built with "), Text(" which is built with "),
A().Target("_blank").Href("https://github.com/fiatjaf/khatru").Text("Khatru"), A().Target("_blank").Href("https://github.com/fiatjaf/khatru").Text("Khatru").
), Class("underline"),
).Class("text-sm"),
) )
} }
type InviteTreePageParams struct { type InviteTreePageParams struct {
LoggedUser string loggedUser string
} }
func inviteTreePageHTML(ctx context.Context, params InviteTreePageParams) HTMLComponent { func inviteTreePageHTML(ctx context.Context, params InviteTreePageParams) HTMLComponent {
return Form( inviteForm := Div()
Input("pubkey").Type("text").Placeholder("npub1...").Class("w-96 rounded-md border-0 p-2 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600"),
Button("invite").Class(buttonClass+" p-2 bg-white hover:bg-gray-50"), if params.loggedUser != "" {
inviteForm = Form(
Input("pubkey").Type("text").Placeholder("npub1...").Class("w-96 rounded-md border-0 p-2 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600"),
Button("invite").Class(buttonClass+" p-2 bg-white hover:bg-gray-50"),
).Attr(
"hx-post", "/add-to-whitelist",
"hx-trigger", "submit",
"hx-target", "#tree",
"_", "on htmx:afterRequest(elt, successful) if successful and elt is I call I.reset()",
)
}
return Div(
inviteForm,
Div( Div(
inviteTreeComponent(ctx, "", params.LoggedUser), inviteTreeComponent(ctx, "", params.loggedUser),
).Id("tree").Class("mt-3"), ).Id("tree").Class("mt-3"),
).Attr(
"hx-post", "/add-to-whitelist",
"hx-trigger", "submit",
"hx-target", "#tree",
"_", "on htmx:afterRequest(elt, successful) if successful and elt is I call I.reset()",
) )
} }

14
relay.go Normal file
View File

@ -0,0 +1,14 @@
package main
import (
"context"
"github.com/nbd-wtf/go-nostr"
)
func rejectEventsFromUsersNotInWhitelist(ctx context.Context, event *nostr.Event) (reject bool, msg string) {
if isPublicKeyInWhitelist(event.PubKey) {
return false, ""
}
return true, "not authorized"
}

View File

@ -13,9 +13,19 @@ const WHITELIST_FILE = "users.json"
type Whitelist map[string]string // { [user_pubkey]: [invited_by] } type Whitelist map[string]string // { [user_pubkey]: [invited_by] }
func addToWhitelist(pubkey string, inviter string) error { func addToWhitelist(pubkey string, inviter string) error {
if nostr.IsValidPublicKeyHex(pubkey) && isPublicKeyInWhitelist(inviter) && !isPublicKeyInWhitelist(pubkey) { if !isPublicKeyInWhitelist(inviter) {
whitelist[pubkey] = inviter return fmt.Errorf("pubkey %s doesn't have permission to invite", inviter)
} }
if !nostr.IsValidPublicKeyHex(pubkey) {
return fmt.Errorf("pubkey invalid: %s", pubkey)
}
if isPublicKeyInWhitelist(pubkey) {
return fmt.Errorf("pubkey already in whitelist: %s", pubkey)
}
whitelist[pubkey] = inviter
return saveWhitelist() return saveWhitelist()
} }