managed.go raw
1 package acl
2
3 import (
4 "context"
5 "encoding/hex"
6 "net"
7 "reflect"
8 "sync"
9
10 "next.orly.dev/pkg/lol/errorf"
11 "next.orly.dev/pkg/lol/log"
12 "next.orly.dev/app/config"
13 "next.orly.dev/pkg/database"
14 "next.orly.dev/pkg/nostr/encoders/bech32encoding"
15 "next.orly.dev/pkg/nostr/encoders/event"
16 "next.orly.dev/pkg/utils"
17 )
18
19 type Managed struct {
20 // Ctx holds the context for the ACL.
21 // Deprecated: Use Context() method instead of accessing directly.
22 Ctx context.Context
23 cfg *config.C
24 db database.Database
25 managedACL *database.ManagedACL
26 owners [][]byte
27 admins [][]byte
28 peerAdmins [][]byte // peer relay identity pubkeys with admin access
29 mx sync.RWMutex
30 }
31
32 // Context returns the ACL context.
33 func (m *Managed) Context() context.Context {
34 return m.Ctx
35 }
36
37 func (m *Managed) Configure(cfg ...any) (err error) {
38 log.I.F("configuring managed ACL")
39 for _, ca := range cfg {
40 switch c := ca.(type) {
41 case *config.C:
42 m.cfg = c
43 case database.Database:
44 m.db = c
45 // ManagedACL requires the concrete Badger database type
46 // Type assertion to check if it's a Badger database
47 if d, ok := c.(*database.D); ok {
48 m.managedACL = database.NewManagedACL(d)
49 } else {
50 log.W.F("managed ACL: database is not Badger, managed ACL features will be limited")
51 }
52 case context.Context:
53 m.Ctx = c
54 default:
55 err = errorf.E("invalid type: %T", reflect.TypeOf(ca))
56 }
57 }
58 if m.cfg == nil || m.db == nil {
59 err = errorf.E("both config and database must be set")
60 return
61 }
62
63 // Load owners
64 for _, owner := range m.cfg.Owners {
65 if len(owner) == 0 {
66 continue
67 }
68 var pk []byte
69 if pk, err = bech32encoding.NpubOrHexToPublicKeyBinary(owner); err != nil {
70 continue
71 }
72 m.owners = append(m.owners, pk)
73 }
74
75 // Load admins
76 for _, admin := range m.cfg.Admins {
77 if len(admin) == 0 {
78 continue
79 }
80 var pk []byte
81 if pk, err = bech32encoding.NpubOrHexToPublicKeyBinary(admin); err != nil {
82 continue
83 }
84 m.admins = append(m.admins, pk)
85 }
86
87 return
88 }
89
90 // UpdatePeerAdmins updates the list of peer relay identity pubkeys that have admin access
91 func (m *Managed) UpdatePeerAdmins(peerPubkeys [][]byte) {
92 m.mx.Lock()
93 defer m.mx.Unlock()
94 m.peerAdmins = make([][]byte, len(peerPubkeys))
95 copy(m.peerAdmins, peerPubkeys)
96 log.I.F("updated peer admin list with %d pubkeys", len(peerPubkeys))
97 }
98
99 func (m *Managed) GetAccessLevel(pub []byte, address string) (level string) {
100 m.mx.RLock()
101 defer m.mx.RUnlock()
102
103 // If no pubkey provided and auth is required, return "none"
104 if len(pub) == 0 && m.cfg.AuthRequired {
105 return "none"
106 }
107
108 // Check owners first
109 for _, v := range m.owners {
110 if utils.FastEqual(v, pub) {
111 return "owner"
112 }
113 }
114
115 // Check admins
116 for _, v := range m.admins {
117 if utils.FastEqual(v, pub) {
118 return "admin"
119 }
120 }
121
122 // Check peer relay identity pubkeys (they get admin access)
123 for _, v := range m.peerAdmins {
124 if utils.FastEqual(v, pub) {
125 return "admin"
126 }
127 }
128
129 // managedACL may be nil when database is not Badger (e.g., gRPC proxy).
130 // Fall through to default read access in that case.
131 if m.managedACL == nil {
132 if len(pub) == 0 {
133 return "none"
134 }
135 return "read"
136 }
137
138 // Check if pubkey is banned
139 pubkeyHex := hex.EncodeToString(pub)
140 if banned, err := m.managedACL.IsPubkeyBanned(pubkeyHex); err == nil && banned {
141 return "banned"
142 }
143
144 // Check if pubkey is explicitly allowed
145 if allowed, err := m.managedACL.IsPubkeyAllowed(pubkeyHex); err == nil && allowed {
146 return "write"
147 }
148
149 // Check if IP is blocked
150 if blocked, err := m.managedACL.IsIPBlocked(address); err == nil && blocked {
151 return "blocked"
152 }
153
154 // Default to read-only for managed mode
155 return "read"
156 }
157
158 func (m *Managed) CheckPolicy(ev *event.E) (allowed bool, err error) {
159 // If managedACL is nil (non-Badger DB), allow everything
160 if m.managedACL == nil {
161 return true, nil
162 }
163
164 // Check if event is banned
165 eventID := hex.EncodeToString(ev.ID)
166 if banned, err := m.managedACL.IsEventBanned(eventID); err == nil && banned {
167 return false, nil
168 }
169
170 // Check if event is explicitly allowed
171 if allowed, err := m.managedACL.IsEventAllowed(eventID); err == nil && allowed {
172 return true, nil
173 }
174
175 // Check if event kind is allowed
176 if allowed, err := m.managedACL.IsKindAllowed(int(ev.Kind)); err == nil && !allowed {
177 // If there are allowed kinds configured and this kind is not in the list, deny
178 allowedKinds, err := m.managedACL.ListAllowedKinds()
179 if err == nil && len(allowedKinds) > 0 {
180 return false, nil
181 }
182 }
183
184 // Check if author is banned
185 authorHex := hex.EncodeToString(ev.Pubkey)
186 if banned, err := m.managedACL.IsPubkeyBanned(authorHex); err == nil && banned {
187 return false, nil
188 }
189
190 // Check if author is explicitly allowed
191 if allowed, err := m.managedACL.IsPubkeyAllowed(authorHex); err == nil && allowed {
192 return true, nil
193 }
194
195 // For managed mode, default to allowing events from owners and admins
196 for _, v := range m.owners {
197 if utils.FastEqual(v, ev.Pubkey) {
198 return true, nil
199 }
200 }
201
202 for _, v := range m.admins {
203 if utils.FastEqual(v, ev.Pubkey) {
204 return true, nil
205 }
206 }
207
208 // Check if we should add this event to moderation queue
209 // This could be extended to add events to moderation based on content analysis
210 // For now, we'll just allow the event
211
212 // Default to allowing events in managed mode (can be restricted by explicit bans/allows)
213 return true, nil
214 }
215
216 func (m *Managed) GetACLInfo() (name, description, documentation string) {
217 return "managed", "managed ACL with NIP-86 support",
218 `Managed ACL mode provides fine-grained access control through NIP-86 management API.
219
220 Features:
221 - Ban/allow specific pubkeys
222 - Ban/allow specific events
223 - Block IP addresses
224 - Allow/deny specific event kinds
225 - Relay metadata management
226 - Event moderation queue
227
228 This mode requires explicit management through the NIP-86 API endpoints.
229 Only relay owners can access the management interface and API.`
230 }
231
232 func (m *Managed) Type() string {
233 return "managed"
234 }
235
236 func (m *Managed) Syncer() {
237 // Managed ACL doesn't need background syncing
238 // All management is done through the API
239 }
240
241 // Helper methods for the management API
242
243 // IsIPBlocked checks if an IP address is blocked
244 func (m *Managed) IsIPBlocked(ip string) bool {
245 // Parse IP to handle both IPv4 and IPv6
246 parsedIP := net.ParseIP(ip)
247 if parsedIP == nil {
248 return false
249 }
250
251 blocked, err := m.managedACL.IsIPBlocked(ip)
252 if err != nil {
253 log.W.F("error checking if IP is blocked: %v", err)
254 return false
255 }
256 return blocked
257 }
258
259 // GetManagedACL returns the managed ACL database instance
260 func (m *Managed) GetManagedACL() *database.ManagedACL {
261 return m.managedACL
262 }
263
264 func init() {
265 Registry.Register(new(Managed))
266 }
267