api

package
v0.7.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 3, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package api hosts the relay-management HTTP handlers. authHelpers is the NIP-98 glue: it pulls a signed kind-27235 event out of the Authorization header, hashes the request body, hands both off to handlers.VerifyNIP98Event, and (for admin endpoints) checks that the authenticated pubkey matches the relay owner in relay_metadata.json.

Per NIP-98 §Encoding the auth header is `Authorization: Nostr <b64>` where <b64> is the base64-encoded JSON of the event.

NIP-86 (Relay Management API) dispatch lives here. The spec — see https://github.com/nostr-protocol/nips/blob/master/86.md — wraps a JSON-RPC-style envelope around a single HTTP endpoint:

POST / HTTP/1.1
Content-Type: application/nostr+json+rpc
Authorization: Nostr <base64-encoded kind-27235 event>

{"method": "<name>", "params": [...]}

The response is `{"result": <method-specific>, "error": "<string>"}`, where `error` is empty on success. Every method requires NIP-98 auth and (in grain's implementation) signer == relay owner per relay_metadata.json — RequireOwner is the gate.

This file ships read-only methods. Mutation methods (banpubkey / allowpubkey / changerelay* / etc.) will land in a follow-up commit once the on-disk write path is in place; `supportedmethods` advertises only what's actually wired so well-behaved clients can feature-detect.

Index

Constants

View Source
const NIP86ContentType = "application/nostr+json+rpc"

NIP86ContentType is the media type NIP-86 reserves. The root dispatcher in server/startup.go uses this string to route requests here ahead of the WebSocket upgrade check.

Variables

View Source
var ErrMissingAuthHeader = errors.New("missing Authorization header")

ErrMissingAuthHeader is returned when the Authorization header is not present at all. Callers convert this to a 401 with a WWW-Authenticate prompt so clients know to retry with credentials.

Functions

func ExtractNIP98Event added in v0.7.0

func ExtractNIP98Event(r *http.Request) (nostr.Event, error)

ExtractNIP98Event pulls the signed event out of an HTTP request's Authorization header. Returns ErrMissingAuthHeader if the header is absent so callers can distinguish "no creds" (401, send challenge) from "bad creds" (401, but no point re-prompting with the same scheme).

func FetchAdminMutelist added in v0.7.0

func FetchAdminMutelist(w http.ResponseWriter, r *http.Request)

FetchAdminMutelist returns the relay owner's latest kind:10000 + kind:30000(d:"mute") events so the browser can decrypt their private `.content` (#60). The relay does the relay-fetching (outbox resolution + subscription) it already does for public mutelist authors; only decryption has to happen browser-side, where the owner's key lives. GET, owner-gated.

@Summary Fetch owner's mute list events @Description Returns the relay owner's raw NIP-51 mute list events (incl. encrypted content) for browser-side decryption. Owner-only via NIP-98. @Tags relay-admin @Produce json @Success 200 {object} fetchAdminMutelistResponse @Failure 401 {string} string "Unauthorized" @Failure 403 {string} string "Forbidden: signer is not relay owner" @Router /api/v1/relay/admin/mutelist/fetch [get]

func GetAllBlacklistedPubkeys

func GetAllBlacklistedPubkeys(w http.ResponseWriter, r *http.Request)

GetAllBlacklistedPubkeys handles the request to return all blacklisted pubkeys organized by source

@Summary List blacklisted pubkeys (cached) @Description Returns permanent bans (from config), live temporary bans (with expiry timestamps), and the grouped per-author mutelist from the in-process cache. @Tags relay-keys @Produce json @Success 200 {object} BlacklistKeysResponse @Failure 500 {string} string "Blacklist configuration not available" @Router /api/v1/relay/keys/blacklist [get]

func GetAllBlacklistedPubkeysLive

func GetAllBlacklistedPubkeysLive(w http.ResponseWriter, r *http.Request)

GetAllBlacklistedPubkeysLive handles the request to return all blacklisted pubkeys with live mutelist fetching

@Summary List blacklisted pubkeys (live) @Description Same shape as the cached endpoint but bypasses the mutelist cache to fetch each author's mutelist from their outbox relays. Slower but always fresh. @Tags relay-keys @Produce json @Success 200 {object} BlacklistKeysResponse @Failure 500 {string} string "Blacklist configuration not available" @Router /api/v1/relay/keys/blacklist/live [get]

func GetAllWhitelistedPubkeys

func GetAllWhitelistedPubkeys(w http.ResponseWriter, r *http.Request)

GetAllWhitelistedPubkeys handles the cached request to return all whitelisted pubkeys organized by source

@Summary List whitelisted pubkeys (cached) @Description Returns the configured whitelist registry (direct pubkeys + npubs converted) and a per-domain breakdown from the in-process cache. This is the registry, not the enforcement gate — values are returned regardless of whether the whitelist is currently enabled. See `/api/v1/relay/keys/whitelist/live` for a fresh fetch. @Tags relay-keys @Produce json @Success 200 {object} WhitelistKeysResponse @Failure 500 {string} string "Whitelist configuration not available" @Router /api/v1/relay/keys/whitelist [get]

func GetAllWhitelistedPubkeysLive

func GetAllWhitelistedPubkeysLive(w http.ResponseWriter, r *http.Request)

GetAllWhitelistedPubkeysLive handles the request to return all whitelisted pubkeys with live domain fetching This endpoint fetches fresh data from domains and is suitable for verification after configuration changes

@Summary List whitelisted pubkeys (live) @Description Same shape as the cached endpoint but fetches every `.well-known/nostr.json` referenced by `domain_whitelist` on demand. Useful after a config change when you want to verify domain resolution without waiting for the background refresh. @Tags relay-keys @Produce json @Success 200 {object} WhitelistKeysResponse @Failure 500 {string} string "Whitelist configuration not available" @Router /api/v1/relay/keys/whitelist/live [get]

func GetAuthConfig

func GetAuthConfig(w http.ResponseWriter, r *http.Request)

GetAuthConfig handles the request to return authentication configuration

@Summary Get auth config @Description Returns whether NIP-42 AUTH is required on websocket connections and the relay URL clients must echo back in their AUTH events. @Tags relay-config @Produce json @Success 200 {object} AuthConfigResponse @Failure 500 {string} string "Server configuration not available" @Router /api/v1/relay/config/auth [get]

func GetBackupRelayConfig

func GetBackupRelayConfig(w http.ResponseWriter, r *http.Request)

GetBackupRelayConfig handles the request to return backup relay configuration

@Summary Get backup relay config @Description Returns the upstream relay grain mirrors accepted events to (URL plus enabled flag). @Tags relay-config @Produce json @Success 200 {object} BackupRelayConfigResponse @Failure 500 {string} string "Server configuration not available" @Router /api/v1/relay/config/backup_relay [get]

func GetBlacklistConfig

func GetBlacklistConfig(w http.ResponseWriter, r *http.Request)

GetBlacklistConfig handles the request to return blacklist configuration

@Summary Get blacklist config @Description Returns the full blacklist.yml — permanent and temporary ban words, blacklisted pubkeys/npubs, and the mutelist authors whose mutelists grain consumes. @Tags relay-config @Produce json @Success 200 {object} BlacklistConfigResponse @Failure 500 {string} string "Blacklist configuration not available" @Router /api/v1/relay/config/blacklist [get]

func GetEventPurgeConfig

func GetEventPurgeConfig(w http.ResponseWriter, r *http.Request)

GetEventPurgeConfig handles the request to return event purging configuration

@Summary Get event-purge config @Description Returns retention settings — what's purged, by kind/category, on what interval, and whether whitelisted authors are exempt. @Tags relay-config @Produce json @Success 200 {object} EventPurgeConfigResponse @Failure 500 {string} string "Server configuration not available" @Router /api/v1/relay/config/event_purge [get]

func GetEventTimeConstraintsConfig

func GetEventTimeConstraintsConfig(w http.ResponseWriter, r *http.Request)

GetEventTimeConstraintsConfig handles the request to return event time constraints configuration

@Summary Get event-time-constraints config @Description Returns the accepted window for event `created_at` (min/max as Unix timestamps and human-readable strings). @Tags relay-config @Produce json @Success 200 {object} EventTimeConstraintsConfigResponse @Failure 500 {string} string "Server configuration not available" @Router /api/v1/relay/config/event_time_constraints [get]

func GetLoggingConfig

func GetLoggingConfig(w http.ResponseWriter, r *http.Request)

GetLoggingConfig handles the request to return logging configuration

@Summary Get logging config @Description Returns log level, file path, rotation settings, and per-component suppressions. @Tags relay-config @Produce json @Success 200 {object} LoggingConfigResponse @Failure 500 {string} string "Server configuration not available" @Router /api/v1/relay/config/logging [get]

func GetRateLimitConfig

func GetRateLimitConfig(w http.ResponseWriter, r *http.Request)

GetRateLimitConfig handles the request to return rate limiting configuration

@Summary Get rate limit config @Description Returns the rate-limit settings (ws/event/req limits and bursts, kind/category overrides, max event size). @Tags relay-config @Produce json @Success 200 {object} RateLimitConfigResponse @Failure 500 {string} string "Server configuration not available" @Router /api/v1/relay/config/rate_limit [get]

func GetResourceLimitsConfig

func GetResourceLimitsConfig(w http.ResponseWriter, r *http.Request)

GetResourceLimitsConfig handles the request to return resource limits configuration

@Summary Get resource limits config @Description Returns the OS resource caps the relay self-imposes — CPU cores, memory ceiling, Go heap target. @Tags relay-config @Produce json @Success 200 {object} ResourceLimitsConfigResponse @Failure 500 {string} string "Server configuration not available" @Router /api/v1/relay/config/resource_limits [get]

func GetServerConfig

func GetServerConfig(w http.ResponseWriter, r *http.Request)

GetServerConfig handles the request to return server configuration

@Summary Get server config @Description Returns the HTTP server settings (timeouts, connection caps, subscription caps). @Tags relay-config @Produce json @Success 200 {object} ServerConfigResponse @Failure 500 {string} string "Server configuration not available" @Router /api/v1/relay/config/server [get]

func GetWhitelistConfig

func GetWhitelistConfig(w http.ResponseWriter, r *http.Request)

GetWhitelistConfig handles the request to return whitelist configuration

@Summary Get whitelist config @Description Returns the full whitelist.yml — pubkey, kind, and domain whitelists with their enabled flags. The configured set is grain's "elevated users" registry; enforcement only happens when `enabled` is true. @Tags relay-config @Produce json @Success 200 {object} WhitelistConfigResponse @Failure 500 {string} string "Whitelist configuration not available" @Router /api/v1/relay/config/whitelist [get]

func HandleNIP86 added in v0.7.0

func HandleNIP86(w http.ResponseWriter, r *http.Request)

HandleNIP86 is the JSON-RPC entry point. All paths return HTTP 200 with a JSON body — method errors live in the response envelope, not the HTTP status. Auth failures (no header, bad signature, non-owner) short-circuit via RequireOwner before any JSON-RPC parsing happens and DO use the right status codes (401 / 403) because they're not JSON-RPC errors — they're HTTP-level access control.

@Summary NIP-86 relay management @Description JSON-RPC over a single POST endpoint per [NIP-86](https://github.com/nostr-protocol/nips/blob/master/86.md). Requires NIP-98 HTTP Auth (kind:27235 with `u`, `method`, `payload` tags) and the signer must equal the relay owner pubkey in `relay_metadata.json`. Body is `{"method": "<name>", "params": [...]}`; response is `{"result": ..., "error": ""}`. Errors live in the envelope, not the HTTP status — only auth failures return 401/403. @Description @Description **Spec methods (reads):** `supportedmethods`, `listallowedpubkeys`, `listbannedpubkeys`, `listallowedkinds`, `listblockedips`. @Description @Description **Spec methods (writes):** `banpubkey` / `unbanpubkey` / `allowpubkey` / `unallowpubkey` (params: `[pubkey, reason?]`), `allowkind` / `disallowkind` (params: `[kind:int]`), `blockip` / `unblockip` (params: `[ip-or-cidr, reason?]`), `changerelayname` / `changerelaydescription` / `changerelayicon` (params: `[value:string]`). @Description @Description **Grain vendor extensions (writes):** `grain_updateserver`, `grain_updateratelimit`, `grain_updateeventpurge`, `grain_updatelogging`, `grain_updateauth`, `grain_updatebackuprelay`, `grain_updateresourcelimits`, `grain_updateeventtimeconstraints`, `grain_updatewhitelistconfig`, `grain_updateblacklistconfig`. Each takes the full section blob as `params[0]` (same shape the matching GET endpoint returns) and stages it to disk; the response is `{ok:true, restart_pending:true}`. Operator clicks Apply → dashboard calls `grain_reloadconfig`. @Description @Description **Grain vendor extensions (ops + reads):** `grain_reloadconfig` (triggers restart), `grain_refreshcache` (synchronous whitelist + blacklist cache refresh), `grain_whitelistconfig` / `grain_blacklistconfig` (full-struct reads — the blacklist read overlays IP fields from config.yml so the dashboard sees one coherent shape), `grain_stats_overview` (server counters + list/cache stats). @Description @Description **Out of scope:** event-moderation methods (`allowevent` / `banevent` / `listbannedevents` / `listeventsneedingmoderation`) need a moderation queue that doesn't exist yet — tracked separately. Call `supportedmethods` at runtime for the authoritative list this build advertises. @Tags nip86 @Accept json @Produce json @Param body body nip86Request true "JSON-RPC envelope" @Success 200 {object} nip86Response @Failure 401 {string} string "Unauthorized — missing/bad NIP-98 header" @Failure 403 {string} string "Forbidden — signer is not relay owner" @Security NostrAuth @Router / [post]

func IsRelayOwner added in v0.7.0

func IsRelayOwner(pubkey string) bool

IsRelayOwner reports whether the given pubkey is the relay owner recorded in relay_metadata.json. The comparison is case-insensitive because some clients/tools normalize hex differently; the on-disk metadata is the source of truth.

func RequireOwner added in v0.7.0

func RequireOwner(w http.ResponseWriter, r *http.Request) (string, bool)

RequireOwner is the gate for admin/management endpoints: it authenticates the request via NIP-98 and confirms the signer is the relay owner. On failure it writes the appropriate response and returns ok=false so the handler can simply early-return.

func SetServerStatsHook added in v0.7.0

func SetServerStatsHook(fn func() ServerStats)

SetServerStatsHook is wired from server/startup.go once the counters are available. The hook is called per grain_stats_overview request — should be cheap (atomic loads).

func SyncAdminMutelist added in v0.7.0

func SyncAdminMutelist(w http.ResponseWriter, r *http.Request)

SyncAdminMutelist accepts the relay owner's decrypted private mute list and folds it into the blacklist (#60).

The relay can't decrypt NIP-51 `.content` — it has no private key — so decryption happens in the owner's browser and only the resulting plain pubkey list reaches this endpoint. POST-only, gated by RequireOwner (NIP-98 + relay-owner check). An empty pubkey list clears the owner's contribution (un-sync).

@Summary Sync owner's private mute list @Description Stores the relay owner's decrypted NIP-51 private mute pubkeys as a blacklist source. Owner-only via NIP-98. @Tags relay-admin @Accept json @Produce json @Param body body syncAdminMutelistRequest true "Decrypted pubkey set" @Success 200 {object} syncAdminMutelistResponse @Failure 401 {string} string "Unauthorized" @Failure 403 {string} string "Forbidden: signer is not relay owner" @Router /api/v1/relay/admin/mutelist/sync [post]

func VerifyAPIAuth added in v0.7.0

func VerifyAPIAuth(r *http.Request) (string, error)

VerifyAPIAuth verifies the NIP-98 Authorization header on r and returns the authenticated pubkey. It reads the request body (if any) to compute the payload hash that NIP-98 requires; the body is restored on r.Body so downstream handlers can read it normally.

Callers that need owner-only access should use RequireOwner, which wraps this with a relay_metadata.json owner check.

Types

type AuthConfigResponse

type AuthConfigResponse struct {
	Required bool   `json:"required"`
	RelayURL string `json:"relay_url"`
}

AuthConfigResponse represents the authentication configuration response

type BackupRelayConfigResponse

type BackupRelayConfigResponse struct {
	Enabled bool     `json:"enabled"`
	URLs    []string `json:"urls"`
}

BackupRelayConfigResponse represents the backup relay configuration response

type BlacklistConfigResponse

type BlacklistConfigResponse struct {
	Enabled                     bool     `json:"enabled"`
	PermanentBanWords           []string `json:"permanent_ban_words"`
	TempBanWords                []string `json:"temp_ban_words"`
	MaxTempBans                 int      `json:"max_temp_bans"`
	TempBanDuration             int      `json:"temp_ban_duration"`
	PermanentBlacklistPubkeys   []string `json:"permanent_blacklist_pubkeys"`
	PermanentBlacklistNpubs     []string `json:"permanent_blacklist_npubs"`
	MuteListAuthors             []string `json:"mutelist_authors"`
	MutelistCacheRefreshMinutes int      `json:"mutelist_cache_refresh_minutes"`
}

BlacklistConfigResponse represents the blacklist configuration response

type BlacklistKeysResponse

type BlacklistKeysResponse struct {
	Permanent          []string                 `json:"permanent"`
	Temporary          []map[string]interface{} `json:"temporary"`
	Mutelist           map[string][]string      `json:"mutelist"`
	AdminMutelistCount int                      `json:"admin_mutelist_count"`
}

BlacklistKeysResponse represents the blacklist keys response.

AdminMutelistCount is a privacy-preserving COUNT, never the pubkeys: the relay owner's private mute list (#60) is decrypted from their own NIP-51 content, and this endpoint is public + rendered on the public home page. Listing those pubkeys would leak who the owner privately muted, so we only surface how many were folded into the blacklist.

type EventPurgeConfigResponse

type EventPurgeConfigResponse struct {
	Enabled              bool            `json:"enabled"`
	DisableAtStartup     bool            `json:"disable_at_startup"`
	KeepIntervalHours    int             `json:"keep_interval_hours"`
	PurgeIntervalMinutes int             `json:"purge_interval_minutes"`
	PurgeByCategory      map[string]bool `json:"purge_by_category"`
	PurgeByKindEnabled   bool            `json:"purge_by_kind_enabled"`
	KindsToPurge         []int           `json:"kinds_to_purge"`
	ExcludeWhitelisted   bool            `json:"exclude_whitelisted"`
}

EventPurgeConfigResponse represents the event purging configuration response

type EventTimeConstraintsConfigResponse

type EventTimeConstraintsConfigResponse struct {
	MinCreatedAt       int64  `json:"min_created_at"`
	MinCreatedAtString string `json:"min_created_at_string"`
	MaxCreatedAt       int64  `json:"max_created_at"`
	MaxCreatedAtString string `json:"max_created_at_string"`
}

EventTimeConstraintsConfigResponse represents the event time constraints configuration response

type LoggingConfigResponse

type LoggingConfigResponse struct {
	Level              string   `json:"level"`
	File               string   `json:"file"`
	MaxSizeMB          int      `json:"max_log_size_mb"`
	Structure          bool     `json:"structure"`
	CheckIntervalMin   int      `json:"check_interval_min"`
	BackupCount        int      `json:"backup_count"`
	SuppressComponents []string `json:"suppress_components"`
}

LoggingConfigResponse represents the logging configuration response

type RateLimitConfigResponse

type RateLimitConfigResponse struct {
	WsLimit        float64                            `json:"ws_limit"`
	WsBurst        int                                `json:"ws_burst"`
	EventLimit     float64                            `json:"event_limit"`
	EventBurst     int                                `json:"event_burst"`
	ReqLimit       float64                            `json:"req_limit"`
	ReqBurst       int                                `json:"req_burst"`
	MaxEventSize   int                                `json:"max_event_size"`
	KindSizeLimits []cfgType.KindSizeLimitConfig      `json:"kind_size_limits"`
	CategoryLimits map[string]cfgType.KindLimitConfig `json:"category_limits"`
	KindLimits     []cfgType.KindLimitConfig          `json:"kind_limits"`
}

RateLimitConfigResponse represents the rate limiting configuration response

type ResourceLimitsConfigResponse

type ResourceLimitsConfigResponse struct {
	CPUCores   int `json:"cpu_cores"`
	MemoryMB   int `json:"memory_mb"`
	HeapSizeMB int `json:"heap_size_mb"`
}

ResourceLimitsConfigResponse represents the resource limits configuration response

type ServerConfigResponse

type ServerConfigResponse struct {
	ReadTimeout               int `json:"read_timeout"`
	WriteTimeout              int `json:"write_timeout"`
	IdleTimeout               int `json:"idle_timeout"`
	MaxConnections            int `json:"max_connections"`
	MaxSubscriptionsPerClient int `json:"max_subscriptions_per_client"`
	ImplicitReqLimit          int `json:"implicit_req_limit"`
}

ServerConfigResponse represents the server configuration response

type ServerStats added in v0.7.0

type ServerStats struct {
	ActiveConnections int64  `json:"active_connections"`
	TotalMessagesSent int64  `json:"total_messages_sent"`
	UptimeSeconds     int64  `json:"uptime_seconds"`
	Version           string `json:"version"`
	BuildTime         string `json:"build_time"`
	GitCommit         string `json:"git_commit"`
}

ServerStats is the slice of stats only the server package can produce. Kept small on purpose — every field added here has to thread through the hook.

type WhitelistConfigResponse

type WhitelistConfigResponse struct {
	PubkeyWhitelist struct {
		Enabled             bool     `json:"enabled"`
		Pubkeys             []string `json:"pubkeys"`
		Npubs               []string `json:"npubs"`
		CacheRefreshMinutes int      `json:"cache_refresh_minutes"`
	} `json:"pubkey_whitelist"`
	KindWhitelist struct {
		Enabled bool     `json:"enabled"`
		Kinds   []string `json:"kinds"`
	} `json:"kind_whitelist"`
	DomainWhitelist struct {
		Enabled             bool     `json:"enabled"`
		Domains             []string `json:"domains"`
		CacheRefreshMinutes int      `json:"cache_refresh_minutes"`
	} `json:"domain_whitelist"`
}

WhitelistConfigResponse represents the whitelist configuration response

type WhitelistDomainInfo

type WhitelistDomainInfo struct {
	Domain  string   `json:"domain"`
	Pubkeys []string `json:"pubkeys"`
}

WhitelistDomainInfo represents domain information with its pubkeys

type WhitelistKeysResponse

type WhitelistKeysResponse struct {
	List    []string              `json:"list"`
	Domains []WhitelistDomainInfo `json:"domains"`
}

WhitelistKeysResponse represents the whitelist keys response

Directories

Path Synopsis
Package docs serves grain's OpenAPI specification.
Package docs serves grain's OpenAPI specification.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL