clone

a self-hosted multitenant nostr relay with sites, file storage, git hosting, and inbox and outbox delivery

feat/ui-updates
tinyrelay / tinyclient / private.go
private.go 2.6K
1package tinyclient23import (4 "context"5 "errors"6 "net/http"7 "strings"89 "github.com/FelineStateMachine/tinyrelay/internal/policy"10)1112// PrivateReader is implemented by the daemon so web pages use the same13// membership check as websocket and HTTP data paths. It is optional to keep14// webui usable with small embedders; those embedders get owner-only fallback.15type PrivateReader interface {16 ReadAllowed(context.Context, string) error17}1819func privatePolicyEnabled(p policy.Policy) bool { return p.Features.Grasp08 }2021func privateShellPath(path string) bool {22 if path == "/" || path == "/signin" || path == "/account" || path == "/manage/connect" {23 return true24 }25 if path == "/manage/rpc" || path == "/manage/jobs/status" {26 return false27 }28 return strings.HasPrefix(path, "/manage/") && !strings.HasSuffix(path, ".json")29}3031func privateProtectedPath(path string) bool {32 if privateShellPath(path) || path == "/webmcp.js" || path == "/signer.js" || path == "/fixi.js" || path == "/qr.svg" || path == "/sw.js" || path == "/manifest.webmanifest" || path == "/icon.svg" || path == "/icon-mono.svg" {33 return false34 }35 return true36}3738func (a *App) privateReadAllowed(request *http.Request, actor string) error {39 if actor == "" {40 return errors.New("auth-required: private relay membership required")41 }42 if reader, ok := a.backend.(PrivateReader); ok {43 return reader.ReadAllowed(request.Context(), actor)44 }45 if actor != a.backend.Policy().Owner {46 return errors.New("restricted: private relay membership required")47 }48 return nil49}5051func (a *App) denyPrivateEndpoint(writer http.ResponseWriter, request *http.Request) bool {52 if !privatePolicyEnabled(a.backend.Policy()) || !privateProtectedPath(request.URL.Path) {53 return false54 }55 actor, err := a.resolveActor(request)56 if err != nil || actor == "" {57 writer.Header().Set("Cache-Control", "private, no-store")58 http.Error(writer, "authentication required", http.StatusUnauthorized)59 return true60 }61 if err := a.privateReadAllowed(request, actor); err != nil {62 writer.Header().Set("Cache-Control", "private, no-store")63 http.Error(writer, err.Error(), http.StatusForbidden)64 return true65 }66 return false67}6869func (a *App) privatePageData(data *PageData, request *http.Request, actor string) {70 if !privatePolicyEnabled(data.Policy) || !privateShellPath(request.URL.Path) {71 return72 }73 if err := a.privateReadAllowed(request, actor); err == nil {74 return75 }76 data.Private = true77 data.Title = "Private relay"78 data.Slug = "private relay"79 data.URL = ""80 data.Identity = ""81 data.Policy = policy.Policy{}82 data.Actor = ""83 data.Owner = false84 data.Event = nil85 data.Feed = nil86 data.Methods = nil87}