mirror of
https://github.com/github-tijlxyz/khatru-pyramid.git
synced 2025-06-23 16:05:28 +00:00
actually blocking writes from non-whitelisted people, fixes in eventstore and some other UI tweaks.
This commit is contained in:
parent
6565b4dcf5
commit
19bc00da24
@ -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
2
go.mod
@ -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
|
||||||
|
13
handler.go
13
handler.go
@ -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
19
main.go
@ -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)
|
||||||
|
47
pages.go
47
pages.go
@ -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
14
relay.go
Normal 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"
|
||||||
|
}
|
14
whitelist.go
14
whitelist.go
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user