mirror of
https://github.com/github-tijlxyz/khatru-pyramid.git
synced 2025-06-12 18:45:07 +00:00
cookie-based nip-98-like login.
This commit is contained in:
parent
0506a198c1
commit
f850a485cb
21
handler.go
21
handler.go
@ -3,35 +3,44 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
|
"github.com/nbd-wtf/go-nostr/nip19"
|
||||||
"github.com/theplant/htmlgo"
|
"github.com/theplant/htmlgo"
|
||||||
)
|
)
|
||||||
|
|
||||||
// embed ui files
|
// embed ui files
|
||||||
|
|
||||||
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),
|
||||||
|
})
|
||||||
htmlgo.Fprint(w, baseHTML(content), r.Context())
|
htmlgo.Fprint(w, baseHTML(content), r.Context())
|
||||||
}
|
}
|
||||||
|
|
||||||
func addToWhitelistHandler(w http.ResponseWriter, r *http.Request) {
|
func addToWhitelistHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
loggedUser := getLoggedUser(r)
|
||||||
|
|
||||||
pubkey := r.PostFormValue("pubkey")
|
pubkey := r.PostFormValue("pubkey")
|
||||||
if err := addToWhitelist(r.Context(), pubkey, s.RelayPubkey); err != nil {
|
if pfx, value, err := nip19.Decode(pubkey); err == nil && pfx == "npub" {
|
||||||
|
pubkey = value.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := addToWhitelist(r.Context(), pubkey, loggedUser); err != nil {
|
||||||
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 := buildInviteTree(r.Context(), s.RelayPubkey)
|
content := buildInviteTree(r.Context(), s.RelayPubkey, loggedUser)
|
||||||
htmlgo.Fprint(w, content, r.Context())
|
htmlgo.Fprint(w, content, r.Context())
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeFromWhitelistHandler(w http.ResponseWriter, r *http.Request) {
|
func removeFromWhitelistHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
loggedUser := getLoggedUser(r)
|
||||||
pubkey := r.PostFormValue("pubkey")
|
pubkey := r.PostFormValue("pubkey")
|
||||||
if err := removeFromWhitelist(r.Context(), pubkey); err != nil {
|
if err := removeFromWhitelist(r.Context(), pubkey, loggedUser); err != nil {
|
||||||
http.Error(w, "failed to remove from whitelist: "+err.Error(), 500)
|
http.Error(w, "failed to remove from whitelist: "+err.Error(), 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
content := buildInviteTree(r.Context(), s.RelayPubkey)
|
content := buildInviteTree(r.Context(), s.RelayPubkey, loggedUser)
|
||||||
htmlgo.Fprint(w, content, r.Context())
|
htmlgo.Fprint(w, content, r.Context())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
15
main.go
15
main.go
@ -1,12 +1,15 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/fiatjaf/khatru"
|
"github.com/fiatjaf/khatru"
|
||||||
"github.com/fiatjaf/khatru/plugins/storage/badgern"
|
"github.com/fiatjaf/khatru/plugins/storage/badgern"
|
||||||
"github.com/kelseyhightower/envconfig"
|
"github.com/kelseyhightower/envconfig"
|
||||||
|
"github.com/nbd-wtf/go-nostr"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -67,3 +70,15 @@ func main() {
|
|||||||
log.Fatal().Err(err).Msg("failed to serve")
|
log.Fatal().Err(err).Msg("failed to serve")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getLoggedUser(r *http.Request) string {
|
||||||
|
if cookie, _ := r.Cookie("nip98"); cookie != nil {
|
||||||
|
if evtj, err := url.QueryUnescape(cookie.Value); err == nil {
|
||||||
|
var evt nostr.Event
|
||||||
|
if err := json.Unmarshal([]byte(evtj), &evt); err == nil {
|
||||||
|
return evt.PubKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
31
pages.go
31
pages.go
@ -6,7 +6,7 @@ import (
|
|||||||
. "github.com/theplant/htmlgo"
|
. "github.com/theplant/htmlgo"
|
||||||
)
|
)
|
||||||
|
|
||||||
const buttonClass = "rounded-md bg-white p-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
|
const buttonClass = "rounded-md text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300"
|
||||||
|
|
||||||
func baseHTML(inside HTMLComponent) HTMLComponent {
|
func baseHTML(inside HTMLComponent) HTMLComponent {
|
||||||
navItemClass := "text-gray-600 hover:bg-gray-200 rounded-md px-3 py-2 font-medium"
|
navItemClass := "text-gray-600 hover:bg-gray-200 rounded-md px-3 py-2 font-medium"
|
||||||
@ -29,6 +29,7 @@ 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).Attr("_", "on click get window.nostr.signEvent({created_at: Math.round(Date.now()/1000), kind: 27235, tags: [['u', location.href]], content: ''}) then get JSON.stringify(it) then set cookies['nip98'] to it"),
|
||||||
).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"),
|
||||||
@ -68,29 +69,41 @@ func homePageHTML(ctx context.Context, params HomePageParams) HTMLComponent {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
type InviteTreePageParams struct{}
|
type InviteTreePageParams struct {
|
||||||
|
LoggedUser string
|
||||||
|
}
|
||||||
|
|
||||||
func inviteTreePageHTML(ctx context.Context, params InviteTreePageParams) HTMLComponent {
|
func inviteTreePageHTML(ctx context.Context, params InviteTreePageParams) HTMLComponent {
|
||||||
return Form(
|
return 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"),
|
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),
|
Button("invite").Class(buttonClass+" p-2 bg-white hover:bg-gray-50"),
|
||||||
Div(
|
Div(
|
||||||
buildInviteTree(ctx, s.RelayPubkey),
|
buildInviteTree(ctx, s.RelayPubkey, params.LoggedUser),
|
||||||
).Id("tree").Class("mt-3"),
|
).Id("tree").Class("mt-3"),
|
||||||
).Attr("hx-post", "/add-to-whitelist", "hx-trigger", "submit", "hx-target", "#tree")
|
).Attr("hx-post", "/add-to-whitelist", "hx-trigger", "submit", "hx-target", "#tree")
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildInviteTree(ctx context.Context, invitedBy string) HTMLComponent {
|
func buildInviteTree(ctx context.Context, invitedBy string, loggedUser string) HTMLComponent {
|
||||||
children := make([]HTMLComponent, 0, len(whitelist))
|
children := make([]HTMLComponent, 0, len(whitelist))
|
||||||
for _, entry := range whitelist {
|
for _, entry := range whitelist {
|
||||||
if entry.InvitedBy == invitedBy {
|
if entry.InvitedBy == invitedBy {
|
||||||
user := getUserInfo(ctx, entry.PublicKey)
|
user := getUserInfo(ctx, entry.PublicKey)
|
||||||
|
button := Span("")
|
||||||
|
if invitedBy == loggedUser {
|
||||||
|
button = Button("remove").
|
||||||
|
Class(buttonClass+" px-2 bg-red-100 hover:bg-red-300").
|
||||||
|
Attr("hx-post", "/remove-from-whitelist",
|
||||||
|
"hx-trigger", "click",
|
||||||
|
"hx-target", "#tree",
|
||||||
|
"hx-vals", `{"pubkey": "`+entry.PublicKey+`"}`)
|
||||||
|
}
|
||||||
|
|
||||||
children = append(children,
|
children = append(children,
|
||||||
Li(
|
Li(
|
||||||
A().Href("nostr:"+user.Npub).Text(user.Name).Class("font-mono"),
|
A().Href("nostr:"+user.Npub).Text(user.Name).Class("font-mono py-1"),
|
||||||
Button("remove").Class(buttonClass).Attr("hx-post", "/remove-from-whitelist", "hx-trigger", "click", "hx-target", "#tree"),
|
button,
|
||||||
buildInviteTree(ctx, entry.PublicKey),
|
buildInviteTree(ctx, entry.PublicKey, loggedUser),
|
||||||
).Class("ml-3"),
|
).Class("ml-4"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
12
whitelist.go
12
whitelist.go
@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/nbd-wtf/go-nostr"
|
"github.com/nbd-wtf/go-nostr"
|
||||||
@ -71,18 +72,21 @@ func whitelistRejecter(ctx context.Context, evt *nostr.Event) (reject bool, msg
|
|||||||
return false, ""
|
return false, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func addToWhitelist(ctx context.Context, pubkey string, invitedBy string) error {
|
func addToWhitelist(ctx context.Context, pubkey string, inviter string) error {
|
||||||
if nostr.IsValidPublicKeyHex(pubkey) && !isPublicKeyInWhitelist(pubkey) {
|
if nostr.IsValidPublicKeyHex(pubkey) && isPublicKeyInWhitelist(inviter) && !isPublicKeyInWhitelist(pubkey) {
|
||||||
whitelist = append(whitelist, WhitelistEntry{PublicKey: pubkey, InvitedBy: invitedBy})
|
whitelist = append(whitelist, WhitelistEntry{PublicKey: pubkey, InvitedBy: inviter})
|
||||||
}
|
}
|
||||||
return saveWhitelist()
|
return saveWhitelist()
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeFromWhitelist(ctx context.Context, pubkey string) error {
|
func removeFromWhitelist(ctx context.Context, pubkey string, deleter string) error {
|
||||||
idx := slices.IndexFunc(whitelist, func(we WhitelistEntry) bool { return we.PublicKey == pubkey })
|
idx := slices.IndexFunc(whitelist, func(we WhitelistEntry) bool { return we.PublicKey == pubkey })
|
||||||
if idx == -1 {
|
if idx == -1 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if whitelist[idx].InvitedBy != deleter {
|
||||||
|
return fmt.Errorf("can't remove a user you haven't invited")
|
||||||
|
}
|
||||||
|
|
||||||
whitelist = append(whitelist[0:idx], whitelist[idx+1:]...)
|
whitelist = append(whitelist[0:idx], whitelist[idx+1:]...)
|
||||||
return saveWhitelist()
|
return saveWhitelist()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user