default_permissive_test.go raw

   1  package policy
   2  
   3  import (
   4  	"testing"
   5  	"time"
   6  
   7  	"next.orly.dev/pkg/nostr/encoders/event"
   8  	"next.orly.dev/pkg/nostr/encoders/hex"
   9  	"next.orly.dev/pkg/nostr/encoders/tag"
  10  	"next.orly.dev/pkg/nostr/interfaces/signer/p8k"
  11  	"next.orly.dev/pkg/lol/chk"
  12  )
  13  
  14  // =============================================================================
  15  // Default-Permissive Access Control Tests
  16  // =============================================================================
  17  
  18  // TestDefaultPermissiveRead tests that read access is allowed by default
  19  // when no read restrictions are configured.
  20  func TestDefaultPermissiveRead(t *testing.T) {
  21  	// No read restrictions configured
  22  	policyJSON := []byte(`{
  23  		"default_policy": "deny",
  24  		"rules": {
  25  			"1": {
  26  				"description": "No read restrictions"
  27  			}
  28  		}
  29  	}`)
  30  
  31  	policy, err := New(policyJSON)
  32  	if err != nil {
  33  		t.Fatalf("Failed to create policy: %v", err)
  34  	}
  35  
  36  	authorSigner, authorPubkey := generateTestKeypair(t)
  37  	_, readerPubkey := generateTestKeypair(t)
  38  	_, randomPubkey := generateTestKeypair(t)
  39  
  40  	ev := createTestEvent(t, authorSigner, "test content", 1)
  41  
  42  	tests := []struct {
  43  		name        string
  44  		pubkey      []byte
  45  		expectAllow bool
  46  	}{
  47  		{
  48  			name:        "author can read (default permissive)",
  49  			pubkey:      authorPubkey,
  50  			expectAllow: true,
  51  		},
  52  		{
  53  			name:        "reader can read (default permissive)",
  54  			pubkey:      readerPubkey,
  55  			expectAllow: true,
  56  		},
  57  		{
  58  			name:        "random user can read (default permissive)",
  59  			pubkey:      randomPubkey,
  60  			expectAllow: true,
  61  		},
  62  		{
  63  			name:        "nil pubkey can read (default permissive)",
  64  			pubkey:      nil,
  65  			expectAllow: true,
  66  		},
  67  	}
  68  
  69  	for _, tt := range tests {
  70  		t.Run(tt.name, func(t *testing.T) {
  71  			allowed, err := policy.CheckPolicy("read", ev, tt.pubkey, "127.0.0.1")
  72  			if err != nil {
  73  				t.Fatalf("CheckPolicy error: %v", err)
  74  			}
  75  			if allowed != tt.expectAllow {
  76  				t.Errorf("CheckPolicy() = %v, expected %v", allowed, tt.expectAllow)
  77  			}
  78  		})
  79  	}
  80  }
  81  
  82  // TestDefaultPermissiveWrite tests that write access is allowed by default
  83  // when no write restrictions are configured.
  84  func TestDefaultPermissiveWrite(t *testing.T) {
  85  	// No write restrictions configured
  86  	policyJSON := []byte(`{
  87  		"default_policy": "deny",
  88  		"rules": {
  89  			"1": {
  90  				"description": "No write restrictions"
  91  			}
  92  		}
  93  	}`)
  94  
  95  	policy, err := New(policyJSON)
  96  	if err != nil {
  97  		t.Fatalf("Failed to create policy: %v", err)
  98  	}
  99  
 100  	writerSigner, writerPubkey := generateTestKeypair(t)
 101  	_, randomPubkey := generateTestKeypair(t)
 102  
 103  	tests := []struct {
 104  		name        string
 105  		signer      *p8k.Signer
 106  		pubkey      []byte
 107  		expectAllow bool
 108  	}{
 109  		{
 110  			name:        "writer can write (default permissive)",
 111  			signer:      writerSigner,
 112  			pubkey:      writerPubkey,
 113  			expectAllow: true,
 114  		},
 115  		{
 116  			name:        "random user can write (default permissive)",
 117  			signer:      writerSigner,
 118  			pubkey:      randomPubkey,
 119  			expectAllow: true,
 120  		},
 121  	}
 122  
 123  	for _, tt := range tests {
 124  		t.Run(tt.name, func(t *testing.T) {
 125  			ev := createTestEvent(t, tt.signer, "test content", 1)
 126  			allowed, err := policy.CheckPolicy("write", ev, tt.pubkey, "127.0.0.1")
 127  			if err != nil {
 128  				t.Fatalf("CheckPolicy error: %v", err)
 129  			}
 130  			if allowed != tt.expectAllow {
 131  				t.Errorf("CheckPolicy() = %v, expected %v", allowed, tt.expectAllow)
 132  			}
 133  		})
 134  	}
 135  }
 136  
 137  // TestReadFollowsWhitelist tests the read_follows_whitelist field.
 138  func TestReadFollowsWhitelist(t *testing.T) {
 139  	_, curatorPubkey := generateTestKeypair(t)
 140  	_, followedPubkey := generateTestKeypair(t)
 141  	_, unfollowedPubkey := generateTestKeypair(t)
 142  	authorSigner, authorPubkey := generateTestKeypair(t)
 143  
 144  	curatorHex := hex.Enc(curatorPubkey)
 145  
 146  	policyJSON := []byte(`{
 147  		"default_policy": "deny",
 148  		"rules": {
 149  			"1": {
 150  				"description": "Only curator follows can read",
 151  				"read_follows_whitelist": ["` + curatorHex + `"]
 152  			}
 153  		}
 154  	}`)
 155  
 156  	policy, err := New(policyJSON)
 157  	if err != nil {
 158  		t.Fatalf("Failed to create policy: %v", err)
 159  	}
 160  
 161  	// Simulate loading curator's follows (includes followed user and curator themselves)
 162  	policy.UpdateRuleReadFollowsWhitelist(1, [][]byte{followedPubkey})
 163  
 164  	ev := createTestEvent(t, authorSigner, "test content", 1)
 165  
 166  	tests := []struct {
 167  		name        string
 168  		pubkey      []byte
 169  		expectAllow bool
 170  	}{
 171  		{
 172  			name:        "curator can read (is in whitelist pubkeys)",
 173  			pubkey:      curatorPubkey,
 174  			expectAllow: true,
 175  		},
 176  		{
 177  			name:        "followed user can read",
 178  			pubkey:      followedPubkey,
 179  			expectAllow: true,
 180  		},
 181  		{
 182  			name:        "unfollowed user denied",
 183  			pubkey:      unfollowedPubkey,
 184  			expectAllow: false,
 185  		},
 186  		{
 187  			name:        "author cannot read (not in follows)",
 188  			pubkey:      authorPubkey,
 189  			expectAllow: false,
 190  		},
 191  		{
 192  			name:        "nil pubkey denied",
 193  			pubkey:      nil,
 194  			expectAllow: false,
 195  		},
 196  	}
 197  
 198  	for _, tt := range tests {
 199  		t.Run(tt.name, func(t *testing.T) {
 200  			allowed, err := policy.CheckPolicy("read", ev, tt.pubkey, "127.0.0.1")
 201  			if err != nil {
 202  				t.Fatalf("CheckPolicy error: %v", err)
 203  			}
 204  			if allowed != tt.expectAllow {
 205  				t.Errorf("CheckPolicy() = %v, expected %v", allowed, tt.expectAllow)
 206  			}
 207  		})
 208  	}
 209  
 210  	// Verify write is still default-permissive (no write restriction)
 211  	t.Run("write is still default permissive", func(t *testing.T) {
 212  		allowed, err := policy.CheckPolicy("write", ev, unfollowedPubkey, "127.0.0.1")
 213  		if err != nil {
 214  			t.Fatalf("CheckPolicy error: %v", err)
 215  		}
 216  		if !allowed {
 217  			t.Error("Expected write to be allowed (no write restriction)")
 218  		}
 219  	})
 220  }
 221  
 222  // TestWriteFollowsWhitelist tests the write_follows_whitelist field.
 223  func TestWriteFollowsWhitelist(t *testing.T) {
 224  	moderatorSigner, moderatorPubkey := generateTestKeypair(t)
 225  	followedSigner, followedPubkey := generateTestKeypair(t)
 226  	unfollowedSigner, unfollowedPubkey := generateTestKeypair(t)
 227  
 228  	moderatorHex := hex.Enc(moderatorPubkey)
 229  
 230  	policyJSON := []byte(`{
 231  		"default_policy": "deny",
 232  		"rules": {
 233  			"1": {
 234  				"description": "Only moderator follows can write",
 235  				"write_follows_whitelist": ["` + moderatorHex + `"]
 236  			}
 237  		}
 238  	}`)
 239  
 240  	policy, err := New(policyJSON)
 241  	if err != nil {
 242  		t.Fatalf("Failed to create policy: %v", err)
 243  	}
 244  
 245  	// Simulate loading moderator's follows
 246  	policy.UpdateRuleWriteFollowsWhitelist(1, [][]byte{followedPubkey})
 247  
 248  	tests := []struct {
 249  		name        string
 250  		signer      *p8k.Signer
 251  		pubkey      []byte
 252  		expectAllow bool
 253  	}{
 254  		{
 255  			name:        "moderator can write (is in whitelist pubkeys)",
 256  			signer:      moderatorSigner,
 257  			pubkey:      moderatorPubkey,
 258  			expectAllow: true,
 259  		},
 260  		{
 261  			name:        "followed user can write",
 262  			signer:      followedSigner,
 263  			pubkey:      followedPubkey,
 264  			expectAllow: true,
 265  		},
 266  		{
 267  			name:        "unfollowed user denied",
 268  			signer:      unfollowedSigner,
 269  			pubkey:      unfollowedPubkey,
 270  			expectAllow: false,
 271  		},
 272  	}
 273  
 274  	for _, tt := range tests {
 275  		t.Run(tt.name, func(t *testing.T) {
 276  			ev := createTestEvent(t, tt.signer, "test content", 1)
 277  			allowed, err := policy.CheckPolicy("write", ev, tt.pubkey, "127.0.0.1")
 278  			if err != nil {
 279  				t.Fatalf("CheckPolicy error: %v", err)
 280  			}
 281  			if allowed != tt.expectAllow {
 282  				t.Errorf("CheckPolicy() = %v, expected %v", allowed, tt.expectAllow)
 283  			}
 284  		})
 285  	}
 286  
 287  	// Verify read is still default-permissive (no read restriction)
 288  	t.Run("read is still default permissive", func(t *testing.T) {
 289  		ev := createTestEvent(t, unfollowedSigner, "test content", 1)
 290  		allowed, err := policy.CheckPolicy("read", ev, unfollowedPubkey, "127.0.0.1")
 291  		if err != nil {
 292  			t.Fatalf("CheckPolicy error: %v", err)
 293  		}
 294  		if !allowed {
 295  			t.Error("Expected read to be allowed (no read restriction)")
 296  		}
 297  	})
 298  }
 299  
 300  // TestGlobalReadFollowsWhitelist tests read_follows_whitelist in global rule.
 301  func TestGlobalReadFollowsWhitelist(t *testing.T) {
 302  	_, curatorPubkey := generateTestKeypair(t)
 303  	_, followedPubkey := generateTestKeypair(t)
 304  	_, unfollowedPubkey := generateTestKeypair(t)
 305  	authorSigner, _ := generateTestKeypair(t)
 306  
 307  	curatorHex := hex.Enc(curatorPubkey)
 308  
 309  	policyJSON := []byte(`{
 310  		"default_policy": "deny",
 311  		"global": {
 312  			"description": "Global read follows whitelist",
 313  			"read_follows_whitelist": ["` + curatorHex + `"]
 314  		}
 315  	}`)
 316  
 317  	policy, err := New(policyJSON)
 318  	if err != nil {
 319  		t.Fatalf("Failed to create policy: %v", err)
 320  	}
 321  
 322  	// Update global read follows whitelist
 323  	policy.UpdateGlobalReadFollowsWhitelist([][]byte{followedPubkey})
 324  
 325  	// Test with kind 1
 326  	t.Run("kind 1", func(t *testing.T) {
 327  		ev := createTestEvent(t, authorSigner, "test content", 1)
 328  
 329  		// Followed user can read
 330  		allowed, err := policy.CheckPolicy("read", ev, followedPubkey, "127.0.0.1")
 331  		if err != nil {
 332  			t.Fatalf("CheckPolicy error: %v", err)
 333  		}
 334  		if !allowed {
 335  			t.Error("Expected followed user to be allowed to read")
 336  		}
 337  
 338  		// Unfollowed user denied
 339  		allowed, err = policy.CheckPolicy("read", ev, unfollowedPubkey, "127.0.0.1")
 340  		if err != nil {
 341  			t.Fatalf("CheckPolicy error: %v", err)
 342  		}
 343  		if allowed {
 344  			t.Error("Expected unfollowed user to be denied")
 345  		}
 346  	})
 347  }
 348  
 349  // TestGlobalWriteFollowsWhitelist tests write_follows_whitelist in global rule.
 350  func TestGlobalWriteFollowsWhitelist(t *testing.T) {
 351  	_, moderatorPubkey := generateTestKeypair(t)
 352  	followedSigner, followedPubkey := generateTestKeypair(t)
 353  	unfollowedSigner, unfollowedPubkey := generateTestKeypair(t)
 354  
 355  	moderatorHex := hex.Enc(moderatorPubkey)
 356  
 357  	policyJSON := []byte(`{
 358  		"default_policy": "deny",
 359  		"global": {
 360  			"description": "Global write follows whitelist",
 361  			"write_follows_whitelist": ["` + moderatorHex + `"]
 362  		}
 363  	}`)
 364  
 365  	policy, err := New(policyJSON)
 366  	if err != nil {
 367  		t.Fatalf("Failed to create policy: %v", err)
 368  	}
 369  
 370  	// Update global write follows whitelist
 371  	policy.UpdateGlobalWriteFollowsWhitelist([][]byte{followedPubkey})
 372  
 373  	// Test with kind 1
 374  	t.Run("kind 1", func(t *testing.T) {
 375  		// Followed user can write
 376  		ev := createTestEvent(t, followedSigner, "test content", 1)
 377  		allowed, err := policy.CheckPolicy("write", ev, followedPubkey, "127.0.0.1")
 378  		if err != nil {
 379  			t.Fatalf("CheckPolicy error: %v", err)
 380  		}
 381  		if !allowed {
 382  			t.Error("Expected followed user to be allowed to write")
 383  		}
 384  
 385  		// Unfollowed user denied
 386  		ev = createTestEvent(t, unfollowedSigner, "test content", 1)
 387  		allowed, err = policy.CheckPolicy("write", ev, unfollowedPubkey, "127.0.0.1")
 388  		if err != nil {
 389  			t.Fatalf("CheckPolicy error: %v", err)
 390  		}
 391  		if allowed {
 392  			t.Error("Expected unfollowed user to be denied")
 393  		}
 394  	})
 395  }
 396  
 397  // TestPrivilegedOnlyAppliesToReadDP tests that privileged only affects read access.
 398  func TestPrivilegedOnlyAppliesToReadDP(t *testing.T) {
 399  	authorSigner, authorPubkey := generateTestKeypair(t)
 400  	_, recipientPubkey := generateTestKeypair(t)
 401  	thirdPartySigner, thirdPartyPubkey := generateTestKeypair(t)
 402  
 403  	policyJSON := []byte(`{
 404  		"default_policy": "deny",
 405  		"rules": {
 406  			"4": {
 407  				"description": "Encrypted DMs - privileged",
 408  				"privileged": true
 409  			}
 410  		}
 411  	}`)
 412  
 413  	policy, err := New(policyJSON)
 414  	if err != nil {
 415  		t.Fatalf("Failed to create policy: %v", err)
 416  	}
 417  
 418  	// Create event with p-tag for recipient
 419  	ev := event.New()
 420  	ev.Kind = 4
 421  	ev.Content = []byte("encrypted content")
 422  	ev.CreatedAt = time.Now().Unix()
 423  	ev.Tags = tag.NewS()
 424  	pTag := tag.NewFromAny("p", hex.Enc(recipientPubkey))
 425  	ev.Tags.Append(pTag)
 426  	if err := ev.Sign(authorSigner); chk.E(err) {
 427  		t.Fatalf("Failed to sign event: %v", err)
 428  	}
 429  
 430  	// READ tests
 431  	t.Run("author can read", func(t *testing.T) {
 432  		allowed, err := policy.CheckPolicy("read", ev, authorPubkey, "127.0.0.1")
 433  		if err != nil {
 434  			t.Fatalf("CheckPolicy error: %v", err)
 435  		}
 436  		if !allowed {
 437  			t.Error("Expected author to be allowed to read")
 438  		}
 439  	})
 440  
 441  	t.Run("recipient can read", func(t *testing.T) {
 442  		allowed, err := policy.CheckPolicy("read", ev, recipientPubkey, "127.0.0.1")
 443  		if err != nil {
 444  			t.Fatalf("CheckPolicy error: %v", err)
 445  		}
 446  		if !allowed {
 447  			t.Error("Expected recipient to be allowed to read")
 448  		}
 449  	})
 450  
 451  	t.Run("third party cannot read", func(t *testing.T) {
 452  		allowed, err := policy.CheckPolicy("read", ev, thirdPartyPubkey, "127.0.0.1")
 453  		if err != nil {
 454  			t.Fatalf("CheckPolicy error: %v", err)
 455  		}
 456  		if allowed {
 457  			t.Error("Expected third party to be denied read access")
 458  		}
 459  	})
 460  
 461  	// WRITE tests - privileged should NOT affect write
 462  	t.Run("third party CAN write (privileged doesn't affect write)", func(t *testing.T) {
 463  		ev := createTestEvent(t, thirdPartySigner, "test content", 4)
 464  		allowed, err := policy.CheckPolicy("write", ev, thirdPartyPubkey, "127.0.0.1")
 465  		if err != nil {
 466  			t.Fatalf("CheckPolicy error: %v", err)
 467  		}
 468  		if !allowed {
 469  			t.Error("Expected third party to be allowed to write (privileged doesn't restrict write)")
 470  		}
 471  	})
 472  }
 473  
 474  // TestCombinedReadWriteFollowsWhitelists tests using both whitelists on same rule.
 475  func TestCombinedReadWriteFollowsWhitelists(t *testing.T) {
 476  	_, curatorPubkey := generateTestKeypair(t)
 477  	_, moderatorPubkey := generateTestKeypair(t)
 478  	readerSigner, readerPubkey := generateTestKeypair(t)
 479  	writerSigner, writerPubkey := generateTestKeypair(t)
 480  	_, outsiderPubkey := generateTestKeypair(t)
 481  
 482  	curatorHex := hex.Enc(curatorPubkey)
 483  	moderatorHex := hex.Enc(moderatorPubkey)
 484  
 485  	policyJSON := []byte(`{
 486  		"default_policy": "deny",
 487  		"rules": {
 488  			"30023": {
 489  				"description": "Articles - different read/write follows",
 490  				"read_follows_whitelist": ["` + curatorHex + `"],
 491  				"write_follows_whitelist": ["` + moderatorHex + `"]
 492  			}
 493  		}
 494  	}`)
 495  
 496  	policy, err := New(policyJSON)
 497  	if err != nil {
 498  		t.Fatalf("Failed to create policy: %v", err)
 499  	}
 500  
 501  	// Curator follows reader, moderator follows writer
 502  	policy.UpdateRuleReadFollowsWhitelist(30023, [][]byte{readerPubkey})
 503  	policy.UpdateRuleWriteFollowsWhitelist(30023, [][]byte{writerPubkey})
 504  
 505  	tests := []struct {
 506  		name        string
 507  		access      string
 508  		signer      *p8k.Signer
 509  		pubkey      []byte
 510  		expectAllow bool
 511  	}{
 512  		// Read tests
 513  		{
 514  			name:        "reader can read",
 515  			access:      "read",
 516  			signer:      readerSigner,
 517  			pubkey:      readerPubkey,
 518  			expectAllow: true,
 519  		},
 520  		{
 521  			name:        "writer cannot read (not in read follows)",
 522  			access:      "read",
 523  			signer:      writerSigner,
 524  			pubkey:      writerPubkey,
 525  			expectAllow: false,
 526  		},
 527  		{
 528  			name:        "outsider cannot read",
 529  			access:      "read",
 530  			signer:      readerSigner,
 531  			pubkey:      outsiderPubkey,
 532  			expectAllow: false,
 533  		},
 534  		// Write tests
 535  		{
 536  			name:        "writer can write",
 537  			access:      "write",
 538  			signer:      writerSigner,
 539  			pubkey:      writerPubkey,
 540  			expectAllow: true,
 541  		},
 542  		{
 543  			name:        "reader cannot write (not in write follows)",
 544  			access:      "write",
 545  			signer:      readerSigner,
 546  			pubkey:      readerPubkey,
 547  			expectAllow: false,
 548  		},
 549  		{
 550  			name:        "outsider cannot write",
 551  			access:      "write",
 552  			signer:      readerSigner,
 553  			pubkey:      outsiderPubkey,
 554  			expectAllow: false,
 555  		},
 556  	}
 557  
 558  	for _, tt := range tests {
 559  		t.Run(tt.name, func(t *testing.T) {
 560  			ev := createTestEvent(t, tt.signer, "test content", 30023)
 561  			allowed, err := policy.CheckPolicy(tt.access, ev, tt.pubkey, "127.0.0.1")
 562  			if err != nil {
 563  				t.Fatalf("CheckPolicy error: %v", err)
 564  			}
 565  			if allowed != tt.expectAllow {
 566  				t.Errorf("CheckPolicy() = %v, expected %v", allowed, tt.expectAllow)
 567  			}
 568  		})
 569  	}
 570  }
 571  
 572  // TestReadAllowWithReadFollowsWhitelist tests combining read_allow and read_follows_whitelist.
 573  func TestReadAllowWithReadFollowsWhitelist(t *testing.T) {
 574  	_, curatorPubkey := generateTestKeypair(t)
 575  	_, followedPubkey := generateTestKeypair(t)
 576  	_, explicitPubkey := generateTestKeypair(t)
 577  	_, outsiderPubkey := generateTestKeypair(t)
 578  	authorSigner, _ := generateTestKeypair(t)
 579  
 580  	curatorHex := hex.Enc(curatorPubkey)
 581  	explicitHex := hex.Enc(explicitPubkey)
 582  
 583  	policyJSON := []byte(`{
 584  		"default_policy": "deny",
 585  		"rules": {
 586  			"1": {
 587  				"description": "Read via follows OR explicit allow",
 588  				"read_follows_whitelist": ["` + curatorHex + `"],
 589  				"read_allow": ["` + explicitHex + `"]
 590  			}
 591  		}
 592  	}`)
 593  
 594  	policy, err := New(policyJSON)
 595  	if err != nil {
 596  		t.Fatalf("Failed to create policy: %v", err)
 597  	}
 598  
 599  	policy.UpdateRuleReadFollowsWhitelist(1, [][]byte{followedPubkey})
 600  
 601  	ev := createTestEvent(t, authorSigner, "test content", 1)
 602  
 603  	tests := []struct {
 604  		name        string
 605  		pubkey      []byte
 606  		expectAllow bool
 607  	}{
 608  		{
 609  			name:        "followed user can read",
 610  			pubkey:      followedPubkey,
 611  			expectAllow: true,
 612  		},
 613  		{
 614  			name:        "explicit allow user can read",
 615  			pubkey:      explicitPubkey,
 616  			expectAllow: true,
 617  		},
 618  		{
 619  			name:        "curator can read (is whitelist pubkey)",
 620  			pubkey:      curatorPubkey,
 621  			expectAllow: true,
 622  		},
 623  		{
 624  			name:        "outsider denied",
 625  			pubkey:      outsiderPubkey,
 626  			expectAllow: false,
 627  		},
 628  	}
 629  
 630  	for _, tt := range tests {
 631  		t.Run(tt.name, func(t *testing.T) {
 632  			allowed, err := policy.CheckPolicy("read", ev, tt.pubkey, "127.0.0.1")
 633  			if err != nil {
 634  				t.Fatalf("CheckPolicy error: %v", err)
 635  			}
 636  			if allowed != tt.expectAllow {
 637  				t.Errorf("CheckPolicy() = %v, expected %v", allowed, tt.expectAllow)
 638  			}
 639  		})
 640  	}
 641  }
 642  
 643  // TestGetAllFollowsWhitelistPubkeysDP tests the combined pubkey retrieval.
 644  func TestGetAllFollowsWhitelistPubkeysDP(t *testing.T) {
 645  	read1 := "1111111111111111111111111111111111111111111111111111111111111111"
 646  	read2 := "2222222222222222222222222222222222222222222222222222222222222222"
 647  	write1 := "3333333333333333333333333333333333333333333333333333333333333333"
 648  	legacy := "4444444444444444444444444444444444444444444444444444444444444444"
 649  
 650  	policyJSON := []byte(`{
 651  		"default_policy": "allow",
 652  		"global": {
 653  			"read_follows_whitelist": ["` + read1 + `"],
 654  			"write_follows_whitelist": ["` + write1 + `"]
 655  		},
 656  		"rules": {
 657  			"1": {
 658  				"read_follows_whitelist": ["` + read2 + `"],
 659  				"follows_whitelist_admins": ["` + legacy + `"]
 660  			}
 661  		}
 662  	}`)
 663  
 664  	policy, err := New(policyJSON)
 665  	if err != nil {
 666  		t.Fatalf("Failed to create policy: %v", err)
 667  	}
 668  
 669  	allPubkeys := policy.GetAllFollowsWhitelistPubkeys()
 670  	if len(allPubkeys) != 4 {
 671  		t.Errorf("Expected 4 unique pubkeys, got %d", len(allPubkeys))
 672  	}
 673  
 674  	// Check each is present
 675  	pubkeySet := make(map[string]bool)
 676  	for _, pk := range allPubkeys {
 677  		pubkeySet[pk] = true
 678  	}
 679  
 680  	expected := []string{read1, read2, write1, legacy}
 681  	for _, exp := range expected {
 682  		if !pubkeySet[exp] {
 683  			t.Errorf("Expected pubkey %s not found", exp)
 684  		}
 685  	}
 686  }
 687  
 688  // TestWriteAllowIfTagged tests the write_allow_if_tagged field.
 689  func TestWriteAllowIfTagged(t *testing.T) {
 690  	// Group A: unrestricted writers
 691  	groupASigner, groupAPubkey := generateTestKeypair(t)
 692  	groupAHex := hex.Enc(groupAPubkey)
 693  
 694  	// Group B: can only write if they p-tag a Group A member
 695  	groupBSigner, groupBPubkey := generateTestKeypair(t)
 696  
 697  	// Random user: no special access
 698  	randomSigner, randomPubkey := generateTestKeypair(t)
 699  
 700  	policyJSON := []byte(`{
 701  		"default_policy": "deny",
 702  		"kind": { "whitelist": [1] },
 703  		"rules": {
 704  			"1": {
 705  				"description": "Group A writes freely, others must tag Group A",
 706  				"write_allow": ["` + groupAHex + `"],
 707  				"write_allow_if_tagged": ["` + groupAHex + `"]
 708  			}
 709  		}
 710  	}`)
 711  
 712  	pol, err := New(policyJSON)
 713  	if err != nil {
 714  		t.Fatalf("Failed to create policy: %v", err)
 715  	}
 716  
 717  	t.Run("group_A_writes_freely", func(t *testing.T) {
 718  		ev := createTestEvent(t, groupASigner, "hello", 1)
 719  		allowed, err := pol.CheckPolicy("write", ev, groupAPubkey, "127.0.0.1")
 720  		if err != nil {
 721  			t.Fatalf("CheckPolicy error: %v", err)
 722  		}
 723  		if !allowed {
 724  			t.Error("Group A pubkey should be allowed to write freely")
 725  		}
 726  	})
 727  
 728  	t.Run("group_B_with_p_tag_allowed", func(t *testing.T) {
 729  		ev := createTestEvent(t, groupBSigner, "replying to group A", 1)
 730  		addPTag(ev, groupAPubkey)
 731  		allowed, err := pol.CheckPolicy("write", ev, groupBPubkey, "127.0.0.1")
 732  		if err != nil {
 733  			t.Fatalf("CheckPolicy error: %v", err)
 734  		}
 735  		if !allowed {
 736  			t.Error("Group B pubkey with p-tag to Group A should be allowed")
 737  		}
 738  	})
 739  
 740  	t.Run("group_B_without_p_tag_denied", func(t *testing.T) {
 741  		ev := createTestEvent(t, groupBSigner, "no tag", 1)
 742  		allowed, err := pol.CheckPolicy("write", ev, groupBPubkey, "127.0.0.1")
 743  		if err != nil {
 744  			t.Fatalf("CheckPolicy error: %v", err)
 745  		}
 746  		if allowed {
 747  			t.Error("Group B pubkey without p-tag should be denied")
 748  		}
 749  	})
 750  
 751  	t.Run("random_user_with_p_tag_allowed", func(t *testing.T) {
 752  		ev := createTestEvent(t, randomSigner, "mentioning group A", 1)
 753  		addPTag(ev, groupAPubkey)
 754  		allowed, err := pol.CheckPolicy("write", ev, randomPubkey, "127.0.0.1")
 755  		if err != nil {
 756  			t.Fatalf("CheckPolicy error: %v", err)
 757  		}
 758  		if !allowed {
 759  			t.Error("Random user with p-tag to Group A should be allowed")
 760  		}
 761  	})
 762  
 763  	t.Run("random_user_without_p_tag_denied", func(t *testing.T) {
 764  		ev := createTestEvent(t, randomSigner, "no tag", 1)
 765  		allowed, err := pol.CheckPolicy("write", ev, randomPubkey, "127.0.0.1")
 766  		if err != nil {
 767  			t.Fatalf("CheckPolicy error: %v", err)
 768  		}
 769  		if allowed {
 770  			t.Error("Random user without p-tag should be denied")
 771  		}
 772  	})
 773  }
 774  
 775  // TestWriteAllowIfTaggedStandalone tests write_allow_if_tagged without write_allow.
 776  func TestWriteAllowIfTaggedStandalone(t *testing.T) {
 777  	_, gatePubkey := generateTestKeypair(t)
 778  	gateHex := hex.Enc(gatePubkey)
 779  
 780  	writerSigner, writerPubkey := generateTestKeypair(t)
 781  
 782  	policyJSON := []byte(`{
 783  		"default_policy": "deny",
 784  		"kind": { "whitelist": [1] },
 785  		"rules": {
 786  			"1": {
 787  				"description": "Anyone can write if they tag the gate pubkey",
 788  				"write_allow_if_tagged": ["` + gateHex + `"]
 789  			}
 790  		}
 791  	}`)
 792  
 793  	pol, err := New(policyJSON)
 794  	if err != nil {
 795  		t.Fatalf("Failed to create policy: %v", err)
 796  	}
 797  
 798  	t.Run("with_tag_allowed", func(t *testing.T) {
 799  		ev := createTestEvent(t, writerSigner, "tagged", 1)
 800  		addPTag(ev, gatePubkey)
 801  		allowed, err := pol.CheckPolicy("write", ev, writerPubkey, "127.0.0.1")
 802  		if err != nil {
 803  			t.Fatalf("CheckPolicy error: %v", err)
 804  		}
 805  		if !allowed {
 806  			t.Error("Should be allowed when tagging gate pubkey")
 807  		}
 808  	})
 809  
 810  	t.Run("without_tag_denied", func(t *testing.T) {
 811  		ev := createTestEvent(t, writerSigner, "no tag", 1)
 812  		allowed, err := pol.CheckPolicy("write", ev, writerPubkey, "127.0.0.1")
 813  		if err != nil {
 814  			t.Fatalf("CheckPolicy error: %v", err)
 815  		}
 816  		if allowed {
 817  			t.Error("Should be denied without tagging gate pubkey")
 818  		}
 819  	})
 820  }
 821