|
|
|
internal/records/identity.go raw
| 1 | package records | | 2 | | | 3 | import ( | | 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 | | | 16 | func (s *Service) group() string { return s.groupID } | | 17 | | | 18 | func 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 | | | 32 | func 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. | | 53 | func (s *Service) PublicKey() string { | | 54 | key, _ := event.PublicKey(s.secret) | | 55 | return key | | 56 | } | | 57 | | | 58 | func (s *Service) RelayURL() string { return s.relayURL } | | 59 | | | 60 | func (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 | | | 74 | func (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 | | | 97 | func (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 | | | 112 | func (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 | | | 116 | func (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 | | | 120 | func (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 | | | 126 | func (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 | | | 138 | func (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 | | | 157 | func (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 | | | 172 | func (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 | | | 180 | func (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 | | | 231 | func (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| owner | c01503e8035f |
|---|
| visibility | public |
|---|
| head | d730679639bf |
|---|
Maintainers- c01503e8035f owner
- ba1b5beed5b5 agent hermes
Clonehttps://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git
|