tinyrelayfile
tinyrelay/internal/records/identity.go/

internal/records/identity.go raw
1package records
2
3import (
4 "context"
5 "database/sql"
6 "encoding/json"
7 "errors"
8 "fmt"
9 "strconv"
10
11 "github.com/FelineStateMachine/tinyrelay/internal/event"
12 "github.com/FelineStateMachine/tinyrelay/internal/policy"
13 "github.com/FelineStateMachine/tinyrelay/internal/storage"
14)
15
16func (s *Service) group() string { return s.groupID }
17
18func groupMetaTags(group string, p policy.Policy) [][]string {
19 tags := [][]string{{"-"}, {"d", group}, {"name", p.Name}, {"about", p.Description}}
20 if p.Icon != "" {
21 tags = append(tags, []string{"picture", p.Icon})
22 }
23 if p.Reads == "members" {
24 tags = append(tags, []string{"private"})
25 }
26 if p.Writes != "open" {
27 tags = append(tags, []string{"restricted"})
28 }
29 return tags
30}
31
32func loadOrCreateSecret(ctx context.Context, s *storage.Store) (string, error) {
33 var secret string
34 if err := s.GetSetting(ctx, secretSetting, &secret); err == nil {
35 if len(secret) != 64 {
36 return "", errors.New("records: invalid persisted relay secret")
37 }
38 return secret, nil
39 } else if !errors.Is(err, sql.ErrNoRows) {
40 return "", err
41 }
42 secret, err := event.GenerateKey()
43 if err != nil {
44 return "", fmt.Errorf("records: generate relay key: %w", err)
45 }
46 if err := s.PutSetting(ctx, secretSetting, secret); err != nil {
47 return "", err
48 }
49 return secret, nil
50}
51
52// PublicKey returns the relay signing key, never its secret.
53func (s *Service) PublicKey() string {
54 key, _ := event.PublicKey(s.secret)
55 return key
56}
57
58func (s *Service) RelayURL() string { return s.relayURL }
59
60func (s *Service) nextTimestamp(ctx context.Context, tx *sql.Tx, now int64) (int64, error) {
61 var previous int64
62 if err := tx.QueryRowContext(ctx, `SELECT value FROM settings WHERE key='records.clock'`).Scan(&previous); err != nil && !errors.Is(err, sql.ErrNoRows) {
63 return 0, err
64 }
65 if now <= previous {
66 now = previous + 1
67 }
68 if err := storage.PutSetting(ctx, tx, "records.clock", now); err != nil {
69 return 0, err
70 }
71 return now, nil
72}
73
74func (s *Service) signed(ctx context.Context, kind int, tags [][]string, content string, now int64) (event.Event, error) {
75 s.mu.Lock()
76 defer s.mu.Unlock()
77 var out event.Event
78 err := s.store.WithTx(ctx, func(tx *sql.Tx) error {
79 t, err := s.nextTimestamp(ctx, tx, now)
80 if err != nil {
81 return err
82 }
83 out = event.Event{CreatedAt: t, Kind: kind, Tags: tags, Content: content}
84 return event.Sign(&out, s.secret)
85 })
86 if err != nil {
87 return event.Event{}, err
88 }
89 if s.onGenerated != nil {
90 if err := s.onGenerated(ctx, out); err != nil {
91 return event.Event{}, err
92 }
93 }
94 return out, nil
95}
96
97func (s *Service) signedOnly(ctx context.Context, kind int, tags [][]string, content string, now int64) (event.Event, error) {
98 s.mu.Lock()
99 defer s.mu.Unlock()
100 var out event.Event
101 err := s.store.WithTx(ctx, func(tx *sql.Tx) error {
102 t, err := s.nextTimestamp(ctx, tx, now)
103 if err != nil {
104 return err
105 }
106 out = event.Event{CreatedAt: t, Kind: kind, Tags: tags, Content: content}
107 return event.Sign(&out, s.secret)
108 })
109 return out, err
110}
111
112func (s *Service) Generate(ctx context.Context, kind int, tags [][]string, content string, now int64) (event.Event, error) {
113 return s.signed(ctx, kind, tags, content, now)
114}
115
116func (s *Service) Sign(ctx context.Context, kind int, tags [][]string, content string, now int64) (event.Event, error) {
117 return s.signedOnly(ctx, kind, tags, content, now)
118}
119
120func (s *Service) profile(ctx context.Context, now int64) (event.Event, error) {
121 p := s.policy()
122 b, _ := json.Marshal(map[string]string{"name": p.Name, "about": p.Description, "picture": p.Icon})
123 return s.signed(ctx, event.KIND_PROFILE, nil, string(b), now)
124}
125
126func (s *Service) discovery(ctx context.Context, now int64) (event.Event, error) {
127 p := s.policy()
128 tags := [][]string{{"d", s.relayURL}}
129 if p.Reads != "open" {
130 tags = append(tags, []string{"r", p.Reads})
131 }
132 if p.Writes != "open" {
133 tags = append(tags, []string{"writes", p.Writes})
134 }
135 return s.signed(ctx, event.KIND_RELAY_DISCOVERY, tags, s.relayURL, now)
136}
137
138func (s *Service) roster(ctx context.Context, now int64) (event.Event, error) {
139 tags := [][]string{{"-"}}
140 communityMembers, err := s.members(ctx)
141 if err != nil {
142 return event.Event{}, err
143 }
144 for _, member := range communityMembers {
145 tag := []string{"member", member.PubKey}
146 if member.Role != "" && member.Role != "member" {
147 tag = append(tag, member.Role)
148 }
149 tags = append(tags, tag)
150 }
151 if len(tags) == 1 && s.policy().Owner != "" {
152 tags = append(tags, []string{"member", s.policy().Owner, "owner"})
153 }
154 return s.signed(ctx, event.KIND_ROSTER, tags, "", now)
155}
156
157func (s *Service) groupState(ctx context.Context, now int64) (event.Event, error) {
158 p := s.policy()
159 tags := [][]string{{"-"}, {"d", s.group()}, {"name", p.Name}, {"about", p.Description}}
160 if p.Icon != "" {
161 tags = append(tags, []string{"picture", p.Icon})
162 }
163 if p.Reads == "members" {
164 tags = append(tags, []string{"private"})
165 }
166 if p.Writes != "open" {
167 tags = append(tags, []string{"restricted"})
168 }
169 return s.signed(ctx, event.KIND_GROUP_METADATA, tags, "", now)
170}
171
172func (s *Service) MembershipDelta(ctx context.Context, pubkey string, added bool, now int64) (event.Event, error) {
173 kind := event.KIND_MEMBER_REMOVED
174 if added {
175 kind = event.KIND_MEMBER_ADDED
176 }
177 return s.signed(ctx, kind, [][]string{{"-"}, {"p", pubkey}}, "", now)
178}
179
180func (s *Service) groupRecords(ctx context.Context, now int64) ([]event.Event, error) {
181 p := s.policy()
182 communityMembers, err := s.members(ctx)
183 if err != nil {
184 return nil, err
185 }
186 var memberTags, admins, roleTags [][]string
187 for _, member := range communityMembers {
188 if member.Role == "agent" {
189 memberTags = append(memberTags, []string{"p", member.PubKey, member.Role})
190 } else {
191 memberTags = append(memberTags, []string{"p", member.PubKey})
192 }
193 if member.Role == "owner" || member.Role == "moderator" {
194 admins = append(admins, []string{"p", member.PubKey, member.Role})
195 }
196 }
197 if !p.DirectoryPublic {
198 memberTags = nil
199 }
200 for _, role := range roleOrder {
201 roleTags = append(roleTags, []string{"role", role, rolesAbout[role]})
202 }
203 vals := []struct {
204 kind int
205 tags [][]string
206 content string
207 }{
208 {event.KIND_GROUP_METADATA, groupMetaTags(s.group(), p), ""},
209 {event.KIND_GROUP_ADMINS, append([][]string{{"-"}, {"d", s.group()}}, admins...), ""},
210 {event.KIND_GROUP_MEMBERS, append([][]string{{"-"}, {"d", s.group()}}, memberTags...), ""},
211 {event.KIND_GROUP_ROLES, append([][]string{{"-"}, {"d", s.group()}}, roleTags...), ""},
212 }
213 out := make([]event.Event, 0, len(vals)+3)
214 for _, v := range vals {
215 e, err := s.signed(ctx, v.kind, v.tags, v.content, now)
216 if err != nil {
217 return nil, err
218 }
219 out = append(out, e)
220 }
221 for i, role := range roleOrder {
222 e, err := s.signed(ctx, event.KIND_ROLE_DEF, [][]string{{"-"}, {"d", role}, {"label", role}, {"description", rolesAbout[role]}, {"order", strconv.Itoa(i + 1)}}, "", now)
223 if err != nil {
224 return nil, err
225 }
226 out = append(out, e)
227 }
228 return out, nil
229}
230
231func (s *Service) groupRecordsEmpty(ctx context.Context, now int64, p policy.Policy) ([]event.Event, error) {
232 _ = p
233 vals := []struct {
234 kind int
235 tags [][]string
236 content string
237 }{
238 {event.KIND_GROUP_METADATA, groupMetaTags(s.group(), p), ""},
239 {event.KIND_GROUP_ADMINS, [][]string{{"-"}, {"d", s.group()}}, ""},
240 {event.KIND_GROUP_MEMBERS, [][]string{{"-"}, {"d", s.group()}}, ""},
241 {event.KIND_GROUP_ROLES, [][]string{{"-"}, {"d", s.group()}}, ""},
242 {event.KIND_ROLE_DEF, [][]string{{"-"}, {"d", "owner"}, {"label", "owner"}, {"description", "relay owner"}, {"order", "1"}}, ""},
243 {event.KIND_ROLE_DEF, [][]string{{"-"}, {"d", "moderator"}, {"label", "moderator"}, {"description", "relay moderator"}, {"order", "2"}}, ""},
244 {event.KIND_ROLE_DEF, [][]string{{"-"}, {"d", "member"}, {"label", "member"}, {"description", "relay member"}, {"order", "3"}}, ""},
245 {event.KIND_ROLE_DEF, [][]string{{"-"}, {"d", "agent"}, {"label", "agent"}, {"description", "agent under an owner-signed grant"}, {"order", "4"}}, ""},
246 }
247 out := make([]event.Event, 0, len(vals))
248 for _, v := range vals {
249 e, err := s.signed(ctx, v.kind, v.tags, v.content, now)
250 if err != nil {
251 return nil, err
252 }
253 out = append(out, e)
254 }
255 return out, nil
256}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

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

Refs