atagen

vineyard

public
Star0
Watch0
25d14e49ab6861630869e2129d40652900bc5833
1
ssh://brewery@pls.lobotomise.me:33/atagen/vineyard.git

Blame: src/policy.rs

25d14e49ab68atagen2026-09-24 11:07 UTC1
use std::borrow::Cow;
25d14e49ab68atagen2026-09-24 11:07 UTC2
use std::collections::{HashMap, HashSet};
25d14e49ab68atagen2026-09-24 11:07 UTC3
use std::net::{IpAddr, SocketAddr};
25d14e49ab68atagen2026-09-24 11:07 UTC4
use std::sync::{Arc, Mutex, PoisonError};
25d14e49ab68atagen2026-09-24 11:07 UTC5
use std::time::{Duration, Instant};
25d14e49ab68atagen2026-09-24 11:07 UTC6
25d14e49ab68atagen2026-09-24 11:07 UTC7
use nostr::event::{Event, EventId, Kind};
25d14e49ab68atagen2026-09-24 11:07 UTC8
use nostr::filter::Filter;
25d14e49ab68atagen2026-09-24 11:07 UTC9
use nostr::key::PublicKey;
25d14e49ab68atagen2026-09-24 11:07 UTC10
use nostr::message::MachineReadablePrefix;
25d14e49ab68atagen2026-09-24 11:07 UTC11
use nostr::nips::nip65::extract_relay_list;
25d14e49ab68atagen2026-09-24 11:07 UTC12
use nostr::types::Timestamp;
25d14e49ab68atagen2026-09-24 11:07 UTC13
use nostr_database::{DatabaseEventStatus, NostrDatabase, error::Error};
25d14e49ab68atagen2026-09-24 11:07 UTC14
use serde::Deserialize;
25d14e49ab68atagen2026-09-24 11:07 UTC15
25d14e49ab68atagen2026-09-24 11:07 UTC16
use crate::config::{Config, Limits};
25d14e49ab68atagen2026-09-24 11:07 UTC17
25d14e49ab68atagen2026-09-24 11:07 UTC18
#[derive(Debug)]
25d14e49ab68atagen2026-09-24 11:07 UTC19
struct Usage {
25d14e49ab68atagen2026-09-24 11:07 UTC20
    window: Instant,
25d14e49ab68atagen2026-09-24 11:07 UTC21
    events: u32,
25d14e49ab68atagen2026-09-24 11:07 UTC22
    queries: u32,
25d14e49ab68atagen2026-09-24 11:07 UTC23
}
25d14e49ab68atagen2026-09-24 11:07 UTC24
25d14e49ab68atagen2026-09-24 11:07 UTC25
#[derive(Debug, Clone)]
25d14e49ab68atagen2026-09-24 11:07 UTC26
pub struct Policy {
25d14e49ab68atagen2026-09-24 11:07 UTC27
    database: Arc<dyn NostrDatabase>,
25d14e49ab68atagen2026-09-24 11:07 UTC28
    limits: Limits,
25d14e49ab68atagen2026-09-24 11:07 UTC29
    forge_keys: HashSet<PublicKey>,
25d14e49ab68atagen2026-09-24 11:07 UTC30
    peers: Arc<Mutex<HashMap<IpAddr, Usage>>>,
25d14e49ab68atagen2026-09-24 11:07 UTC31
}
25d14e49ab68atagen2026-09-24 11:07 UTC32
25d14e49ab68atagen2026-09-24 11:07 UTC33
#[derive(Deserialize)]
25d14e49ab68atagen2026-09-24 11:07 UTC34
#[serde(deny_unknown_fields)]
25d14e49ab68atagen2026-09-24 11:07 UTC35
struct Binding {
25d14e49ab68atagen2026-09-24 11:07 UTC36
    version: u64,
25d14e49ab68atagen2026-09-24 11:07 UTC37
    action: String,
25d14e49ab68atagen2026-09-24 11:07 UTC38
    fingerprint: String,
25d14e49ab68atagen2026-09-24 11:07 UTC39
    certificate: Option<String>,
25d14e49ab68atagen2026-09-24 11:07 UTC40
    signature: Option<String>,
25d14e49ab68atagen2026-09-24 11:07 UTC41
}
25d14e49ab68atagen2026-09-24 11:07 UTC42
25d14e49ab68atagen2026-09-24 11:07 UTC43
pub(crate) enum WritePolicyResult {
25d14e49ab68atagen2026-09-24 11:07 UTC44
    Accept,
25d14e49ab68atagen2026-09-24 11:07 UTC45
    Respond {
25d14e49ab68atagen2026-09-24 11:07 UTC46
        prefix: MachineReadablePrefix,
25d14e49ab68atagen2026-09-24 11:07 UTC47
        message: Cow<'static, str>,
25d14e49ab68atagen2026-09-24 11:07 UTC48
        status: bool,
25d14e49ab68atagen2026-09-24 11:07 UTC49
    },
25d14e49ab68atagen2026-09-24 11:07 UTC50
}
25d14e49ab68atagen2026-09-24 11:07 UTC51
25d14e49ab68atagen2026-09-24 11:07 UTC52
impl WritePolicyResult {
25d14e49ab68atagen2026-09-24 11:07 UTC53
    const fn accepted(prefix: MachineReadablePrefix, message: &'static str) -> Self {
25d14e49ab68atagen2026-09-24 11:07 UTC54
        Self::Respond {
25d14e49ab68atagen2026-09-24 11:07 UTC55
            prefix,
25d14e49ab68atagen2026-09-24 11:07 UTC56
            message: Cow::Borrowed(message),
25d14e49ab68atagen2026-09-24 11:07 UTC57
            status: true,
25d14e49ab68atagen2026-09-24 11:07 UTC58
        }
25d14e49ab68atagen2026-09-24 11:07 UTC59
    }
25d14e49ab68atagen2026-09-24 11:07 UTC60
25d14e49ab68atagen2026-09-24 11:07 UTC61
    const fn reject(prefix: MachineReadablePrefix, message: &'static str) -> Self {
25d14e49ab68atagen2026-09-24 11:07 UTC62
        Self::Respond {
25d14e49ab68atagen2026-09-24 11:07 UTC63
            prefix,
25d14e49ab68atagen2026-09-24 11:07 UTC64
            message: Cow::Borrowed(message),
25d14e49ab68atagen2026-09-24 11:07 UTC65
            status: false,
25d14e49ab68atagen2026-09-24 11:07 UTC66
        }
25d14e49ab68atagen2026-09-24 11:07 UTC67
    }
25d14e49ab68atagen2026-09-24 11:07 UTC68
}
25d14e49ab68atagen2026-09-24 11:07 UTC69
25d14e49ab68atagen2026-09-24 11:07 UTC70
pub(crate) enum QueryPolicyResult {
25d14e49ab68atagen2026-09-24 11:07 UTC71
    Accept,
25d14e49ab68atagen2026-09-24 11:07 UTC72
    Reject {
25d14e49ab68atagen2026-09-24 11:07 UTC73
        prefix: MachineReadablePrefix,
25d14e49ab68atagen2026-09-24 11:07 UTC74
        message: Cow<'static, str>,
25d14e49ab68atagen2026-09-24 11:07 UTC75
    },
25d14e49ab68atagen2026-09-24 11:07 UTC76
}
25d14e49ab68atagen2026-09-24 11:07 UTC77
25d14e49ab68atagen2026-09-24 11:07 UTC78
impl QueryPolicyResult {
25d14e49ab68atagen2026-09-24 11:07 UTC79
    const fn reject(prefix: MachineReadablePrefix, message: &'static str) -> Self {
25d14e49ab68atagen2026-09-24 11:07 UTC80
        Self::Reject {
25d14e49ab68atagen2026-09-24 11:07 UTC81
            prefix,
25d14e49ab68atagen2026-09-24 11:07 UTC82
            message: Cow::Borrowed(message),
25d14e49ab68atagen2026-09-24 11:07 UTC83
        }
25d14e49ab68atagen2026-09-24 11:07 UTC84
    }
25d14e49ab68atagen2026-09-24 11:07 UTC85
}
25d14e49ab68atagen2026-09-24 11:07 UTC86
25d14e49ab68atagen2026-09-24 11:07 UTC87
impl Policy {
25d14e49ab68atagen2026-09-24 11:07 UTC88
    pub fn new(database: Arc<dyn NostrDatabase>, config: &Config) -> anyhow::Result<Self> {
25d14e49ab68atagen2026-09-24 11:07 UTC89
        config.validate()?;
25d14e49ab68atagen2026-09-24 11:07 UTC90
        Ok(Self {
25d14e49ab68atagen2026-09-24 11:07 UTC91
            database,
25d14e49ab68atagen2026-09-24 11:07 UTC92
            limits: config.limits.clone(),
25d14e49ab68atagen2026-09-24 11:07 UTC93
            forge_keys: config.forge_keys()?,
25d14e49ab68atagen2026-09-24 11:07 UTC94
            peers: Arc::new(Mutex::new(HashMap::new())),
25d14e49ab68atagen2026-09-24 11:07 UTC95
        })
25d14e49ab68atagen2026-09-24 11:07 UTC96
    }
25d14e49ab68atagen2026-09-24 11:07 UTC97
25d14e49ab68atagen2026-09-24 11:07 UTC98
    fn charge(&self, ip: IpAddr, publish: bool) -> bool {
25d14e49ab68atagen2026-09-24 11:07 UTC99
        let mut peers = self.peers.lock().unwrap_or_else(PoisonError::into_inner);
25d14e49ab68atagen2026-09-24 11:07 UTC100
        peers.retain(|_, usage| usage.window.elapsed() < Duration::from_secs(60));
25d14e49ab68atagen2026-09-24 11:07 UTC101
        if peers.len() >= 4096 && !peers.contains_key(&ip) {
25d14e49ab68atagen2026-09-24 11:07 UTC102
            return false;
25d14e49ab68atagen2026-09-24 11:07 UTC103
        }
25d14e49ab68atagen2026-09-24 11:07 UTC104
        let usage = peers.entry(ip).or_insert_with(|| Usage {
25d14e49ab68atagen2026-09-24 11:07 UTC105
            window: Instant::now(),
25d14e49ab68atagen2026-09-24 11:07 UTC106
            events: 0,
25d14e49ab68atagen2026-09-24 11:07 UTC107
            queries: 0,
25d14e49ab68atagen2026-09-24 11:07 UTC108
        });
25d14e49ab68atagen2026-09-24 11:07 UTC109
        let (count, limit) = if publish {
25d14e49ab68atagen2026-09-24 11:07 UTC110
            (&mut usage.events, self.limits.events_per_minute)
25d14e49ab68atagen2026-09-24 11:07 UTC111
        } else {
25d14e49ab68atagen2026-09-24 11:07 UTC112
            (&mut usage.queries, self.limits.queries_per_minute)
25d14e49ab68atagen2026-09-24 11:07 UTC113
        };
25d14e49ab68atagen2026-09-24 11:07 UTC114
        if *count >= limit {
25d14e49ab68atagen2026-09-24 11:07 UTC115
            return false;
25d14e49ab68atagen2026-09-24 11:07 UTC116
        }
25d14e49ab68atagen2026-09-24 11:07 UTC117
        *count += 1;
25d14e49ab68atagen2026-09-24 11:07 UTC118
        drop(peers);
25d14e49ab68atagen2026-09-24 11:07 UTC119
        true
25d14e49ab68atagen2026-09-24 11:07 UTC120
    }
25d14e49ab68atagen2026-09-24 11:07 UTC121
25d14e49ab68atagen2026-09-24 11:07 UTC122
    pub(crate) async fn admit_event(
25d14e49ab68atagen2026-09-24 11:07 UTC123
        &self,
25d14e49ab68atagen2026-09-24 11:07 UTC124
        event: &Event,
25d14e49ab68atagen2026-09-24 11:07 UTC125
        address: &SocketAddr,
25d14e49ab68atagen2026-09-24 11:07 UTC126
    ) -> WritePolicyResult {
25d14e49ab68atagen2026-09-24 11:07 UTC127
        if !self.charge(address.ip(), true) {
25d14e49ab68atagen2026-09-24 11:07 UTC128
            return WritePolicyResult::reject(
25d14e49ab68atagen2026-09-24 11:07 UTC129
                MachineReadablePrefix::RateLimited,
25d14e49ab68atagen2026-09-24 11:07 UTC130
                "publish rate exceeded",
25d14e49ab68atagen2026-09-24 11:07 UTC131
            );
25d14e49ab68atagen2026-09-24 11:07 UTC132
        }
25d14e49ab68atagen2026-09-24 11:07 UTC133
        self.admit(event).await.unwrap_or_else(|error| {
25d14e49ab68atagen2026-09-24 11:07 UTC134
            tracing::error!(%error, "event admission database failure");
25d14e49ab68atagen2026-09-24 11:07 UTC135
            WritePolicyResult::reject(MachineReadablePrefix::Error, "database unavailable")
25d14e49ab68atagen2026-09-24 11:07 UTC136
        })
25d14e49ab68atagen2026-09-24 11:07 UTC137
    }
25d14e49ab68atagen2026-09-24 11:07 UTC138
25d14e49ab68atagen2026-09-24 11:07 UTC139
    pub(crate) async fn admit_peer(&self, event: &Event) -> WritePolicyResult {
25d14e49ab68atagen2026-09-24 11:07 UTC140
        match self.admit(event).await {
25d14e49ab68atagen2026-09-24 11:07 UTC141
            Ok(result) => result,
25d14e49ab68atagen2026-09-24 11:07 UTC142
            Err(error) => {
25d14e49ab68atagen2026-09-24 11:07 UTC143
                tracing::error!(%error, "event admission database failure");
25d14e49ab68atagen2026-09-24 11:07 UTC144
                WritePolicyResult::reject(MachineReadablePrefix::Error, "database unavailable")
25d14e49ab68atagen2026-09-24 11:07 UTC145
            }
25d14e49ab68atagen2026-09-24 11:07 UTC146
        }
25d14e49ab68atagen2026-09-24 11:07 UTC147
    }
25d14e49ab68atagen2026-09-24 11:07 UTC148
25d14e49ab68atagen2026-09-24 11:07 UTC149
    pub(crate) async fn admit_query(
25d14e49ab68atagen2026-09-24 11:07 UTC150
        &self,
25d14e49ab68atagen2026-09-24 11:07 UTC151
        filter: &Filter,
25d14e49ab68atagen2026-09-24 11:07 UTC152
        address: &SocketAddr,
25d14e49ab68atagen2026-09-24 11:07 UTC153
    ) -> QueryPolicyResult {
25d14e49ab68atagen2026-09-24 11:07 UTC154
        if !self.charge(address.ip(), false) {
25d14e49ab68atagen2026-09-24 11:07 UTC155
            return QueryPolicyResult::reject(
25d14e49ab68atagen2026-09-24 11:07 UTC156
                MachineReadablePrefix::RateLimited,
25d14e49ab68atagen2026-09-24 11:07 UTC157
                "query rate exceeded",
25d14e49ab68atagen2026-09-24 11:07 UTC158
            );
25d14e49ab68atagen2026-09-24 11:07 UTC159
        }
25d14e49ab68atagen2026-09-24 11:07 UTC160
        if filter.as_json().len() > self.limits.max_filter_bytes {
25d14e49ab68atagen2026-09-24 11:07 UTC161
            return QueryPolicyResult::reject(
25d14e49ab68atagen2026-09-24 11:07 UTC162
                MachineReadablePrefix::RateLimited,
25d14e49ab68atagen2026-09-24 11:07 UTC163
                "filter byte limit exceeded",
25d14e49ab68atagen2026-09-24 11:07 UTC164
            );
25d14e49ab68atagen2026-09-24 11:07 UTC165
        }
25d14e49ab68atagen2026-09-24 11:07 UTC166
        if filter.search.is_some() {
25d14e49ab68atagen2026-09-24 11:07 UTC167
            return QueryPolicyResult::reject(
25d14e49ab68atagen2026-09-24 11:07 UTC168
                MachineReadablePrefix::Unsupported,
25d14e49ab68atagen2026-09-24 11:07 UTC169
                "search is not supported",
25d14e49ab68atagen2026-09-24 11:07 UTC170
            );
25d14e49ab68atagen2026-09-24 11:07 UTC171
        }
25d14e49ab68atagen2026-09-24 11:07 UTC172
        if let Some(limit) = filter.limit {
25d14e49ab68atagen2026-09-24 11:07 UTC173
            return if limit > self.limits.max_query_results {
25d14e49ab68atagen2026-09-24 11:07 UTC174
                QueryPolicyResult::reject(
25d14e49ab68atagen2026-09-24 11:07 UTC175
                    MachineReadablePrefix::RateLimited,
25d14e49ab68atagen2026-09-24 11:07 UTC176
                    "query result limit exceeded",
25d14e49ab68atagen2026-09-24 11:07 UTC177
                )
25d14e49ab68atagen2026-09-24 11:07 UTC178
            } else {
25d14e49ab68atagen2026-09-24 11:07 UTC179
                QueryPolicyResult::Accept
25d14e49ab68atagen2026-09-24 11:07 UTC180
            };
25d14e49ab68atagen2026-09-24 11:07 UTC181
        }
25d14e49ab68atagen2026-09-24 11:07 UTC182
        match self
25d14e49ab68atagen2026-09-24 11:07 UTC183
            .database
25d14e49ab68atagen2026-09-24 11:07 UTC184
            .count(filter.clone().limit(self.limits.max_query_results + 1))
25d14e49ab68atagen2026-09-24 11:07 UTC185
            .await
25d14e49ab68atagen2026-09-24 11:07 UTC186
        {
25d14e49ab68atagen2026-09-24 11:07 UTC187
            Ok(count) if count <= self.limits.max_query_results => QueryPolicyResult::Accept,
25d14e49ab68atagen2026-09-24 11:07 UTC188
            Ok(_) => QueryPolicyResult::reject(
25d14e49ab68atagen2026-09-24 11:07 UTC189
                MachineReadablePrefix::RateLimited,
25d14e49ab68atagen2026-09-24 11:07 UTC190
                "history result limit exceeded",
25d14e49ab68atagen2026-09-24 11:07 UTC191
            ),
25d14e49ab68atagen2026-09-24 11:07 UTC192
            Err(error) => {
25d14e49ab68atagen2026-09-24 11:07 UTC193
                tracing::error!(%error, "query admission database failure");
25d14e49ab68atagen2026-09-24 11:07 UTC194
                QueryPolicyResult::reject(MachineReadablePrefix::Error, "database unavailable")
25d14e49ab68atagen2026-09-24 11:07 UTC195
            }
25d14e49ab68atagen2026-09-24 11:07 UTC196
        }
25d14e49ab68atagen2026-09-24 11:07 UTC197
    }
25d14e49ab68atagen2026-09-24 11:07 UTC198
25d14e49ab68atagen2026-09-24 11:07 UTC199
    async fn admit(&self, event: &Event) -> Result<WritePolicyResult, Error> {
25d14e49ab68atagen2026-09-24 11:07 UTC200
        if let Some(result) = self.check_event(event) {
25d14e49ab68atagen2026-09-24 11:07 UTC201
            return Ok(result);
25d14e49ab68atagen2026-09-24 11:07 UTC202
        }
25d14e49ab68atagen2026-09-24 11:07 UTC203
        match self.database.check_id(&event.id).await? {
25d14e49ab68atagen2026-09-24 11:07 UTC204
            DatabaseEventStatus::Saved => {
25d14e49ab68atagen2026-09-24 11:07 UTC205
                return Ok(WritePolicyResult::accepted(
25d14e49ab68atagen2026-09-24 11:07 UTC206
                    MachineReadablePrefix::Duplicate,
25d14e49ab68atagen2026-09-24 11:07 UTC207
                    "already stored",
25d14e49ab68atagen2026-09-24 11:07 UTC208
                ));
25d14e49ab68atagen2026-09-24 11:07 UTC209
            }
25d14e49ab68atagen2026-09-24 11:07 UTC210
            DatabaseEventStatus::Deleted => return Ok(blocked("event is deleted")),
25d14e49ab68atagen2026-09-24 11:07 UTC211
            DatabaseEventStatus::NotExistent => {}
25d14e49ab68atagen2026-09-24 11:07 UTC212
        }
25d14e49ab68atagen2026-09-24 11:07 UTC213
        self.admit_kind(event).await
25d14e49ab68atagen2026-09-24 11:07 UTC214
    }
25d14e49ab68atagen2026-09-24 11:07 UTC215
25d14e49ab68atagen2026-09-24 11:07 UTC216
    fn check_event(&self, event: &Event) -> Option<WritePolicyResult> {
25d14e49ab68atagen2026-09-24 11:07 UTC217
        if event.as_json().len() > self.limits.max_event_bytes
25d14e49ab68atagen2026-09-24 11:07 UTC218
            || event.tags.len() > self.limits.max_tags
25d14e49ab68atagen2026-09-24 11:07 UTC219
        {
25d14e49ab68atagen2026-09-24 11:07 UTC220
            return Some(blocked("event exceeds size or tag limit"));
25d14e49ab68atagen2026-09-24 11:07 UTC221
        }
25d14e49ab68atagen2026-09-24 11:07 UTC222
        if event.created_at.as_secs() == 0
25d14e49ab68atagen2026-09-24 11:07 UTC223
            || event.created_at.as_secs() > Timestamp::now().as_secs().saturating_add(900)
25d14e49ab68atagen2026-09-24 11:07 UTC224
        {
25d14e49ab68atagen2026-09-24 11:07 UTC225
            return Some(blocked("event timestamp is outside the supported range"));
25d14e49ab68atagen2026-09-24 11:07 UTC226
        }
25d14e49ab68atagen2026-09-24 11:07 UTC227
        if event.is_expired() || event.is_protected() {
25d14e49ab68atagen2026-09-24 11:07 UTC228
            return Some(blocked("expired or protected events are not accepted"));
25d14e49ab68atagen2026-09-24 11:07 UTC229
        }
25d14e49ab68atagen2026-09-24 11:07 UTC230
        None
25d14e49ab68atagen2026-09-24 11:07 UTC231
    }
25d14e49ab68atagen2026-09-24 11:07 UTC232
25d14e49ab68atagen2026-09-24 11:07 UTC233
    async fn admit_kind(&self, event: &Event) -> Result<WritePolicyResult, Error> {
25d14e49ab68atagen2026-09-24 11:07 UTC234
        match event.kind.as_u16() {
25d14e49ab68atagen2026-09-24 11:07 UTC235
            10002 => {
25d14e49ab68atagen2026-09-24 11:07 UTC236
                if !self.forge_keys.contains(&event.pubkey)
25d14e49ab68atagen2026-09-24 11:07 UTC237
                    && !self.registered_forge(event.pubkey).await?
25d14e49ab68atagen2026-09-24 11:07 UTC238
                {
25d14e49ab68atagen2026-09-24 11:07 UTC239
                    return Ok(blocked("relay list signer has no registered repository"));
25d14e49ab68atagen2026-09-24 11:07 UTC240
                }
25d14e49ab68atagen2026-09-24 11:07 UTC241
                let relay_tags = event
25d14e49ab68atagen2026-09-24 11:07 UTC242
                    .tags
25d14e49ab68atagen2026-09-24 11:07 UTC243
                    .iter()
25d14e49ab68atagen2026-09-24 11:07 UTC244
                    .filter(|tag| tag.as_slice().first().is_some_and(|name| name == "r"))
25d14e49ab68atagen2026-09-24 11:07 UTC245
                    .count();
25d14e49ab68atagen2026-09-24 11:07 UTC246
                if relay_tags > 16 || extract_relay_list(event).count() != relay_tags {
25d14e49ab68atagen2026-09-24 11:07 UTC247
                    return Ok(blocked("relay list is invalid or too large"));
25d14e49ab68atagen2026-09-24 11:07 UTC248
                }
25d14e49ab68atagen2026-09-24 11:07 UTC249
            }
25d14e49ab68atagen2026-09-24 11:07 UTC250
            30617 => {
25d14e49ab68atagen2026-09-24 11:07 UTC251
                let Some(identifier) = identifier(event) else {
25d14e49ab68atagen2026-09-24 11:07 UTC252
                    return Ok(blocked("one valid repository identifier is required"));
25d14e49ab68atagen2026-09-24 11:07 UTC253
                };
25d14e49ab68atagen2026-09-24 11:07 UTC254
                if !self.registered(event.pubkey, identifier).await? {
25d14e49ab68atagen2026-09-24 11:07 UTC255
                    let count = self
25d14e49ab68atagen2026-09-24 11:07 UTC256
                        .database
25d14e49ab68atagen2026-09-24 11:07 UTC257
                        .query(
25d14e49ab68atagen2026-09-24 11:07 UTC258
                            Filter::new()
25d14e49ab68atagen2026-09-24 11:07 UTC259
                                .kind(Kind::from(30617))
25d14e49ab68atagen2026-09-24 11:07 UTC260
                                .limit(self.limits.max_repositories + 1),
25d14e49ab68atagen2026-09-24 11:07 UTC261
                        )
25d14e49ab68atagen2026-09-24 11:07 UTC262
                        .await?
25d14e49ab68atagen2026-09-24 11:07 UTC263
                        .len();
25d14e49ab68atagen2026-09-24 11:07 UTC264
                    if count >= self.limits.max_repositories {
25d14e49ab68atagen2026-09-24 11:07 UTC265
                        return Ok(WritePolicyResult::reject(
25d14e49ab68atagen2026-09-24 11:07 UTC266
                            MachineReadablePrefix::RateLimited,
25d14e49ab68atagen2026-09-24 11:07 UTC267
                            "repository directory is full",
25d14e49ab68atagen2026-09-24 11:07 UTC268
                        ));
25d14e49ab68atagen2026-09-24 11:07 UTC269
                    }
25d14e49ab68atagen2026-09-24 11:07 UTC270
                }
25d14e49ab68atagen2026-09-24 11:07 UTC271
            }
25d14e49ab68atagen2026-09-24 11:07 UTC272
            30618 => {
25d14e49ab68atagen2026-09-24 11:07 UTC273
                let Some(identifier) = identifier(event) else {
25d14e49ab68atagen2026-09-24 11:07 UTC274
                    return Ok(blocked("one valid repository identifier is required"));
25d14e49ab68atagen2026-09-24 11:07 UTC275
                };
25d14e49ab68atagen2026-09-24 11:07 UTC276
                if !self.registered(event.pubkey, identifier).await? {
25d14e49ab68atagen2026-09-24 11:07 UTC277
                    return Ok(blocked("repository is not registered"));
25d14e49ab68atagen2026-09-24 11:07 UTC278
                }
25d14e49ab68atagen2026-09-24 11:07 UTC279
            }
25d14e49ab68atagen2026-09-24 11:07 UTC280
            1617 | 1621 | 1630..=1632 => {
25d14e49ab68atagen2026-09-24 11:07 UTC281
                let mut addresses = 0_usize;
25d14e49ab68atagen2026-09-24 11:07 UTC282
                for tag in event
25d14e49ab68atagen2026-09-24 11:07 UTC283
                    .tags
25d14e49ab68atagen2026-09-24 11:07 UTC284
                    .iter()
25d14e49ab68atagen2026-09-24 11:07 UTC285
                    .filter(|tag| tag.as_slice().first().is_some_and(|name| name == "a"))
25d14e49ab68atagen2026-09-24 11:07 UTC286
                {
25d14e49ab68atagen2026-09-24 11:07 UTC287
                    let Some((kind, author, identifier)) =
25d14e49ab68atagen2026-09-24 11:07 UTC288
                        tag.as_slice().get(1).and_then(|value| coordinate(value))
25d14e49ab68atagen2026-09-24 11:07 UTC289
                    else {
25d14e49ab68atagen2026-09-24 11:07 UTC290
                        return Ok(blocked("invalid repository address"));
25d14e49ab68atagen2026-09-24 11:07 UTC291
                    };
25d14e49ab68atagen2026-09-24 11:07 UTC292
                    if kind != 30617 || !self.registered(author, identifier).await? {
25d14e49ab68atagen2026-09-24 11:07 UTC293
                        return Ok(blocked("repository is not registered"));
25d14e49ab68atagen2026-09-24 11:07 UTC294
                    }
25d14e49ab68atagen2026-09-24 11:07 UTC295
                    if matches!(event.kind.as_u16(), 1630..=1632) && author != event.pubkey {
25d14e49ab68atagen2026-09-24 11:07 UTC296
                        return Ok(blocked("status signer does not own the repository"));
25d14e49ab68atagen2026-09-24 11:07 UTC297
                    }
25d14e49ab68atagen2026-09-24 11:07 UTC298
                    addresses += 1;
25d14e49ab68atagen2026-09-24 11:07 UTC299
                }
25d14e49ab68atagen2026-09-24 11:07 UTC300
                if addresses != 1 {
25d14e49ab68atagen2026-09-24 11:07 UTC301
                    return Ok(blocked("exactly one repository address is required"));
25d14e49ab68atagen2026-09-24 11:07 UTC302
                }
25d14e49ab68atagen2026-09-24 11:07 UTC303
            }
25d14e49ab68atagen2026-09-24 11:07 UTC304
            30382 => {
25d14e49ab68atagen2026-09-24 11:07 UTC305
                if !valid_binding(event) {
25d14e49ab68atagen2026-09-24 11:07 UTC306
                    return Ok(blocked("invalid openpgp binding envelope"));
25d14e49ab68atagen2026-09-24 11:07 UTC307
                }
25d14e49ab68atagen2026-09-24 11:07 UTC308
            }
25d14e49ab68atagen2026-09-24 11:07 UTC309
            5 => return self.deletion(event).await,
25d14e49ab68atagen2026-09-24 11:07 UTC310
            _ => {
25d14e49ab68atagen2026-09-24 11:07 UTC311
                return Ok(WritePolicyResult::reject(
25d14e49ab68atagen2026-09-24 11:07 UTC312
                    MachineReadablePrefix::Unsupported,
25d14e49ab68atagen2026-09-24 11:07 UTC313
                    "event kind is not supported",
25d14e49ab68atagen2026-09-24 11:07 UTC314
                ));
25d14e49ab68atagen2026-09-24 11:07 UTC315
            }
25d14e49ab68atagen2026-09-24 11:07 UTC316
        }
25d14e49ab68atagen2026-09-24 11:07 UTC317
        Ok(WritePolicyResult::Accept)
25d14e49ab68atagen2026-09-24 11:07 UTC318
    }
25d14e49ab68atagen2026-09-24 11:07 UTC319
25d14e49ab68atagen2026-09-24 11:07 UTC320
    async fn registered(&self, author: PublicKey, identifier: &str) -> Result<bool, Error> {
25d14e49ab68atagen2026-09-24 11:07 UTC321
        Ok(self
25d14e49ab68atagen2026-09-24 11:07 UTC322
            .database
25d14e49ab68atagen2026-09-24 11:07 UTC323
            .query(
25d14e49ab68atagen2026-09-24 11:07 UTC324
                Filter::new()
25d14e49ab68atagen2026-09-24 11:07 UTC325
                    .kind(Kind::from(30617))
25d14e49ab68atagen2026-09-24 11:07 UTC326
                    .author(author)
25d14e49ab68atagen2026-09-24 11:07 UTC327
                    .identifier(identifier)
25d14e49ab68atagen2026-09-24 11:07 UTC328
                    .limit(1),
25d14e49ab68atagen2026-09-24 11:07 UTC329
            )
25d14e49ab68atagen2026-09-24 11:07 UTC330
            .await?
25d14e49ab68atagen2026-09-24 11:07 UTC331
            .iter()
25d14e49ab68atagen2026-09-24 11:07 UTC332
            .any(|event| !event.is_expired()))
25d14e49ab68atagen2026-09-24 11:07 UTC333
    }
25d14e49ab68atagen2026-09-24 11:07 UTC334
25d14e49ab68atagen2026-09-24 11:07 UTC335
    async fn registered_forge(&self, author: PublicKey) -> Result<bool, Error> {
25d14e49ab68atagen2026-09-24 11:07 UTC336
        Ok(self
25d14e49ab68atagen2026-09-24 11:07 UTC337
            .database
25d14e49ab68atagen2026-09-24 11:07 UTC338
            .query(
25d14e49ab68atagen2026-09-24 11:07 UTC339
                Filter::new()
25d14e49ab68atagen2026-09-24 11:07 UTC340
                    .kind(Kind::from(30617))
25d14e49ab68atagen2026-09-24 11:07 UTC341
                    .author(author)
25d14e49ab68atagen2026-09-24 11:07 UTC342
                    .limit(1),
25d14e49ab68atagen2026-09-24 11:07 UTC343
            )
25d14e49ab68atagen2026-09-24 11:07 UTC344
            .await?
25d14e49ab68atagen2026-09-24 11:07 UTC345
            .iter()
25d14e49ab68atagen2026-09-24 11:07 UTC346
            .any(|event| !event.is_expired()))
25d14e49ab68atagen2026-09-24 11:07 UTC347
    }
25d14e49ab68atagen2026-09-24 11:07 UTC348
25d14e49ab68atagen2026-09-24 11:07 UTC349
    async fn deletion(&self, event: &Event) -> Result<WritePolicyResult, Error> {
25d14e49ab68atagen2026-09-24 11:07 UTC350
        let mut targets = 0_usize;
25d14e49ab68atagen2026-09-24 11:07 UTC351
        for tag in event.tags.iter() {
25d14e49ab68atagen2026-09-24 11:07 UTC352
            let values = tag.as_slice();
25d14e49ab68atagen2026-09-24 11:07 UTC353
            match values.first().map(String::as_str) {
25d14e49ab68atagen2026-09-24 11:07 UTC354
                Some("e") => {
25d14e49ab68atagen2026-09-24 11:07 UTC355
                    let Some(id) = values
25d14e49ab68atagen2026-09-24 11:07 UTC356
                        .get(1)
25d14e49ab68atagen2026-09-24 11:07 UTC357
                        .filter(|id| id.len() == 64)
25d14e49ab68atagen2026-09-24 11:07 UTC358
                        .and_then(|id| id.parse::<EventId>().ok())
25d14e49ab68atagen2026-09-24 11:07 UTC359
                    else {
25d14e49ab68atagen2026-09-24 11:07 UTC360
                        return Ok(blocked("invalid deletion target"));
25d14e49ab68atagen2026-09-24 11:07 UTC361
                    };
25d14e49ab68atagen2026-09-24 11:07 UTC362
                    if let Some(target) = self.database.event_by_id(&id).await? {
25d14e49ab68atagen2026-09-24 11:07 UTC363
                        if target.pubkey != event.pubkey || target.kind == Kind::EventDeletion {
25d14e49ab68atagen2026-09-24 11:07 UTC364
                            return Ok(blocked("deletion target is not removable by this author"));
25d14e49ab68atagen2026-09-24 11:07 UTC365
                        }
25d14e49ab68atagen2026-09-24 11:07 UTC366
                    }
25d14e49ab68atagen2026-09-24 11:07 UTC367
                    targets += 1;
25d14e49ab68atagen2026-09-24 11:07 UTC368
                }
25d14e49ab68atagen2026-09-24 11:07 UTC369
                Some("a") => {
25d14e49ab68atagen2026-09-24 11:07 UTC370
                    let Some((kind, author, _)) = values.get(1).and_then(|value| coordinate(value))
25d14e49ab68atagen2026-09-24 11:07 UTC371
                    else {
25d14e49ab68atagen2026-09-24 11:07 UTC372
                        return Ok(blocked("invalid deletion address"));
25d14e49ab68atagen2026-09-24 11:07 UTC373
                    };
25d14e49ab68atagen2026-09-24 11:07 UTC374
                    if author != event.pubkey || !matches!(kind, 30617 | 30618 | 30382) {
25d14e49ab68atagen2026-09-24 11:07 UTC375
                        return Ok(blocked("deletion address is not owned by this author"));
25d14e49ab68atagen2026-09-24 11:07 UTC376
                    }
25d14e49ab68atagen2026-09-24 11:07 UTC377
                    targets += 1;
25d14e49ab68atagen2026-09-24 11:07 UTC378
                }
25d14e49ab68atagen2026-09-24 11:07 UTC379
                _ => {}
25d14e49ab68atagen2026-09-24 11:07 UTC380
            }
25d14e49ab68atagen2026-09-24 11:07 UTC381
        }
25d14e49ab68atagen2026-09-24 11:07 UTC382
        Ok(if targets == 0 {
25d14e49ab68atagen2026-09-24 11:07 UTC383
            blocked("deletion target is required")
25d14e49ab68atagen2026-09-24 11:07 UTC384
        } else {
25d14e49ab68atagen2026-09-24 11:07 UTC385
            WritePolicyResult::Accept
25d14e49ab68atagen2026-09-24 11:07 UTC386
        })
25d14e49ab68atagen2026-09-24 11:07 UTC387
    }
25d14e49ab68atagen2026-09-24 11:07 UTC388
}
25d14e49ab68atagen2026-09-24 11:07 UTC389
25d14e49ab68atagen2026-09-24 11:07 UTC390
const fn blocked(message: &'static str) -> WritePolicyResult {
25d14e49ab68atagen2026-09-24 11:07 UTC391
    WritePolicyResult::reject(MachineReadablePrefix::Blocked, message)
25d14e49ab68atagen2026-09-24 11:07 UTC392
}
25d14e49ab68atagen2026-09-24 11:07 UTC393
25d14e49ab68atagen2026-09-24 11:07 UTC394
fn valid_identifier(identifier: &str) -> bool {
25d14e49ab68atagen2026-09-24 11:07 UTC395
    !identifier.is_empty() && identifier.len() <= 512 && !identifier.chars().any(char::is_control)
25d14e49ab68atagen2026-09-24 11:07 UTC396
}
25d14e49ab68atagen2026-09-24 11:07 UTC397
25d14e49ab68atagen2026-09-24 11:07 UTC398
fn identifier(event: &Event) -> Option<&str> {
25d14e49ab68atagen2026-09-24 11:07 UTC399
    let mut tags = event
25d14e49ab68atagen2026-09-24 11:07 UTC400
        .tags
25d14e49ab68atagen2026-09-24 11:07 UTC401
        .iter()
25d14e49ab68atagen2026-09-24 11:07 UTC402
        .filter(|tag| tag.as_slice().first().is_some_and(|name| name == "d"));
25d14e49ab68atagen2026-09-24 11:07 UTC403
    let tag = tags.next()?.as_slice();
25d14e49ab68atagen2026-09-24 11:07 UTC404
    if tags.next().is_some() || tag.len() != 2 || !valid_identifier(&tag[1]) {
25d14e49ab68atagen2026-09-24 11:07 UTC405
        return None;
25d14e49ab68atagen2026-09-24 11:07 UTC406
    }
25d14e49ab68atagen2026-09-24 11:07 UTC407
    Some(&tag[1])
25d14e49ab68atagen2026-09-24 11:07 UTC408
}
25d14e49ab68atagen2026-09-24 11:07 UTC409
25d14e49ab68atagen2026-09-24 11:07 UTC410
pub(crate) fn coordinate(value: &str) -> Option<(u16, PublicKey, &str)> {
25d14e49ab68atagen2026-09-24 11:07 UTC411
    let mut parts = value.splitn(3, ':');
25d14e49ab68atagen2026-09-24 11:07 UTC412
    let kind = parts.next()?.parse().ok()?;
25d14e49ab68atagen2026-09-24 11:07 UTC413
    let author_hex = parts.next()?;
25d14e49ab68atagen2026-09-24 11:07 UTC414
    if author_hex.len() != 64 || !author_hex.bytes().all(|byte| byte.is_ascii_hexdigit()) {
25d14e49ab68atagen2026-09-24 11:07 UTC415
        return None;
25d14e49ab68atagen2026-09-24 11:07 UTC416
    }
25d14e49ab68atagen2026-09-24 11:07 UTC417
    let author = author_hex.parse().ok()?;
25d14e49ab68atagen2026-09-24 11:07 UTC418
    let identifier = parts.next()?;
25d14e49ab68atagen2026-09-24 11:07 UTC419
    valid_identifier(identifier).then_some((kind, author, identifier))
25d14e49ab68atagen2026-09-24 11:07 UTC420
}
25d14e49ab68atagen2026-09-24 11:07 UTC421
25d14e49ab68atagen2026-09-24 11:07 UTC422
fn valid_binding(event: &Event) -> bool {
25d14e49ab68atagen2026-09-24 11:07 UTC423
    if event.content.len() > 192 * 1024 {
25d14e49ab68atagen2026-09-24 11:07 UTC424
        return false;
25d14e49ab68atagen2026-09-24 11:07 UTC425
    }
25d14e49ab68atagen2026-09-24 11:07 UTC426
    let Ok(body) = serde_json::from_str::<Binding>(&event.content) else {
25d14e49ab68atagen2026-09-24 11:07 UTC427
        return false;
25d14e49ab68atagen2026-09-24 11:07 UTC428
    };
25d14e49ab68atagen2026-09-24 11:07 UTC429
    if body.version != 1
25d14e49ab68atagen2026-09-24 11:07 UTC430
        || !matches!(body.fingerprint.len(), 40 | 64)
25d14e49ab68atagen2026-09-24 11:07 UTC431
        || !body
25d14e49ab68atagen2026-09-24 11:07 UTC432
            .fingerprint
25d14e49ab68atagen2026-09-24 11:07 UTC433
            .bytes()
25d14e49ab68atagen2026-09-24 11:07 UTC434
            .all(|byte| byte.is_ascii_hexdigit())
25d14e49ab68atagen2026-09-24 11:07 UTC435
        || identifier(event) != Some(body.fingerprint.as_str())
25d14e49ab68atagen2026-09-24 11:07 UTC436
    {
25d14e49ab68atagen2026-09-24 11:07 UTC437
        return false;
25d14e49ab68atagen2026-09-24 11:07 UTC438
    }
25d14e49ab68atagen2026-09-24 11:07 UTC439
    match body.action.as_str() {
25d14e49ab68atagen2026-09-24 11:07 UTC440
        "bind" => {
25d14e49ab68atagen2026-09-24 11:07 UTC441
            body.certificate
25d14e49ab68atagen2026-09-24 11:07 UTC442
                .is_some_and(|certificate| !certificate.trim().is_empty())
25d14e49ab68atagen2026-09-24 11:07 UTC443
                && body
25d14e49ab68atagen2026-09-24 11:07 UTC444
                    .signature
25d14e49ab68atagen2026-09-24 11:07 UTC445
                    .is_some_and(|signature| !signature.trim().is_empty())
25d14e49ab68atagen2026-09-24 11:07 UTC446
        }
25d14e49ab68atagen2026-09-24 11:07 UTC447
        "revoke" => body.certificate.is_none() && body.signature.is_none(),
25d14e49ab68atagen2026-09-24 11:07 UTC448
        _ => false,
25d14e49ab68atagen2026-09-24 11:07 UTC449
    }
25d14e49ab68atagen2026-09-24 11:07 UTC450
}