Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
042b47a4d9
|
@@ -113,11 +113,11 @@ func TestMaxExpiryDuration(t *testing.T) {
|
||||
expectAllow: true,
|
||||
},
|
||||
{
|
||||
name: "valid expiry at exact limit",
|
||||
name: "expiry at exact limit rejected",
|
||||
maxExpiryDuration: "PT1H",
|
||||
eventExpiry: 3600, // exactly 1 hour
|
||||
eventExpiry: 3600, // exactly 1 hour - >= means this is rejected
|
||||
hasExpiryTag: true,
|
||||
expectAllow: true,
|
||||
expectAllow: false,
|
||||
},
|
||||
{
|
||||
name: "expiry exceeds limit",
|
||||
@@ -235,6 +235,79 @@ func TestMaxExpiryDurationPrecedence(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Test that max_expiry_duration only applies to writes, not reads
|
||||
func TestMaxExpiryDurationWriteOnly(t *testing.T) {
|
||||
signer, pubkey := generateTestKeypair(t)
|
||||
|
||||
// Policy with strict max_expiry_duration
|
||||
policyJSON := []byte(`{
|
||||
"default_policy": "allow",
|
||||
"rules": {
|
||||
"4": {
|
||||
"description": "DM events with expiry",
|
||||
"max_expiry_duration": "PT10M",
|
||||
"privileged": true
|
||||
}
|
||||
}
|
||||
}`)
|
||||
|
||||
policy, err := New(policyJSON)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create policy: %v", err)
|
||||
}
|
||||
|
||||
// Create event WITHOUT an expiry tag - this would fail write validation
|
||||
// but should still be readable
|
||||
ev := createTestEventForNewFields(t, signer, "test DM", 4)
|
||||
if err := ev.Sign(signer); chk.E(err) {
|
||||
t.Fatalf("Failed to sign: %v", err)
|
||||
}
|
||||
|
||||
// Write should fail (no expiry tag when max_expiry_duration is set)
|
||||
allowed, err := policy.CheckPolicy("write", ev, pubkey, "127.0.0.1")
|
||||
if err != nil {
|
||||
t.Fatalf("CheckPolicy write error: %v", err)
|
||||
}
|
||||
if allowed {
|
||||
t.Error("Write should be denied for event without expiry tag when max_expiry_duration is set")
|
||||
}
|
||||
|
||||
// Read should succeed (validation constraints don't apply to reads)
|
||||
allowed, err = policy.CheckPolicy("read", ev, pubkey, "127.0.0.1")
|
||||
if err != nil {
|
||||
t.Fatalf("CheckPolicy read error: %v", err)
|
||||
}
|
||||
if !allowed {
|
||||
t.Error("Read should be allowed - max_expiry_duration is write-only validation")
|
||||
}
|
||||
|
||||
// Also test with an event that has expiry exceeding the limit
|
||||
ev2 := createTestEventForNewFields(t, signer, "test DM 2", 4)
|
||||
expiryTs := ev2.CreatedAt + 7200 // 2 hours - exceeds 10 minute limit
|
||||
addTagString(ev2, "expiration", int64ToString(expiryTs))
|
||||
if err := ev2.Sign(signer); chk.E(err) {
|
||||
t.Fatalf("Failed to sign: %v", err)
|
||||
}
|
||||
|
||||
// Write should fail (expiry exceeds limit)
|
||||
allowed, err = policy.CheckPolicy("write", ev2, pubkey, "127.0.0.1")
|
||||
if err != nil {
|
||||
t.Fatalf("CheckPolicy write error: %v", err)
|
||||
}
|
||||
if allowed {
|
||||
t.Error("Write should be denied for event with expiry exceeding max_expiry_duration")
|
||||
}
|
||||
|
||||
// Read should still succeed
|
||||
allowed, err = policy.CheckPolicy("read", ev2, pubkey, "127.0.0.1")
|
||||
if err != nil {
|
||||
t.Fatalf("CheckPolicy read error: %v", err)
|
||||
}
|
||||
if !allowed {
|
||||
t.Error("Read should be allowed - max_expiry_duration is write-only validation")
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ProtectedRequired Tests
|
||||
// =============================================================================
|
||||
|
||||
@@ -1280,7 +1280,8 @@ func (p *P) checkRulePolicy(
|
||||
}
|
||||
|
||||
// Check required tags
|
||||
if len(rule.MustHaveTags) > 0 {
|
||||
// Only apply for write access - we validate what goes in, not what comes out
|
||||
if access == "write" && len(rule.MustHaveTags) > 0 {
|
||||
for _, requiredTag := range rule.MustHaveTags {
|
||||
if ev.Tags.GetFirst([]byte(requiredTag)) == nil {
|
||||
return false, nil
|
||||
@@ -1289,7 +1290,8 @@ func (p *P) checkRulePolicy(
|
||||
}
|
||||
|
||||
// Check expiry time (uses maxExpirySeconds which is parsed from MaxExpiryDuration or MaxExpiry)
|
||||
if rule.maxExpirySeconds != nil && *rule.maxExpirySeconds > 0 {
|
||||
// Only apply for write access - we validate what goes in, not what comes out
|
||||
if access == "write" && rule.maxExpirySeconds != nil && *rule.maxExpirySeconds > 0 {
|
||||
expiryTag := ev.Tags.GetFirst([]byte("expiration"))
|
||||
if expiryTag == nil {
|
||||
return false, nil // Must have expiry if max_expiry is set
|
||||
@@ -1302,7 +1304,7 @@ func (p *P) checkRulePolicy(
|
||||
return false, nil // Invalid expiry format
|
||||
}
|
||||
maxAllowedExpiry := ev.CreatedAt + *rule.maxExpirySeconds
|
||||
if expiryTs > maxAllowedExpiry {
|
||||
if expiryTs >= maxAllowedExpiry {
|
||||
log.D.F("expiration %d exceeds max allowed %d (created_at %d + max_expiry %d)",
|
||||
expiryTs, maxAllowedExpiry, ev.CreatedAt, *rule.maxExpirySeconds)
|
||||
return false, nil // Expiry too far in the future
|
||||
@@ -1310,7 +1312,8 @@ func (p *P) checkRulePolicy(
|
||||
}
|
||||
|
||||
// Check ProtectedRequired (NIP-70: events must have "-" tag)
|
||||
if rule.ProtectedRequired {
|
||||
// Only apply for write access - we validate what goes in, not what comes out
|
||||
if access == "write" && rule.ProtectedRequired {
|
||||
protectedTag := ev.Tags.GetFirst([]byte("-"))
|
||||
if protectedTag == nil {
|
||||
log.D.F("protected_required: event missing '-' tag (NIP-70)")
|
||||
@@ -1319,7 +1322,8 @@ func (p *P) checkRulePolicy(
|
||||
}
|
||||
|
||||
// Check IdentifierRegex (validates "d" tag values)
|
||||
if rule.identifierRegexCache != nil {
|
||||
// Only apply for write access - we validate what goes in, not what comes out
|
||||
if access == "write" && rule.identifierRegexCache != nil {
|
||||
dTags := ev.Tags.GetAll([]byte("d"))
|
||||
if len(dTags) == 0 {
|
||||
log.D.F("identifier_regex: event missing 'd' tag")
|
||||
@@ -1336,7 +1340,8 @@ func (p *P) checkRulePolicy(
|
||||
}
|
||||
|
||||
// Check MaxAgeOfEvent (maximum age of event in seconds)
|
||||
if rule.MaxAgeOfEvent != nil && *rule.MaxAgeOfEvent > 0 {
|
||||
// Only apply for write access - we validate what goes in, not what comes out
|
||||
if access == "write" && rule.MaxAgeOfEvent != nil && *rule.MaxAgeOfEvent > 0 {
|
||||
currentTime := time.Now().Unix()
|
||||
maxAllowedTime := currentTime - *rule.MaxAgeOfEvent
|
||||
if ev.CreatedAt < maxAllowedTime {
|
||||
@@ -1345,7 +1350,8 @@ func (p *P) checkRulePolicy(
|
||||
}
|
||||
|
||||
// Check MaxAgeEventInFuture (maximum time event can be in the future in seconds)
|
||||
if rule.MaxAgeEventInFuture != nil && *rule.MaxAgeEventInFuture > 0 {
|
||||
// Only apply for write access - we validate what goes in, not what comes out
|
||||
if access == "write" && rule.MaxAgeEventInFuture != nil && *rule.MaxAgeEventInFuture > 0 {
|
||||
currentTime := time.Now().Unix()
|
||||
maxFutureTime := currentTime + *rule.MaxAgeEventInFuture
|
||||
if ev.CreatedAt > maxFutureTime {
|
||||
|
||||
@@ -1 +1 @@
|
||||
v0.31.6
|
||||
v0.31.7
|
||||
|
||||
Reference in New Issue
Block a user