clone

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

feat/tinyagent
tinyrelay / internal / daemon / private_grasp.go
private_grasp.go 4.1K
1package daemon23import (4 "context"5 "errors"6 "net/http"78 "github.com/FelineStateMachine/tinyrelay/internal/event"9 "github.com/FelineStateMachine/tinyrelay/internal/policy"10)1112type privateGitProofKey struct{}13type privateGitPayloadCheckedKey struct{}1415func privateGitRequest(r *http.Request, proof event.Event) *http.Request {16 ctx := context.WithValue(r.Context(), privateGitProofKey{}, proof)17 ctx = context.WithValue(ctx, privateGitPayloadCheckedKey{}, struct{}{})18 return r.WithContext(ctx)19}2021func privateGitProof(r *http.Request) (event.Event, bool) {22 proof, ok := r.Context().Value(privateGitProofKey{}).(event.Event)23 return proof, ok24}2526// PrivateServiceProfile describes a GRASP-08 tenant's configured protocol,27// authentication methods, read policy and owner identity. Protocol adapters28// choose the fields appropriate to the request's authenticated audience.29type PrivateServiceProfile struct {30 Enabled bool31 Protocol string32 Authentication []string33 Reads string34 Owner string35}3637// PrivateAccess records tenant-level authority resolved from current community38// membership. Owner, member and moderator roles grant private-service access.39type PrivateAccess struct {40 PubKey string41 Role string42 Member bool43 Owner bool44}4546func (a PrivateAccess) Allowed() bool { return a.Member || a.Owner }4748// PrivateServiceEnabled reports whether this tenant has the complete private49// service contract enabled. Callers should use PrivateAccess before exposing50// any tenant or repository metadata.51func (t *Tenant) PrivateServiceEnabled() bool {52 return t != nil && t.Policy().PrivateServiceEnabled()53}5455func (t *Tenant) PrivateProfile() PrivateServiceProfile {56 if t == nil {57 return PrivateServiceProfile{}58 }59 p := t.Policy()60 return PrivateServiceProfile{61 Enabled: p.PrivateServiceEnabled(),62 Protocol: "GRASP-08",63 Authentication: []string{"NIP-42", "NIP-98"},64 Reads: p.Reads,65 Owner: p.Owner,66 }67}6869// PrivateAccess reads current membership and bans for each request, so access70// changes apply to the next authorization check.71func (t *Tenant) PrivateAccess(ctx context.Context, pubkey string) (PrivateAccess, error) {72 access := PrivateAccess{PubKey: pubkey}73 if t == nil || !t.PrivateServiceEnabled() {74 return access, nil75 }76 if pubkey == "" {77 return access, errors.New("auth-required: private service requires NIP-42 or NIP-98")78 }79 banned, err := t.community.IsBanned(ctx, pubkey)80 if err != nil {81 return access, err82 }83 if banned {84 return access, errors.New("blocked: private service identity is banned")85 }86 role, err := t.community.Role(ctx, pubkey)87 if err != nil {88 return access, err89 }90 access.Role = role91 access.Owner = role == "owner" || pubkey == t.Policy().Owner92 access.Member = role == "member" || role == "moderator"93 if !access.Allowed() {94 return access, errors.New("restricted: private service membership required")95 }96 return access, nil97}9899func (t *Tenant) requirePrivateAccess(ctx context.Context, pubkey string) error {100 _, err := t.PrivateAccess(ctx, pubkey)101 return err102}103104// AuthorizePrivatePeer is the narrow boundary for outbound Git/event sync.105// Callers must invoke it immediately before using a private peer credential;106// a previously successful check must never be reused after membership changes.107func (t *Tenant) AuthorizePrivatePeer(ctx context.Context, pubkey string) error {108 if !t.PrivateServiceEnabled() {109 return errors.New("restricted: private peer access is disabled")110 }111 return t.requirePrivateAccess(ctx, pubkey)112}113114// privateNIP11 returns the metadata safe to expose before authentication.115// The advertised owner key is the service identity needed by NIP-42 clients;116// tenant-specific descriptive fields remain intentionally omitted.117func (t *Tenant) privateNIP11() map[string]any {118 profile := t.PrivateProfile()119 return map[string]any{120 "name": "private relay",121 "description": "authenticated private relay",122 "pubkey": profile.Owner,123 "supported_grasps": []string{"GRASP-01", "GRASP-08"},124 "private_service": true,125 }126}127128func privatePolicy(p policy.Policy) bool {129 return p.PrivateServiceEnabled()130}