dev2main (#1)

* add management database functionality for allowed and banned pubkeys

* add policy to reject events based on validation rules

* expose PostgreSQL port in Docker Compose for local access

* add event rejection policy for banned pubkeys based on management database

---------

Co-authored-by: highperfocused <highperfocused@pm.me>
This commit is contained in:
mroxso
2025-12-30 21:50:39 +01:00
committed by GitHub
parent 58ec0591f0
commit c93da36c4e
3 changed files with 134 additions and 4 deletions

130
main.go
View File

@@ -1,12 +1,19 @@
package main
import (
"context"
"database/sql"
"fmt"
"net/http"
"os"
"time"
"github.com/fiatjaf/eventstore/postgresql"
"github.com/fiatjaf/khatru"
"github.com/fiatjaf/khatru/policies"
_ "github.com/lib/pq"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip86"
)
func getEnv(key, fallback string) string {
@@ -16,6 +23,34 @@ func getEnv(key, fallback string) string {
return fallback
}
func initManagementDB(db *sql.DB) error {
// create allowed_pubkeys table
_, err := db.Exec(`
CREATE TABLE IF NOT EXISTS allowed_pubkeys (
pubkey TEXT PRIMARY KEY,
reason TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
)
`)
if err != nil {
return fmt.Errorf("failed to create allowed_pubkeys table: %w", err)
}
// create banned_pubkeys table
_, err = db.Exec(`
CREATE TABLE IF NOT EXISTS banned_pubkeys (
pubkey TEXT PRIMARY KEY,
reason TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
)
`)
if err != nil {
return fmt.Errorf("failed to create banned_pubkeys table: %w", err)
}
return nil
}
func main() {
// create the relay instance
relay := khatru.NewRelay()
@@ -37,6 +72,36 @@ func main() {
relay.DeleteEvent = append(relay.DeleteEvent, db.DeleteEvent)
relay.ReplaceEvent = append(relay.ReplaceEvent, db.ReplaceEvent)
relay.RejectEvent = append(relay.RejectEvent, policies.ValidateKind)
// setup management database (second connection for NIP-86)
managementDB, err := sql.Open("postgres", getEnv("DATABASE_URL", "postgresql://postgres:postgres@db:5432/khatru-relay?sslmode=disable"))
if err != nil {
panic(err)
}
defer managementDB.Close()
// initialize management tables
if err := initManagementDB(managementDB); err != nil {
panic(err)
}
relay.RejectEvent = append(relay.RejectEvent,
func(ctx context.Context, event *nostr.Event) (reject bool, msg string) {
var reason string
row := managementDB.QueryRowContext(ctx, `SELECT reason FROM banned_pubkeys WHERE pubkey = $1`, event.PubKey)
switch err := row.Scan(&reason); err {
case sql.ErrNoRows:
return false, ""
case nil:
return true, fmt.Sprintf("pubkey %s banned: %s", event.PubKey, reason)
default:
// on unexpected DB errors, do not reject the event solely because of the failure
return false, ""
}
},
)
// // there are many other configurable things you can set
// relay.RejectEvent = append(relay.RejectEvent,
// // built-in policies
@@ -69,6 +134,71 @@ func main() {
// },
// )
// management endpoints
relay.ManagementAPI.RejectAPICall = append(relay.ManagementAPI.RejectAPICall,
func(ctx context.Context, mp nip86.MethodParams) (reject bool, msg string) {
user := khatru.GetAuthed(ctx)
ownerPubKey := getEnv("RELAY_PUBKEY", "480ec1a7516406090dc042ddf67780ef30f26f3a864e83b417c053a5a611c838")
if user != ownerPubKey {
return true, "auth-required: only relay owner can access management API"
}
return false, ""
})
relay.ManagementAPI.AllowPubKey = func(ctx context.Context, pubkey string, reason string) error {
_, err := managementDB.Exec(`
INSERT INTO allowed_pubkeys (pubkey, reason, created_at)
VALUES ($1, $2, $3)
ON CONFLICT (pubkey) DO UPDATE SET reason = $2, created_at = $3
`, pubkey, reason, time.Now())
return err
}
relay.ManagementAPI.BanPubKey = func(ctx context.Context, pubkey string, reason string) error {
_, err := managementDB.Exec(`
INSERT INTO banned_pubkeys (pubkey, reason, created_at)
VALUES ($1, $2, $3)
ON CONFLICT (pubkey) DO UPDATE SET reason = $2, created_at = $3
`, pubkey, reason, time.Now())
return err
}
relay.ManagementAPI.ListAllowedPubKeys = func(ctx context.Context) ([]nip86.PubKeyReason, error) {
rows, err := managementDB.Query(`SELECT pubkey, reason FROM allowed_pubkeys ORDER BY created_at DESC`)
if err != nil {
return nil, err
}
defer rows.Close()
var result []nip86.PubKeyReason
for rows.Next() {
var pk nip86.PubKeyReason
if err := rows.Scan(&pk.PubKey, &pk.Reason); err != nil {
return nil, err
}
result = append(result, pk)
}
return result, rows.Err()
}
relay.ManagementAPI.ListBannedPubKeys = func(ctx context.Context) ([]nip86.PubKeyReason, error) {
rows, err := managementDB.Query(`SELECT pubkey, reason FROM banned_pubkeys ORDER BY created_at DESC`)
if err != nil {
return nil, err
}
defer rows.Close()
var result []nip86.PubKeyReason
for rows.Next() {
var pk nip86.PubKeyReason
if err := rows.Scan(&pk.PubKey, &pk.Reason); err != nil {
return nil, err
}
result = append(result, pk)
}
return result, rows.Err()
}
mux := relay.Router()
// set up other http handlers
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {