- commit
- fbc0c60c00d62e508d148d64cc100102a8b3c004
- parent
- ee88d2a06e952638cbb3f479ed06d9bb94d58d2e
- author
- Dami <dami.etoile@gmail.com>
| M | internal/ | 98 | +++++++++++++++++++++++++++--- |
@@ -1,12 +1,15 @@
11 package daemon22 33 import (4+ "bytes"45 "context"56 "encoding/json"7+ "errors"68 "io"79 "net/http"810 "net/url"911 "strings"12+ "time"1013 1114 "github.com/FelineStateMachine/tinyrelay/internal/blob"1215 "github.com/FelineStateMachine/tinyrelay/internal/configport"@@ -27,18 +30,38 @@ func (t *Tenant) initServices(ctx context.Context) error {
2730 var err error2831 t.blobs, err = blob.New(ctx, blob.Config{2932 Root: t.meta.Paths.Root, Store: t.store, Authorize: t.authorizeBlob,30- CanRead: func(ctx context.Context, pubkey string, hashes []string) bool {33+ CanRead: func(ctx context.Context, hash string, pubkeys []string) bool {3134 p := t.Policy()3235 if p.Reads == "open" {3336 return true3437 }35- role, err := t.community.Role(ctx, pubkey)36- return err == nil && role != ""38+ for _, pubkey := range pubkeys {39+ if p.Reads == "auth" {40+ return true41+ }42+ role, err := t.community.Role(ctx, pubkey)43+ if err == nil && role != "" {44+ return true45+ }46+ }47+ return false3748 }, IsOwner: func(pubkey string) bool { return pubkey == t.Policy().Owner },3849 })3950 if err != nil {4051 return err4152 }53+ t.community.ConfigurePolicy(t.Policy)54+ t.community.OnMembershipApplied(func(event.Event) {55+ go func() {56+ generated, err := t.records.PublishMembership(context.Background(), time.Now().Unix())57+ if err != nil {58+ return59+ }60+ for _, record := range generated {61+ _ = t.generatedRecord(context.Background(), record)62+ }63+ }()64+ })4265 t.sites, err = sites.New(sites.Config{4366 Store: t.store,4467 GetBlob: func(ctx context.Context, hash string) (sites.Blob, error) {@@ -62,10 +85,10 @@ func (t *Tenant) initServices(ctx context.Context) error {
6285 }6386 t.config = configport.New(configport.ConfigStore{Store: t.store, Community: t.community, Policy: t.Policy, OnApplied: func(p policy.Policy) { t.replacePolicy(p) }})6487 transport := &replication.NostrTransport{Dialer: replication.WebsocketDialer{}}65- t.replication, err = replication.NewService(replication.Config{Store: t.store, Policy: replication.Policy{Enabled: t.Policy().Delivery.Enabled, SelfPubKey: t.Policy().Owner}, CurrentPolicy: func() replication.Policy {88+ t.replication, err = replication.NewService(replication.Config{Store: t.store, Policy: replication.Policy{Enabled: t.Policy().Delivery.Enabled, SelfPubKey: t.records.PublicKey()}, CurrentPolicy: func() replication.Policy {6689 p := t.Policy()67- return replication.Policy{Enabled: p.Delivery.Enabled, SelfPubKey: p.Owner}68- }, Delivery: transport, Pull: transport, Push: transport, DataDir: t.meta.Paths.Root, Ingest: t.ingest})90+ return replication.Policy{Enabled: p.Delivery.Enabled, ReadMembersOnly: p.Reads != "open", SelfPubKey: t.records.PublicKey()}91+ }, Directory: localDirectory{store: t.store}, Delivery: transport, Pull: transport, Push: transport, DataDir: t.meta.Paths.Root, Ingest: t.ingest})6992 if err != nil {7093 return err7194 }@@ -86,6 +109,25 @@ func (t *Tenant) initServices(ctx context.Context) error {
86109 // backend adapts the management method signature to webui's form-oriented API.87110 type backend struct{ tenant *Tenant }88111 112+type localDirectory struct{ store *storage.Store }113+114+func (d localDirectory) WriteRelays(pubkey string) []string { return d.relays(pubkey, true) }115+func (d localDirectory) ReadRelays(pubkey string) []string { return d.relays(pubkey, false) }116+func (d localDirectory) relays(pubkey string, write bool) []string {117+ rows, err := d.store.Query(context.Background(), event.Filter{Authors: []string{pubkey}, Kinds: []int{10002}, Tags: map[string][]string{}}, storage.QueryOptions{Now: time.Now().Unix(), Access: storage.Access{All: true}, Limit: 1})118+ if err != nil || len(rows.Events) == 0 {119+ return nil120+ }121+ result := make([]string, 0)122+ for _, tag := range rows.Events[0].Tags {123+ if len(tag) < 2 || tag[0] != "r" || (!write && len(tag) > 2 && tag[2] != "read") || (write && len(tag) > 2 && tag[2] == "read") {124+ continue125+ }126+ result = append(result, tag[1])127+ }128+ return result129+}130+89131 func (b backend) Query(ctx context.Context, method string, params []json.RawMessage, actor string) (any, error) {90132 return b.tenant.Execute(ctx, actor, method, params)91133 }@@ -121,11 +163,42 @@ func (t *Tenant) siteReadAccess(r *http.Request) bool {
121163 }122164 func (t *Tenant) authorizeBlob(r *http.Request, action blob.Action) (string, error) {123165 e, err := t.auth.WhoAsks(r.Header.Get("Authorization"), t.publicURL+r.URL.RequestURI(), r.Method, "", string(action), strings.TrimPrefix(r.URL.Path, "/"))124- return first(e), err166+ pubkey := first(e)167+ if err != nil {168+ return "", err169+ }170+ if pubkey == "" {171+ return "", errors.New("auth-required: blob authorization required")172+ }173+ if action == blob.ActionUpload || action == blob.ActionMirror {174+ if !t.Policy().Features.Files {175+ return "", errors.New("restricted: file uploads are disabled")176+ }177+ role, roleErr := t.community.Role(r.Context(), pubkey)178+ if roleErr != nil || (t.Policy().Writes != "open" && role == "") {179+ return "", errors.New("restricted: file upload is not allowed")180+ }181+ }182+ return pubkey, nil125183 }126184 func (t *Tenant) authorizeGit(ctx context.Context, r *http.Request, repo gitrelay.Repository) error {127- _, err := t.auth.VerifyNIP98(r.Header.Get("Authorization"), t.publicURL+r.URL.RequestURI(), r.Method, "")128- return err185+ body, err := io.ReadAll(r.Body)186+ if err != nil {187+ return err188+ }189+ r.Body = io.NopCloser(bytes.NewReader(body))190+ e, err := t.auth.VerifyNIP98(r.Header.Get("Authorization"), t.publicURL+r.URL.RequestURI(), r.Method, string(body))191+ if err != nil {192+ return err193+ }194+ if e.PubKey == repo.Owner {195+ return nil196+ }197+ role, roleErr := t.community.Role(ctx, e.PubKey)198+ if roleErr != nil || (role != "owner" && role != "moderator" && role != "member") {199+ return errors.New("restricted: repository authorization required")200+ }201+ return nil129202 }130203 func first(v []string) string {131204 if len(v) == 0 {@@ -134,7 +207,12 @@ func first(v []string) string {
134207 return v[0]135208 }136209 func (t *Tenant) resolveUIActor(r *http.Request) (string, error) {137- e, err := t.auth.VerifyNIP98(r.Header.Get("Authorization"), t.publicURL+r.URL.RequestURI(), r.Method, "")210+ body, err := io.ReadAll(r.Body)211+ if err != nil {212+ return "", err213+ }214+ r.Body = io.NopCloser(bytes.NewReader(body))215+ e, err := t.auth.VerifyNIP98(r.Header.Get("Authorization"), t.publicURL+r.URL.RequestURI(), r.Method, string(body))138216 if err != nil {139217 return "", err140218 }