kind_whitelist_test.go raw
1 package policy
2
3 import (
4 "testing"
5
6 "next.orly.dev/pkg/nostr/encoders/hex"
7 )
8
9 // TestKindWhitelistComprehensive verifies that kind whitelisting properly rejects
10 // unlisted kinds in all scenarios: explicit whitelist, implicit whitelist (rules), and combinations
11 func TestKindWhitelistComprehensive(t *testing.T) {
12 testSigner, testPubkey := generateTestKeypair(t)
13
14 t.Run("Explicit Whitelist - kind IN whitelist, HAS rule", func(t *testing.T) {
15 policy := &P{
16 DefaultPolicy: "allow", // Changed to allow so rules without constraints allow by default
17 Kind: Kinds{
18 Whitelist: []int{1, 3, 5}, // Explicit whitelist
19 },
20 rules: map[int]Rule{
21 1: {Description: "Rule for kind 1"},
22 3: {Description: "Rule for kind 3"},
23 5: {Description: "Rule for kind 5"},
24 },
25 }
26
27 event := createTestEvent(t, testSigner, "test", 1)
28 allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
29 if err != nil {
30 t.Fatalf("Unexpected error: %v", err)
31 }
32 if !allowed {
33 t.Error("Kind 1 should be ALLOWED (in whitelist, has rule, passes rule check)")
34 }
35 })
36
37 t.Run("Explicit Whitelist - kind IN whitelist, NO rule", func(t *testing.T) {
38 policy := &P{
39 DefaultPolicy: "allow",
40 Kind: Kinds{
41 Whitelist: []int{1, 3, 5}, // Explicit whitelist
42 },
43 rules: map[int]Rule{
44 1: {Description: "Rule for kind 1"},
45 // Kind 3 has no rule
46 },
47 }
48
49 event := createTestEvent(t, testSigner, "test", 3)
50 allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
51 if err != nil {
52 t.Fatalf("Unexpected error: %v", err)
53 }
54 if !allowed {
55 t.Error("Kind 3 should be ALLOWED (in whitelist, no rule, default policy is allow)")
56 }
57 })
58
59 t.Run("Explicit Whitelist - kind NOT in whitelist, HAS rule", func(t *testing.T) {
60 policy := &P{
61 DefaultPolicy: "allow",
62 Kind: Kinds{
63 Whitelist: []int{1, 3, 5}, // Explicit whitelist - kind 10 NOT included
64 },
65 rules: map[int]Rule{
66 1: {Description: "Rule for kind 1"},
67 10: {Description: "Rule for kind 10"}, // Has rule but not in whitelist!
68 },
69 }
70
71 event := createTestEvent(t, testSigner, "test", 10)
72 allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
73 if err != nil {
74 t.Fatalf("Unexpected error: %v", err)
75 }
76 if allowed {
77 t.Error("Kind 10 should be REJECTED (NOT in whitelist, even though it has a rule)")
78 }
79 })
80
81 t.Run("Explicit Whitelist - kind NOT in whitelist, NO rule", func(t *testing.T) {
82 policy := &P{
83 DefaultPolicy: "allow",
84 Kind: Kinds{
85 Whitelist: []int{1, 3, 5}, // Explicit whitelist
86 },
87 rules: map[int]Rule{
88 1: {Description: "Rule for kind 1"},
89 },
90 }
91
92 event := createTestEvent(t, testSigner, "test", 99)
93 allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
94 if err != nil {
95 t.Fatalf("Unexpected error: %v", err)
96 }
97 if allowed {
98 t.Error("Kind 99 should be REJECTED (NOT in whitelist)")
99 }
100 })
101
102 t.Run("Implicit Whitelist (rules) - kind HAS rule", func(t *testing.T) {
103 policy := &P{
104 DefaultPolicy: "allow", // Changed to allow so rules without constraints allow by default
105 // No explicit whitelist
106 rules: map[int]Rule{
107 1: {Description: "Rule for kind 1"},
108 3: {Description: "Rule for kind 3"},
109 },
110 }
111
112 event := createTestEvent(t, testSigner, "test", 1)
113 allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
114 if err != nil {
115 t.Fatalf("Unexpected error: %v", err)
116 }
117 if !allowed {
118 t.Error("Kind 1 should be ALLOWED (has rule, implicit whitelist)")
119 }
120 })
121
122 t.Run("Implicit Whitelist (rules) - kind NO rule", func(t *testing.T) {
123 policy := &P{
124 // DefaultPolicy not set (empty) - uses implicit whitelist when rules exist
125 // No explicit whitelist
126 rules: map[int]Rule{
127 1: {Description: "Rule for kind 1"},
128 3: {Description: "Rule for kind 3"},
129 },
130 }
131
132 event := createTestEvent(t, testSigner, "test", 99)
133 allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
134 if err != nil {
135 t.Fatalf("Unexpected error: %v", err)
136 }
137 if allowed {
138 t.Error("Kind 99 should be REJECTED (no rule, implicit whitelist mode)")
139 }
140 })
141
142 t.Run("Explicit Whitelist + Global Rule - kind NOT in whitelist", func(t *testing.T) {
143 policy := &P{
144 DefaultPolicy: "allow",
145 Kind: Kinds{
146 Whitelist: []int{1, 3, 5}, // Explicit whitelist
147 },
148 Global: Rule{
149 Description: "Global rule applies to all kinds",
150 AccessControl: AccessControl{
151 WriteAllow: []string{hex.Enc(testPubkey)},
152 },
153 },
154 rules: map[int]Rule{
155 1: {Description: "Rule for kind 1"},
156 },
157 }
158
159 // Even with global rule, kind not in whitelist should be rejected
160 event := createTestEvent(t, testSigner, "test", 99)
161 allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
162 if err != nil {
163 t.Fatalf("Unexpected error: %v", err)
164 }
165 if allowed {
166 t.Error("Kind 99 should be REJECTED (NOT in whitelist, even with global rule)")
167 }
168 })
169
170 t.Run("Blacklist + Rules - kind in blacklist but has rule", func(t *testing.T) {
171 policy := &P{
172 DefaultPolicy: "allow",
173 Kind: Kinds{
174 Blacklist: []int{10, 20}, // Blacklist
175 },
176 rules: map[int]Rule{
177 1: {Description: "Rule for kind 1"},
178 10: {Description: "Rule for kind 10"}, // Has rule but blacklisted!
179 },
180 }
181
182 // Kind 10 is blacklisted, should be rejected
183 event := createTestEvent(t, testSigner, "test", 10)
184 allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
185 if err != nil {
186 t.Fatalf("Unexpected error: %v", err)
187 }
188 if allowed {
189 t.Error("Kind 10 should be REJECTED (in blacklist)")
190 }
191 })
192
193 t.Run("Blacklist + Rules - kind NOT in blacklist but NO rule (implicit whitelist)", func(t *testing.T) {
194 policy := &P{
195 DefaultPolicy: "allow",
196 Kind: Kinds{
197 Blacklist: []int{10, 20}, // Blacklist
198 },
199 rules: map[int]Rule{
200 1: {Description: "Rule for kind 1"},
201 3: {Description: "Rule for kind 3"},
202 },
203 }
204
205 // Kind 99 is not blacklisted but has no rule
206 // With blacklist present + rules, implicit whitelist applies
207 event := createTestEvent(t, testSigner, "test", 99)
208 allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
209 if err != nil {
210 t.Fatalf("Unexpected error: %v", err)
211 }
212 if allowed {
213 t.Error("Kind 99 should be REJECTED (not in blacklist but no rule, implicit whitelist)")
214 }
215 })
216
217 t.Run("Whitelist takes precedence over Blacklist", func(t *testing.T) {
218 policy := &P{
219 DefaultPolicy: "allow",
220 Kind: Kinds{
221 Whitelist: []int{1, 3, 5, 10}, // Whitelist includes 10
222 Blacklist: []int{10, 20}, // Blacklist also includes 10
223 },
224 rules: map[int]Rule{
225 1: {Description: "Rule for kind 1"},
226 10: {Description: "Rule for kind 10"},
227 },
228 }
229
230 // Kind 10 is in BOTH whitelist and blacklist - whitelist should win
231 event := createTestEvent(t, testSigner, "test", 10)
232 allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
233 if err != nil {
234 t.Fatalf("Unexpected error: %v", err)
235 }
236 if !allowed {
237 t.Error("Kind 10 should be ALLOWED (whitelist takes precedence over blacklist)")
238 }
239 })
240 }
241
242 // TestKindWhitelistRealWorld tests real-world scenarios from the documentation
243 func TestKindWhitelistRealWorld(t *testing.T) {
244 testSigner, testPubkey := generateTestKeypair(t)
245 _, otherPubkey := generateTestKeypair(t)
246
247 t.Run("Real World: Only allow kinds 1, 3, 30023", func(t *testing.T) {
248 policy := &P{
249 DefaultPolicy: "allow", // Allow by default for kinds in whitelist
250 Kind: Kinds{
251 Whitelist: []int{1, 3, 30023},
252 },
253 rules: map[int]Rule{
254 1: {
255 Description: "Text notes",
256 // No WriteAllow = anyone authenticated can write
257 },
258 3: {
259 Description: "Contact lists",
260 // No WriteAllow = anyone authenticated can write
261 },
262 30023: {
263 Description: "Long-form content",
264 AccessControl: AccessControl{
265 WriteAllow: []string{hex.Enc(testPubkey)}, // Only specific user can write
266 },
267 },
268 },
269 }
270
271 // Test kind 1 (allowed)
272 event1 := createTestEvent(t, testSigner, "Hello world", 1)
273 allowed, err := policy.CheckPolicy("write", event1, testPubkey, "127.0.0.1")
274 if err != nil {
275 t.Fatalf("Unexpected error: %v", err)
276 }
277 if !allowed {
278 t.Error("Kind 1 should be ALLOWED")
279 }
280
281 // Test kind 4 (NOT in whitelist, should be rejected)
282 event4 := createTestEvent(t, testSigner, "DM", 4)
283 allowed, err = policy.CheckPolicy("write", event4, testPubkey, "127.0.0.1")
284 if err != nil {
285 t.Fatalf("Unexpected error: %v", err)
286 }
287 if allowed {
288 t.Error("Kind 4 should be REJECTED (not in whitelist)")
289 }
290
291 // Test kind 30023 by authorized user (allowed)
292 event30023Auth := createTestEvent(t, testSigner, "Article", 30023)
293 allowed, err = policy.CheckPolicy("write", event30023Auth, testPubkey, "127.0.0.1")
294 if err != nil {
295 t.Fatalf("Unexpected error: %v", err)
296 }
297 if !allowed {
298 t.Error("Kind 30023 should be ALLOWED for authorized user")
299 }
300
301 // Test kind 30023 by unauthorized user (should fail rule check)
302 event30023Unauth := createTestEvent(t, testSigner, "Article", 30023)
303 allowed, err = policy.CheckPolicy("write", event30023Unauth, otherPubkey, "127.0.0.1")
304 if err != nil {
305 t.Fatalf("Unexpected error: %v", err)
306 }
307 if allowed {
308 t.Error("Kind 30023 should be REJECTED for unauthorized user")
309 }
310
311 // Test kind 9735 (NOT in whitelist, should be rejected even with valid signature)
312 event9735 := createTestEvent(t, testSigner, "Zap", 9735)
313 allowed, err = policy.CheckPolicy("write", event9735, testPubkey, "127.0.0.1")
314 if err != nil {
315 t.Fatalf("Unexpected error: %v", err)
316 }
317 if allowed {
318 t.Error("Kind 9735 should be REJECTED (not in whitelist)")
319 }
320 })
321 }
322