tinyrelayfile
tinyrelay/internal/replication/push.go/

internal/replication/push.go raw
1package replication
2
3import (
4 "context"
5 "encoding/json"
6 "errors"
7 "net/url"
8 "strings"
9
10 "github.com/FelineStateMachine/tinyrelay/internal/event"
11 "github.com/FelineStateMachine/tinyrelay/internal/storage"
12)
13
14type CallbackPolicy struct {
15 HostOrigins []string
16 OwnerOrigins []string
17}
18
19type PushRegistration struct {
20 ID string
21 Owner string
22 Relay string
23 Callback string
24 Filters []event.Filter
25 Ignore []event.Filter
26 IncludeEvent bool
27}
28
29func ParsePushRegistration(e event.Event, policy CallbackPolicy, self string) (PushRegistration, error) {
30 if e.Kind != event.KIND_PUSH_REGISTRATION {
31 return PushRegistration{}, errors.New("replication: wrong push registration kind")
32 }
33 registration := PushRegistration{ID: e.ID, Owner: e.PubKey}
34 registration.Relay = exactTag(e, "relay", self)
35 if registration.Relay == "" {
36 return PushRegistration{}, errors.New("replication: registration does not name this relay")
37 }
38 callback := exactOriginTag(e, "callback")
39 if callback == "" || !contains(policy.HostOrigins, callback) || !contains(policy.OwnerOrigins, callback) {
40 return PushRegistration{}, errors.New("replication: callback origin is not approved")
41 }
42 registration.Callback = callback
43 filters, err := parseFilterTags(e, "filter")
44 if err != nil || len(filters) == 0 {
45 return PushRegistration{}, errors.New("replication: registration needs a filter")
46 }
47 registration.Filters = filters
48 registration.Ignore, err = parseFilterTags(e, "ignore")
49 if err != nil {
50 return PushRegistration{}, err
51 }
52 registration.IncludeEvent = hasTag(e, "include_event")
53 return registration, nil
54}
55
56func PushMatches(reg PushRegistration, e event.Event) bool {
57 if !matchesAny(reg.Filters, e) {
58 return false
59 }
60 return !matchesAny(reg.Ignore, e)
61}
62
63func CallbackPayload(reg PushRegistration, e event.Event) ([]byte, error) {
64 value := map[string]any{"id": e.ID, "relay": reg.Relay}
65 if reg.IncludeEvent {
66 value["event"] = e
67 }
68 return json.Marshal(value)
69}
70
71// PrepareCallbackIntents persists references only. Callback I/O is performed
72// later by a worker after registration and event visibility are rechecked.
73func PrepareCallbackIntents(ctx context.Context, e event.Event, registrations []PushRegistration) []storage.Intent {
74 if ctx.Err() != nil || privateKind(e.Kind) || hasProtectedTag(e) {
75 return nil
76 }
77 out := make([]storage.Intent, 0, len(registrations))
78 for _, registration := range registrations {
79 if !PushMatches(registration, e) {
80 continue
81 }
82 payload, err := json.Marshal(struct {
83 Registration PushRegistration `json:"registration"`
84 }{registration})
85 if err != nil {
86 continue
87 }
88 out = append(out, storage.Intent{Kind: "callback", EventID: e.ID, Target: registration.ID, Payload: string(payload)})
89 }
90 return out
91}
92
93func parseFilterTags(e event.Event, name string) ([]event.Filter, error) {
94 values := event.TagValues(e, name)
95 filters := make([]event.Filter, 0, len(values))
96 for _, value := range values {
97 filter, err := event.ParseFilter([]byte(value))
98 if err != nil {
99 return nil, errors.New("replication: invalid push filter")
100 }
101 filters = append(filters, filter)
102 }
103 return filters, nil
104}
105
106func exactTag(e event.Event, name, wanted string) string {
107 for _, value := range event.TagValues(e, name) {
108 if strings.TrimRight(value, "/") == strings.TrimRight(wanted, "/") && safeRelayURL(value) {
109 return strings.TrimRight(value, "/")
110 }
111 }
112 return ""
113}
114
115func exactOriginTag(e event.Event, name string) string {
116 for _, value := range event.TagValues(e, name) {
117 parsed, err := url.Parse(value)
118 if err == nil && parsed.Scheme == "https" && parsed.User == nil && parsed.Port() == "" && parsed.Path == "" && parsed.RawQuery == "" && parsed.Fragment == "" {
119 return parsed.Scheme + "://" + parsed.Host
120 }
121 }
122 return ""
123}
124
125func matchesAny(filters []event.Filter, e event.Event) bool {
126 for _, filter := range filters {
127 if event.Matches(filter, e) {
128 return true
129 }
130 }
131 return false
132}
133
134func hasTag(e event.Event, name string) bool {
135 for _, tag := range e.Tags {
136 if len(tag) > 0 && tag[0] == name {
137 return true
138 }
139 }
140 return false
141}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs