handle-nip43.go raw
1 package app
2
3 import (
4 "context"
5 "fmt"
6 "strings"
7 "time"
8
9 "next.orly.dev/pkg/lol/chk"
10 "next.orly.dev/pkg/lol/log"
11 "next.orly.dev/pkg/acl"
12 "next.orly.dev/pkg/nostr/encoders/envelopes/okenvelope"
13 "next.orly.dev/pkg/nostr/encoders/event"
14 "next.orly.dev/pkg/nostr/encoders/hex"
15 "next.orly.dev/pkg/protocol/nip43"
16 )
17
18 // HandleNIP43JoinRequest processes a kind 28934 join request
19 func (l *Listener) HandleNIP43JoinRequest(ev *event.E) error {
20 log.D.F("handling NIP-43 join request from %s", hex.Enc(ev.Pubkey))
21
22 // Validate the join request
23 inviteCode, valid, reason := nip43.ValidateJoinRequest(ev)
24 if !valid {
25 log.W.F("invalid join request: %s", reason)
26 return l.sendOKResponse(ev.ID, false, fmt.Sprintf("restricted: %s", reason))
27 }
28
29 // Check if user is already a member
30 isMember, err := l.DB.IsNIP43Member(ev.Pubkey)
31 if chk.E(err) {
32 log.E.F("error checking membership: %v", err)
33 return l.sendOKResponse(ev.ID, false, "error: internal server error")
34 }
35
36 if isMember {
37 log.D.F("user %s is already a member", hex.Enc(ev.Pubkey))
38 return l.sendOKResponse(ev.ID, true, "duplicate: you are already a member of this relay")
39 }
40
41 // Validate the invite code
42 validCode, reason := l.Server.InviteManager.ValidateAndConsume(inviteCode, ev.Pubkey)
43
44 if !validCode {
45 log.W.F("invalid or expired invite code: %s - %s", inviteCode, reason)
46 return l.sendOKResponse(ev.ID, false, fmt.Sprintf("restricted: %s", reason))
47 }
48
49 // Add the member
50 if err = l.DB.AddNIP43Member(ev.Pubkey, inviteCode); chk.E(err) {
51 log.E.F("error adding member: %v", err)
52 return l.sendOKResponse(ev.ID, false, "error: failed to add member")
53 }
54
55 log.I.F("successfully added member %s via invite code", hex.Enc(ev.Pubkey))
56
57 // Publish kind 8000 "add member" event if configured
58 if l.Config.NIP43PublishEvents {
59 if err = l.publishAddUserEvent(ev.Pubkey); chk.E(err) {
60 log.W.F("failed to publish add user event: %v", err)
61 }
62 }
63
64 // Update membership list if configured
65 if l.Config.NIP43PublishMemberList {
66 if err = l.publishMembershipList(); chk.E(err) {
67 log.W.F("failed to publish membership list: %v", err)
68 }
69 }
70
71 relayURL := l.Config.RelayURL
72 if relayURL == "" {
73 relayURL = fmt.Sprintf("wss://%s:%d", l.Config.Listen, l.Config.Port)
74 }
75
76 return l.sendOKResponse(ev.ID, true, fmt.Sprintf("welcome to %s!", relayURL))
77 }
78
79 // HandleNIP43LeaveRequest processes a kind 28936 leave request
80 func (l *Listener) HandleNIP43LeaveRequest(ev *event.E) error {
81 log.D.F("handling NIP-43 leave request from %s", hex.Enc(ev.Pubkey))
82
83 // Validate the leave request
84 valid, reason := nip43.ValidateLeaveRequest(ev)
85 if !valid {
86 log.W.F("invalid leave request: %s", reason)
87 return l.sendOKResponse(ev.ID, false, fmt.Sprintf("error: %s", reason))
88 }
89
90 // Check if user is a member
91 isMember, err := l.DB.IsNIP43Member(ev.Pubkey)
92 if chk.E(err) {
93 log.E.F("error checking membership: %v", err)
94 return l.sendOKResponse(ev.ID, false, "error: internal server error")
95 }
96
97 if !isMember {
98 log.D.F("user %s is not a member", hex.Enc(ev.Pubkey))
99 return l.sendOKResponse(ev.ID, true, "you are not a member of this relay")
100 }
101
102 // Remove the member
103 if err = l.DB.RemoveNIP43Member(ev.Pubkey); chk.E(err) {
104 log.E.F("error removing member: %v", err)
105 return l.sendOKResponse(ev.ID, false, "error: failed to remove member")
106 }
107
108 log.I.F("successfully removed member %s", hex.Enc(ev.Pubkey))
109
110 // Publish kind 8001 "remove member" event if configured
111 if l.Config.NIP43PublishEvents {
112 if err = l.publishRemoveUserEvent(ev.Pubkey); chk.E(err) {
113 log.W.F("failed to publish remove user event: %v", err)
114 }
115 }
116
117 // Update membership list if configured
118 if l.Config.NIP43PublishMemberList {
119 if err = l.publishMembershipList(); chk.E(err) {
120 log.W.F("failed to publish membership list: %v", err)
121 }
122 }
123
124 return l.sendOKResponse(ev.ID, true, "you have been removed from this relay")
125 }
126
127 // HandleNIP43InviteRequest processes a kind 28935 invite request (REQ subscription)
128 func (s *Server) HandleNIP43InviteRequest(pubkey []byte) (*event.E, error) {
129 log.D.F("generating NIP-43 invite for pubkey %s", hex.Enc(pubkey))
130
131 // Check if requester has permission to request invites
132 // This could be based on ACL, admins, etc.
133 accessLevel := acl.Registry.GetAccessLevel(pubkey, "")
134 if accessLevel != "admin" && accessLevel != "owner" {
135 log.W.F("unauthorized invite request from %s (level: %s)", hex.Enc(pubkey), accessLevel)
136 return nil, fmt.Errorf("unauthorized: only admins can request invites")
137 }
138
139 // Generate a new invite code
140 code, err := s.InviteManager.GenerateCode()
141 if chk.E(err) {
142 return nil, err
143 }
144
145 // Get relay identity
146 relaySecret, err := s.db.GetOrCreateRelayIdentitySecret()
147 if chk.E(err) {
148 return nil, err
149 }
150
151 // Build the invite event
152 inviteEvent, err := nip43.BuildInviteEvent(relaySecret, code)
153 if chk.E(err) {
154 return nil, err
155 }
156
157 log.D.F("generated invite code for %s", hex.Enc(pubkey))
158 return inviteEvent, nil
159 }
160
161 // publishAddUserEvent publishes a kind 8000 add user event
162 func (l *Listener) publishAddUserEvent(userPubkey []byte) error {
163 relaySecret, err := l.DB.GetOrCreateRelayIdentitySecret()
164 if chk.E(err) {
165 return err
166 }
167
168 ev, err := nip43.BuildAddUserEvent(relaySecret, userPubkey)
169 if chk.E(err) {
170 return err
171 }
172
173 // Save to database
174 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
175 defer cancel()
176 if _, err = l.DB.SaveEvent(ctx, ev); chk.E(err) {
177 return err
178 }
179
180 // Publish to subscribers
181 l.publishers.Deliver(ev)
182
183 log.I.F("published kind 8000 add user event for %s", hex.Enc(userPubkey))
184 return nil
185 }
186
187 // publishRemoveUserEvent publishes a kind 8001 remove user event
188 func (l *Listener) publishRemoveUserEvent(userPubkey []byte) error {
189 relaySecret, err := l.DB.GetOrCreateRelayIdentitySecret()
190 if chk.E(err) {
191 return err
192 }
193
194 ev, err := nip43.BuildRemoveUserEvent(relaySecret, userPubkey)
195 if chk.E(err) {
196 return err
197 }
198
199 // Save to database
200 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
201 defer cancel()
202 if _, err = l.DB.SaveEvent(ctx, ev); chk.E(err) {
203 return err
204 }
205
206 // Publish to subscribers
207 l.publishers.Deliver(ev)
208
209 log.I.F("published kind 8001 remove user event for %s", hex.Enc(userPubkey))
210 return nil
211 }
212
213 // publishMembershipList publishes a kind 13534 membership list event
214 func (l *Listener) publishMembershipList() error {
215 // Get all members
216 members, err := l.DB.GetAllNIP43Members()
217 if chk.E(err) {
218 return err
219 }
220
221 relaySecret, err := l.DB.GetOrCreateRelayIdentitySecret()
222 if chk.E(err) {
223 return err
224 }
225
226 ev, err := nip43.BuildMemberListEvent(relaySecret, members)
227 if chk.E(err) {
228 return err
229 }
230
231 // Save to database
232 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
233 defer cancel()
234 if _, err = l.DB.SaveEvent(ctx, ev); chk.E(err) {
235 return err
236 }
237
238 // Publish to subscribers
239 l.publishers.Deliver(ev)
240
241 log.I.F("published kind 13534 membership list event with %d members", len(members))
242 return nil
243 }
244
245 // sendOKResponse sends an OK envelope response
246 func (l *Listener) sendOKResponse(eventID []byte, accepted bool, message string) error {
247 // Ensure message doesn't have "restricted: " prefix if already present
248 if accepted && strings.HasPrefix(message, "restricted: ") {
249 message = strings.TrimPrefix(message, "restricted: ")
250 }
251
252 env := okenvelope.NewFrom(eventID, accepted, []byte(message))
253 return env.Write(l)
254 }
255