mirror of
https://github.com/layer-systems/relay.git
synced 2026-06-05 18:11:11 +02:00
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:
@@ -23,8 +23,8 @@ services:
|
|||||||
- POSTGRES_DB=khatru-relay
|
- POSTGRES_DB=khatru-relay
|
||||||
- POSTGRES_PASSWORD=postgres
|
- POSTGRES_PASSWORD=postgres
|
||||||
- POSTGRES_USER=postgres
|
- POSTGRES_USER=postgres
|
||||||
# expose:
|
ports:
|
||||||
# - 5432
|
- 127.0.0.1:5432:5432
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: [ "CMD", "pg_isready" ]
|
test: [ "CMD", "pg_isready" ]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
|
|||||||
4
go.mod
4
go.mod
@@ -5,6 +5,8 @@ go 1.24.4
|
|||||||
require (
|
require (
|
||||||
github.com/fiatjaf/eventstore v0.16.2
|
github.com/fiatjaf/eventstore v0.16.2
|
||||||
github.com/fiatjaf/khatru v0.19.1
|
github.com/fiatjaf/khatru v0.19.1
|
||||||
|
github.com/lib/pq v1.10.9
|
||||||
|
github.com/nbd-wtf/go-nostr v0.52.3
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -26,11 +28,9 @@ require (
|
|||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.18.0 // indirect
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||||
github.com/lib/pq v1.10.9 // indirect
|
|
||||||
github.com/mailru/easyjson v0.9.0 // indirect
|
github.com/mailru/easyjson v0.9.0 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/nbd-wtf/go-nostr v0.52.3 // indirect
|
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
|
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
|
||||||
github.com/rs/cors v1.11.1 // indirect
|
github.com/rs/cors v1.11.1 // indirect
|
||||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
|
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
|
||||||
|
|||||||
130
main.go
130
main.go
@@ -1,12 +1,19 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/fiatjaf/eventstore/postgresql"
|
"github.com/fiatjaf/eventstore/postgresql"
|
||||||
"github.com/fiatjaf/khatru"
|
"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 {
|
func getEnv(key, fallback string) string {
|
||||||
@@ -16,6 +23,34 @@ func getEnv(key, fallback string) string {
|
|||||||
return fallback
|
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() {
|
func main() {
|
||||||
// create the relay instance
|
// create the relay instance
|
||||||
relay := khatru.NewRelay()
|
relay := khatru.NewRelay()
|
||||||
@@ -37,6 +72,36 @@ func main() {
|
|||||||
relay.DeleteEvent = append(relay.DeleteEvent, db.DeleteEvent)
|
relay.DeleteEvent = append(relay.DeleteEvent, db.DeleteEvent)
|
||||||
relay.ReplaceEvent = append(relay.ReplaceEvent, db.ReplaceEvent)
|
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
|
// // there are many other configurable things you can set
|
||||||
// relay.RejectEvent = append(relay.RejectEvent,
|
// relay.RejectEvent = append(relay.RejectEvent,
|
||||||
// // built-in policies
|
// // 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()
|
mux := relay.Router()
|
||||||
// set up other http handlers
|
// set up other http handlers
|
||||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
Reference in New Issue
Block a user