public pages, rate limiters
15
config.go
@@ -1,12 +1,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"encoding/json"
|
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
)
|
)
|
||||||
@@ -146,6 +146,17 @@ func getEnvInt(key string, defaultValue int) int {
|
|||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getEnvBool(key string, defaultValue bool) bool {
|
||||||
|
if value, ok := os.LookupEnv(key); ok {
|
||||||
|
boolValue, err := strconv.ParseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return boolValue
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
var art = `
|
var art = `
|
||||||
██╗ ██╗ █████╗ ██╗ ██╗███████╗███╗ ██╗
|
██╗ ██╗ █████╗ ██╗ ██╗███████╗███╗ ██╗
|
||||||
██║ ██║██╔══██╗██║ ██║██╔════╝████╗ ██║
|
██║ ██║██╔══██╗██║ ██║██╔════╝████╗ ██║
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ services:
|
|||||||
- "./db:/app/db"
|
- "./db:/app/db"
|
||||||
- "./haven:/app/haven"
|
- "./haven:/app/haven"
|
||||||
ports:
|
ports:
|
||||||
- "3335"
|
- "3355"
|
||||||
user: "${DOCKER_UID:-1000}:${DOCKER_GID:-1000}"
|
user: "${DOCKER_UID:-1000}:${DOCKER_GID:-1000}"
|
||||||
|
|
||||||
tor:
|
tor:
|
||||||
|
|||||||
@@ -10,5 +10,5 @@ services:
|
|||||||
- "./db:/app/db"
|
- "./db:/app/db"
|
||||||
- "./haven:/app/haven"
|
- "./haven:/app/haven"
|
||||||
ports:
|
ports:
|
||||||
- "3335:3335"
|
- "3355:3355"
|
||||||
user: "${DOCKER_UID:-1000}:${DOCKER_GID:-1000}"
|
user: "${DOCKER_UID:-1000}:${DOCKER_GID:-1000}"
|
||||||
|
|||||||
106
init.go
@@ -1,8 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/fiatjaf/eventstore/lmdb"
|
"github.com/fiatjaf/eventstore/lmdb"
|
||||||
"github.com/fiatjaf/khatru"
|
"github.com/fiatjaf/khatru"
|
||||||
|
"github.com/fiatjaf/khatru/policies"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -66,6 +69,8 @@ func initRelays() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initRelayLimits()
|
||||||
|
|
||||||
privateRelay.Info.Name = config.PrivateRelayName
|
privateRelay.Info.Name = config.PrivateRelayName
|
||||||
privateRelay.Info.PubKey = nPubToPubkey(config.PrivateRelayNpub)
|
privateRelay.Info.PubKey = nPubToPubkey(config.PrivateRelayNpub)
|
||||||
privateRelay.Info.Description = config.PrivateRelayDescription
|
privateRelay.Info.Description = config.PrivateRelayDescription
|
||||||
@@ -74,6 +79,31 @@ func initRelays() {
|
|||||||
privateRelay.Info.Software = config.RelaySoftware
|
privateRelay.Info.Software = config.RelaySoftware
|
||||||
privateRelay.ServiceURL = "https://" + config.RelayURL + "/private"
|
privateRelay.ServiceURL = "https://" + config.RelayURL + "/private"
|
||||||
|
|
||||||
|
if !privateRelayLimits.AllowEmptyFilters {
|
||||||
|
privateRelay.RejectFilter = append(privateRelay.RejectFilter, policies.NoEmptyFilters)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !privateRelayLimits.AllowComplexFilters {
|
||||||
|
privateRelay.RejectFilter = append(privateRelay.RejectFilter, policies.NoComplexFilters)
|
||||||
|
}
|
||||||
|
|
||||||
|
privateRelay.RejectEvent = append(privateRelay.RejectEvent,
|
||||||
|
policies.RejectEventsWithBase64Media,
|
||||||
|
policies.EventIPRateLimiter(
|
||||||
|
privateRelayLimits.EventIPLimiterTokensPerInterval,
|
||||||
|
time.Minute*time.Duration(privateRelayLimits.EventIPLimiterInterval),
|
||||||
|
privateRelayLimits.EventIPLimiterMaxTokens,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
privateRelay.RejectConnection = append(privateRelay.RejectConnection,
|
||||||
|
policies.ConnectionRateLimiter(
|
||||||
|
privateRelayLimits.ConnectionRateLimiterTokensPerInterval,
|
||||||
|
time.Minute*time.Duration(privateRelayLimits.ConnectionRateLimiterInterval),
|
||||||
|
privateRelayLimits.ConnectionRateLimiterMaxTokens,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
chatRelay.Info.Name = config.ChatRelayName
|
chatRelay.Info.Name = config.ChatRelayName
|
||||||
chatRelay.Info.PubKey = nPubToPubkey(config.ChatRelayNpub)
|
chatRelay.Info.PubKey = nPubToPubkey(config.ChatRelayNpub)
|
||||||
chatRelay.Info.Description = config.ChatRelayDescription
|
chatRelay.Info.Description = config.ChatRelayDescription
|
||||||
@@ -82,6 +112,31 @@ func initRelays() {
|
|||||||
chatRelay.Info.Software = config.RelaySoftware
|
chatRelay.Info.Software = config.RelaySoftware
|
||||||
chatRelay.ServiceURL = "https://" + config.RelayURL + "/chat"
|
chatRelay.ServiceURL = "https://" + config.RelayURL + "/chat"
|
||||||
|
|
||||||
|
if !chatRelayLimits.AllowEmptyFilters {
|
||||||
|
chatRelay.RejectFilter = append(chatRelay.RejectFilter, policies.NoEmptyFilters)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !chatRelayLimits.AllowComplexFilters {
|
||||||
|
chatRelay.RejectFilter = append(chatRelay.RejectFilter, policies.NoComplexFilters)
|
||||||
|
}
|
||||||
|
|
||||||
|
chatRelay.RejectEvent = append(chatRelay.RejectEvent,
|
||||||
|
policies.RejectEventsWithBase64Media,
|
||||||
|
policies.EventIPRateLimiter(
|
||||||
|
chatRelayLimits.EventIPLimiterTokensPerInterval,
|
||||||
|
time.Minute*time.Duration(chatRelayLimits.EventIPLimiterInterval),
|
||||||
|
chatRelayLimits.EventIPLimiterMaxTokens,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
chatRelay.RejectConnection = append(chatRelay.RejectConnection,
|
||||||
|
policies.ConnectionRateLimiter(
|
||||||
|
chatRelayLimits.ConnectionRateLimiterTokensPerInterval,
|
||||||
|
time.Minute*time.Duration(chatRelayLimits.ConnectionRateLimiterInterval),
|
||||||
|
chatRelayLimits.ConnectionRateLimiterMaxTokens,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
outboxRelay.Info.Name = config.OutboxRelayName
|
outboxRelay.Info.Name = config.OutboxRelayName
|
||||||
outboxRelay.Info.PubKey = nPubToPubkey(config.OutboxRelayNpub)
|
outboxRelay.Info.PubKey = nPubToPubkey(config.OutboxRelayNpub)
|
||||||
outboxRelay.Info.Description = config.OutboxRelayDescription
|
outboxRelay.Info.Description = config.OutboxRelayDescription
|
||||||
@@ -89,6 +144,31 @@ func initRelays() {
|
|||||||
outboxRelay.Info.Version = config.RelayVersion
|
outboxRelay.Info.Version = config.RelayVersion
|
||||||
outboxRelay.Info.Software = config.RelaySoftware
|
outboxRelay.Info.Software = config.RelaySoftware
|
||||||
|
|
||||||
|
if !outboxRelayLimits.AllowEmptyFilters {
|
||||||
|
outboxRelay.RejectFilter = append(outboxRelay.RejectFilter, policies.NoEmptyFilters)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !outboxRelayLimits.AllowComplexFilters {
|
||||||
|
outboxRelay.RejectFilter = append(outboxRelay.RejectFilter, policies.NoComplexFilters)
|
||||||
|
}
|
||||||
|
|
||||||
|
outboxRelay.RejectEvent = append(outboxRelay.RejectEvent,
|
||||||
|
policies.RejectEventsWithBase64Media,
|
||||||
|
policies.EventIPRateLimiter(
|
||||||
|
outboxRelayLimits.EventIPLimiterTokensPerInterval,
|
||||||
|
time.Minute*time.Duration(outboxRelayLimits.EventIPLimiterInterval),
|
||||||
|
outboxRelayLimits.EventIPLimiterMaxTokens,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
outboxRelay.RejectConnection = append(outboxRelay.RejectConnection,
|
||||||
|
policies.ConnectionRateLimiter(
|
||||||
|
outboxRelayLimits.ConnectionRateLimiterTokensPerInterval,
|
||||||
|
time.Minute*time.Duration(outboxRelayLimits.ConnectionRateLimiterInterval),
|
||||||
|
outboxRelayLimits.ConnectionRateLimiterMaxTokens,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
inboxRelay.Info.Name = config.InboxRelayName
|
inboxRelay.Info.Name = config.InboxRelayName
|
||||||
inboxRelay.Info.PubKey = nPubToPubkey(config.InboxRelayNpub)
|
inboxRelay.Info.PubKey = nPubToPubkey(config.InboxRelayNpub)
|
||||||
inboxRelay.Info.Description = config.InboxRelayDescription
|
inboxRelay.Info.Description = config.InboxRelayDescription
|
||||||
@@ -96,4 +176,30 @@ func initRelays() {
|
|||||||
inboxRelay.Info.Version = config.RelayVersion
|
inboxRelay.Info.Version = config.RelayVersion
|
||||||
inboxRelay.Info.Software = config.RelaySoftware
|
inboxRelay.Info.Software = config.RelaySoftware
|
||||||
inboxRelay.ServiceURL = "https://" + config.RelayURL + "/inbox"
|
inboxRelay.ServiceURL = "https://" + config.RelayURL + "/inbox"
|
||||||
|
|
||||||
|
if !inboxRelayLimits.AllowEmptyFilters {
|
||||||
|
inboxRelay.RejectFilter = append(inboxRelay.RejectFilter, policies.NoEmptyFilters)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !inboxRelayLimits.AllowComplexFilters {
|
||||||
|
inboxRelay.RejectFilter = append(inboxRelay.RejectFilter, policies.NoComplexFilters)
|
||||||
|
}
|
||||||
|
|
||||||
|
inboxRelay.RejectEvent = append(inboxRelay.RejectEvent,
|
||||||
|
policies.RejectEventsWithBase64Media,
|
||||||
|
policies.EventIPRateLimiter(
|
||||||
|
inboxRelayLimits.EventIPLimiterTokensPerInterval,
|
||||||
|
time.Minute*time.Duration(inboxRelayLimits.EventIPLimiterInterval),
|
||||||
|
inboxRelayLimits.EventIPLimiterMaxTokens,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
inboxRelay.RejectConnection = append(inboxRelay.RejectConnection,
|
||||||
|
policies.ConnectionRateLimiter(
|
||||||
|
inboxRelayLimits.ConnectionRateLimiterTokensPerInterval,
|
||||||
|
time.Minute*time.Duration(inboxRelayLimits.ConnectionRateLimiterInterval),
|
||||||
|
inboxRelayLimits.ConnectionRateLimiterMaxTokens,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
113
limits.go
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
privateRelayLimits PrivateRelayLimits
|
||||||
|
chatRelayLimits ChatRelayLimits
|
||||||
|
inboxRelayLimits InboxRelayLimits
|
||||||
|
outboxRelayLimits OutboxRelayLimits
|
||||||
|
)
|
||||||
|
|
||||||
|
type PrivateRelayLimits struct {
|
||||||
|
EventIPLimiterTokensPerInterval int
|
||||||
|
EventIPLimiterInterval int
|
||||||
|
EventIPLimiterMaxTokens int
|
||||||
|
AllowEmptyFilters bool
|
||||||
|
AllowComplexFilters bool
|
||||||
|
ConnectionRateLimiterTokensPerInterval int
|
||||||
|
ConnectionRateLimiterInterval int
|
||||||
|
ConnectionRateLimiterMaxTokens int
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChatRelayLimits struct {
|
||||||
|
EventIPLimiterTokensPerInterval int
|
||||||
|
EventIPLimiterInterval int
|
||||||
|
EventIPLimiterMaxTokens int
|
||||||
|
AllowEmptyFilters bool
|
||||||
|
AllowComplexFilters bool
|
||||||
|
ConnectionRateLimiterTokensPerInterval int
|
||||||
|
ConnectionRateLimiterInterval int
|
||||||
|
ConnectionRateLimiterMaxTokens int
|
||||||
|
}
|
||||||
|
|
||||||
|
type InboxRelayLimits struct {
|
||||||
|
EventIPLimiterTokensPerInterval int
|
||||||
|
EventIPLimiterInterval int
|
||||||
|
EventIPLimiterMaxTokens int
|
||||||
|
AllowEmptyFilters bool
|
||||||
|
AllowComplexFilters bool
|
||||||
|
ConnectionRateLimiterTokensPerInterval int
|
||||||
|
ConnectionRateLimiterInterval int
|
||||||
|
ConnectionRateLimiterMaxTokens int
|
||||||
|
}
|
||||||
|
|
||||||
|
type OutboxRelayLimits struct {
|
||||||
|
EventIPLimiterTokensPerInterval int
|
||||||
|
EventIPLimiterInterval int
|
||||||
|
EventIPLimiterMaxTokens int
|
||||||
|
AllowEmptyFilters bool
|
||||||
|
AllowComplexFilters bool
|
||||||
|
ConnectionRateLimiterTokensPerInterval int
|
||||||
|
ConnectionRateLimiterInterval int
|
||||||
|
ConnectionRateLimiterMaxTokens int
|
||||||
|
}
|
||||||
|
|
||||||
|
func initRelayLimits() {
|
||||||
|
privateRelayLimits = PrivateRelayLimits{
|
||||||
|
EventIPLimiterTokensPerInterval: getEnvInt("PRIVATE_RELAY_EVENT_IP_LIMITER_TOKENS_PER_INTERVAL", 50),
|
||||||
|
EventIPLimiterInterval: getEnvInt("PRIVATE_RELAY_EVENT_IP_LIMITER_INTERVAL", 1),
|
||||||
|
EventIPLimiterMaxTokens: getEnvInt("PRIVATE_RELAY_EVENT_IP_LIMITER_MAX_TOKENS", 100),
|
||||||
|
AllowEmptyFilters: getEnvBool("PRIVATE_RELAY_ALLOW_EMPTY_FILTERS", true),
|
||||||
|
AllowComplexFilters: getEnvBool("PRIVATE_RELAY_ALLOW_COMPLEX_FILTERS", true),
|
||||||
|
ConnectionRateLimiterTokensPerInterval: getEnvInt("PRIVATE_RELAY_CONNECTION_RATE_LIMITER_TOKENS_PER_INTERVAL", 3),
|
||||||
|
ConnectionRateLimiterInterval: getEnvInt("PRIVATE_RELAY_CONNECTION_RATE_LIMITER_INTERVAL", 5),
|
||||||
|
ConnectionRateLimiterMaxTokens: getEnvInt("PRIVATE_RELAY_CONNECTION_RATE_LIMITER_MAX_TOKENS", 9),
|
||||||
|
}
|
||||||
|
|
||||||
|
chatRelayLimits = ChatRelayLimits{
|
||||||
|
EventIPLimiterTokensPerInterval: getEnvInt("CHAT_RELAY_EVENT_IP_LIMITER_TOKENS_PER_INTERVAL", 50),
|
||||||
|
EventIPLimiterInterval: getEnvInt("CHAT_RELAY_EVENT_IP_LIMITER_INTERVAL", 1),
|
||||||
|
EventIPLimiterMaxTokens: getEnvInt("CHAT_RELAY_EVENT_IP_LIMITER_MAX_TOKENS", 100),
|
||||||
|
AllowEmptyFilters: getEnvBool("CHAT_RELAY_ALLOW_EMPTY_FILTERS", false),
|
||||||
|
AllowComplexFilters: getEnvBool("CHAT_RELAY_ALLOW_COMPLEX_FILTERS", false),
|
||||||
|
ConnectionRateLimiterTokensPerInterval: getEnvInt("CHAT_RELAY_CONNECTION_RATE_LIMITER_TOKENS_PER_INTERVAL", 3),
|
||||||
|
ConnectionRateLimiterInterval: getEnvInt("CHAT_RELAY_CONNECTION_RATE_LIMITER_INTERVAL", 3),
|
||||||
|
ConnectionRateLimiterMaxTokens: getEnvInt("CHAT_RELAY_CONNECTION_RATE_LIMITER_MAX_TOKENS", 9),
|
||||||
|
}
|
||||||
|
|
||||||
|
inboxRelayLimits = InboxRelayLimits{
|
||||||
|
EventIPLimiterTokensPerInterval: getEnvInt("INBOX_RELAY_EVENT_IP_LIMITER_TOKENS_PER_INTERVAL", 10),
|
||||||
|
EventIPLimiterInterval: getEnvInt("INBOX_RELAY_EVENT_IP_LIMITER_INTERVAL", 1),
|
||||||
|
EventIPLimiterMaxTokens: getEnvInt("INBOX_RELAY_EVENT_IP_LIMITER_MAX_TOKENS", 20),
|
||||||
|
AllowEmptyFilters: getEnvBool("INBOX_RELAY_ALLOW_EMPTY_FILTERS", false),
|
||||||
|
AllowComplexFilters: getEnvBool("INBOX_RELAY_ALLOW_COMPLEX_FILTERS", false),
|
||||||
|
ConnectionRateLimiterTokensPerInterval: getEnvInt("INBOX_RELAY_CONNECTION_RATE_LIMITER_TOKENS_PER_INTERVAL", 3),
|
||||||
|
ConnectionRateLimiterInterval: getEnvInt("INBOX_RELAY_CONNECTION_RATE_LIMITER_INTERVAL", 1),
|
||||||
|
ConnectionRateLimiterMaxTokens: getEnvInt("INBOX_RELAY_CONNECTION_RATE_LIMITER_MAX_TOKENS", 9),
|
||||||
|
}
|
||||||
|
|
||||||
|
outboxRelayLimits = OutboxRelayLimits{
|
||||||
|
EventIPLimiterTokensPerInterval: getEnvInt("OUTBOX_RELAY_EVENT_IP_LIMITER_TOKENS_PER_INTERVAL", 10),
|
||||||
|
EventIPLimiterInterval: getEnvInt("OUTBOX_RELAY_EVENT_IP_LIMITER_INTERVAL", 60),
|
||||||
|
EventIPLimiterMaxTokens: getEnvInt("OUTBOX_RELAY_EVENT_IP_LIMITER_MAX_TOKENS", 100),
|
||||||
|
AllowEmptyFilters: getEnvBool("OUTBOX_RELAY_ALLOW_EMPTY_FILTERS", false),
|
||||||
|
AllowComplexFilters: getEnvBool("OUTBOX_RELAY_ALLOW_COMPLEX_FILTERS", false),
|
||||||
|
ConnectionRateLimiterTokensPerInterval: getEnvInt("OUTBOX_RELAY_CONNECTION_RATE_LIMITER_TOKENS_PER_INTERVAL", 3),
|
||||||
|
ConnectionRateLimiterInterval: getEnvInt("OUTBOX_RELAY_CONNECTION_RATE_LIMITER_INTERVAL", 1),
|
||||||
|
ConnectionRateLimiterMaxTokens: getEnvInt("OUTBOX_RELAY_CONNECTION_RATE_LIMITER_MAX_TOKENS", 9),
|
||||||
|
}
|
||||||
|
|
||||||
|
prettyPrintLimits("Private relay limits", privateRelayLimits)
|
||||||
|
prettyPrintLimits("Chat relay limits", chatRelayLimits)
|
||||||
|
prettyPrintLimits("Inbox relay limits", inboxRelayLimits)
|
||||||
|
prettyPrintLimits("Outbox relay limits", outboxRelayLimits)
|
||||||
|
}
|
||||||
|
|
||||||
|
func prettyPrintLimits(label string, value interface{}) {
|
||||||
|
b, _ := json.MarshalIndent(value, "", " ")
|
||||||
|
log.Printf("🚧 %s:\n%s\n", label, string(b))
|
||||||
|
}
|
||||||
101
main.go
@@ -7,6 +7,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
"github.com/fiatjaf/khatru"
|
"github.com/fiatjaf/khatru"
|
||||||
"github.com/nbd-wtf/go-nostr"
|
"github.com/nbd-wtf/go-nostr"
|
||||||
@@ -96,6 +97,31 @@ func makeNewRelay(relayType string) *khatru.Relay {
|
|||||||
return true, "auth-required: publishing this event requires authentication"
|
return true, "auth-required: publishing this event requires authentication"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
mux := privateRelay.Router()
|
||||||
|
static := http.FileServer(http.Dir("templates/static"))
|
||||||
|
|
||||||
|
mux.Handle("GET /static/", http.StripPrefix("/static/", static))
|
||||||
|
mux.Handle("GET /favicon.ico", http.StripPrefix("/", static))
|
||||||
|
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
tmpl := template.Must(template.ParseFiles("templates/index.html"))
|
||||||
|
data := struct {
|
||||||
|
RelayName string
|
||||||
|
RelayPubkey string
|
||||||
|
RelayDescription string
|
||||||
|
RelayURL string
|
||||||
|
}{
|
||||||
|
RelayName: config.PrivateRelayName,
|
||||||
|
RelayPubkey: nPubToPubkey(config.PrivateRelayNpub),
|
||||||
|
RelayDescription: config.PrivateRelayDescription,
|
||||||
|
RelayURL: "wss://" + config.RelayURL + "/outbox",
|
||||||
|
}
|
||||||
|
err := tmpl.Execute(w, data)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return privateRelay
|
return privateRelay
|
||||||
|
|
||||||
case "/chat":
|
case "/chat":
|
||||||
@@ -150,6 +176,31 @@ func makeNewRelay(relayType string) *khatru.Relay {
|
|||||||
return true, "only direct messages are allowed in this relay"
|
return true, "only direct messages are allowed in this relay"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
mux := chatRelay.Router()
|
||||||
|
static := http.FileServer(http.Dir("templates/static"))
|
||||||
|
|
||||||
|
mux.Handle("GET /static/", http.StripPrefix("/static/", static))
|
||||||
|
mux.Handle("GET /favicon.ico", http.StripPrefix("/", static))
|
||||||
|
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
tmpl := template.Must(template.ParseFiles("templates/index.html"))
|
||||||
|
data := struct {
|
||||||
|
RelayName string
|
||||||
|
RelayPubkey string
|
||||||
|
RelayDescription string
|
||||||
|
RelayURL string
|
||||||
|
}{
|
||||||
|
RelayName: config.ChatRelayName,
|
||||||
|
RelayPubkey: nPubToPubkey(config.ChatRelayNpub),
|
||||||
|
RelayDescription: config.ChatRelayDescription,
|
||||||
|
RelayURL: "wss://" + config.RelayURL + "/chat",
|
||||||
|
}
|
||||||
|
err := tmpl.Execute(w, data)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return chatRelay
|
return chatRelay
|
||||||
|
|
||||||
case "/inbox":
|
case "/inbox":
|
||||||
@@ -170,6 +221,31 @@ func makeNewRelay(relayType string) *khatru.Relay {
|
|||||||
return true, "you can only post notes if you've tagged the owner of this relay"
|
return true, "you can only post notes if you've tagged the owner of this relay"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
mux := inboxRelay.Router()
|
||||||
|
static := http.FileServer(http.Dir("templates/static"))
|
||||||
|
|
||||||
|
mux.Handle("GET /static/", http.StripPrefix("/static/", static))
|
||||||
|
mux.Handle("GET /favicon.ico", http.StripPrefix("/", static))
|
||||||
|
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
tmpl := template.Must(template.ParseFiles("templates/index.html"))
|
||||||
|
data := struct {
|
||||||
|
RelayName string
|
||||||
|
RelayPubkey string
|
||||||
|
RelayDescription string
|
||||||
|
RelayURL string
|
||||||
|
}{
|
||||||
|
RelayName: config.InboxRelayName,
|
||||||
|
RelayPubkey: nPubToPubkey(config.InboxRelayNpub),
|
||||||
|
RelayDescription: config.InboxRelayDescription,
|
||||||
|
RelayURL: "wss://" + config.RelayURL + "/inbox",
|
||||||
|
}
|
||||||
|
err := tmpl.Execute(w, data)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return inboxRelay
|
return inboxRelay
|
||||||
|
|
||||||
default: // default to outbox
|
default: // default to outbox
|
||||||
@@ -187,6 +263,31 @@ func makeNewRelay(relayType string) *khatru.Relay {
|
|||||||
return true, "only notes signed by the owner of this relay are allowed"
|
return true, "only notes signed by the owner of this relay are allowed"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
mux := outboxRelay.Router()
|
||||||
|
static := http.FileServer(http.Dir("templates/static"))
|
||||||
|
|
||||||
|
mux.Handle("GET /static/", http.StripPrefix("/static/", static))
|
||||||
|
mux.Handle("GET /favicon.ico", http.StripPrefix("/", static))
|
||||||
|
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
tmpl := template.Must(template.ParseFiles("templates/index.html"))
|
||||||
|
data := struct {
|
||||||
|
RelayName string
|
||||||
|
RelayPubkey string
|
||||||
|
RelayDescription string
|
||||||
|
RelayURL string
|
||||||
|
}{
|
||||||
|
RelayName: config.OutboxRelayName,
|
||||||
|
RelayPubkey: nPubToPubkey(config.OutboxRelayNpub),
|
||||||
|
RelayDescription: config.OutboxRelayDescription,
|
||||||
|
RelayURL: "wss://" + config.RelayURL + "/outbox",
|
||||||
|
}
|
||||||
|
err := tmpl.Execute(w, data)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return outboxRelay
|
return outboxRelay
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
64
templates/index.html
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta name="theme-color" content="#ffffff" />
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>{{.RelayName}}</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<style>
|
||||||
|
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap");
|
||||||
|
body {
|
||||||
|
font-family: "Inter", sans-serif;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="bg-gray-900 text-white">
|
||||||
|
<div class="flex-grow flex flex-col justify-center items-center px-4">
|
||||||
|
<!-- Container -->
|
||||||
|
<div class="text-center max-w-2xl">
|
||||||
|
<!-- Heading (Relay Name) -->
|
||||||
|
<h1 class="text-5xl md:text-6xl font-bold text-purple-400 mb-6">
|
||||||
|
{{.RelayName}}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<!-- Relay Description -->
|
||||||
|
<p class="text-xl md:text-2xl text-gray-300 mb-8">
|
||||||
|
{{.RelayDescription}}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Relay URL -->
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="text-lg md:text-xl text-purple-300 hover:text-purple-400 underline"
|
||||||
|
>
|
||||||
|
{{.RelayURL}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="text-center py-6 bg-gray-800 w-full">
|
||||||
|
<p class="text-lg text-gray-400">
|
||||||
|
Proudly powered by
|
||||||
|
<a
|
||||||
|
href="https://khatru.nostr.technology/"
|
||||||
|
target="_blank"
|
||||||
|
class="text-purple-300 hover:text-purple-400 underline"
|
||||||
|
>Khatru</a
|
||||||
|
>
|
||||||
|
|
|
||||||
|
<a
|
||||||
|
href="https://github.com/bitvora/haven"
|
||||||
|
target="_blank"
|
||||||
|
class="text-purple-300 hover:text-purple-400 underline"
|
||||||
|
>Haven Relay on Github</a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
templates/static/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
templates/static/android-chrome-256x256.png
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
BIN
templates/static/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
9
templates/static/browserconfig.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<browserconfig>
|
||||||
|
<msapplication>
|
||||||
|
<tile>
|
||||||
|
<square150x150logo src="/mstile-150x150.png"/>
|
||||||
|
<TileColor>#da532c</TileColor>
|
||||||
|
</tile>
|
||||||
|
</msapplication>
|
||||||
|
</browserconfig>
|
||||||
BIN
templates/static/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 846 B |
BIN
templates/static/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
templates/static/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
templates/static/mstile-150x150.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
15
templates/static/safari-pinned-tab.svg
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="274.000000pt" height="274.000000pt" viewBox="0 0 274.000000 274.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
<metadata>
|
||||||
|
Created by potrace 1.14, written by Peter Selinger 2001-2017
|
||||||
|
</metadata>
|
||||||
|
<g transform="translate(0.000000,274.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#000000" stroke="none">
|
||||||
|
<path d="M0 1365 l0 -1285 1370 0 1370 0 0 1285 0 1285 -1370 0 -1370 0 0
|
||||||
|
-1285z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 603 B |