Skip to main content

sofie_http/shield/
arguments.rs

1use std::sync::Arc;
2
3use sofie_core::{Error, Result, Timestamp, Uuid};
4
5use super::blocks::{Grade, Key};
6use super::detector::{Message, Signal};
7use super::rule::Rule;
8use super::{RequestShield, Sentinel, effects};
9use crate::throttle::Requester;
10use crate::{Area, RequestContext};
11use sofie_db::Resolve;
12
13/// Without the shield running it counts nothing and locks nothing.
14pub struct SignIns {
15    pub(super) request: Option<RequestShield>,
16}
17
18impl SignIns {
19    /// For this caller: another caller's failures lock it out only past the account-wide count.
20    #[must_use]
21    pub fn locked(&self, account: Uuid) -> bool {
22        self.request.as_ref().is_some_and(|request| {
23            let sentinel = &request.sentinel;
24            let from = Key::AccountFrom(account, request.anonymous());
25            [Key::Account(account), from].into_iter().any(|key| {
26                sentinel
27                    .blocks
28                    .find(key, sentinel.now())
29                    .is_some_and(|block| block.grade == Grade::Lock)
30            })
31        })
32    }
33
34    pub fn failed(&self, account: Option<Uuid>) {
35        let Some(request) = &self.request else {
36            return;
37        };
38        request.count(Rule::SignIn, Key::Caller(request.anonymous()));
39        if let Some(account) = account {
40            let from = Key::AccountFrom(account, request.anonymous());
41            request.count(Rule::Account, from);
42            request.count(Rule::AccountTotal, Key::Account(account));
43        }
44    }
45
46    pub fn signed_up(&self) {
47        if let Some(request) = &self.request {
48            request.count(Rule::SignUp, Key::Caller(request.anonymous()));
49        }
50    }
51}
52
53impl RequestShield {
54    /// By address even when signed in, as whoever signs in next from there is someone else.
55    fn anonymous(&self) -> Requester {
56        Requester::of(None, self.address)
57    }
58
59    fn count(&self, rule: Rule, key: Key) {
60        self.sentinel.post(Message::Signal(Signal {
61            rule,
62            key,
63            address: self.address,
64            area: Some(self.area),
65            at: self.sentinel.now(),
66        }));
67    }
68}
69
70impl<A: Area> Resolve<RequestContext<A>> for SignIns {
71    fn resolve(context: &mut RequestContext<A>) -> impl Future<Output = Result<Self>> + Send {
72        std::future::ready(Ok(Self {
73            request: context.matched.shield.clone(),
74        }))
75    }
76}
77
78pub struct ShieldBlocks {
79    sentinel: Option<Arc<Sentinel>>,
80}
81
82/// An address is shown by its network only.
83#[derive(Clone, Debug, PartialEq, Eq)]
84pub struct ActiveBlock {
85    pub id: u64,
86    pub caller: String,
87    pub rule: &'static str,
88    pub grade: &'static str,
89    pub until: Timestamp,
90}
91
92impl ShieldBlocks {
93    #[must_use]
94    pub fn active(&self) -> Vec<ActiveBlock> {
95        let Some(sentinel) = &self.sentinel else {
96            return Vec::new();
97        };
98        sentinel
99            .blocks
100            .active(sentinel.now())
101            .into_iter()
102            .map(|(key, block)| ActiveBlock {
103                id: block.id,
104                caller: key.shown(),
105                rule: block.rule.name(),
106                grade: block.grade.name(),
107                until: Timestamp::from_microsecond(block.until).unwrap_or(Timestamp::MAX),
108            })
109            .collect()
110    }
111
112    /// Also on every instance when shared; the caller's counts start over.
113    pub async fn lift(&self, id: u64) -> Result<()> {
114        let sentinel = self.sentinel.as_ref().ok_or(Error::NotFound)?;
115        let key = sentinel.blocks.lift(id).ok_or(Error::NotFound)?;
116        sentinel.post(Message::Lifted(key));
117        tracing::warn!(shield.caller = %key.shown(), "a shield block was lifted");
118        match &sentinel.shared {
119            Some(db) => effects::lift_shared(db, key).await,
120            None => Ok(()),
121        }
122    }
123
124    fn of<A: Area, M>(context: &RequestContext<A, M>) -> Self {
125        Self {
126            sentinel: context
127                .matched
128                .shield
129                .as_ref()
130                .map(|request| Arc::clone(&request.sentinel)),
131        }
132    }
133}
134
135impl<A: Area> Resolve<RequestContext<A>> for ShieldBlocks {
136    fn resolve(context: &mut RequestContext<A>) -> impl Future<Output = Result<Self>> + Send {
137        std::future::ready(Ok(Self::of(context)))
138    }
139}
140
141/// What a GET may take: lifting a block needs `ShieldBlocks`, in a POST.
142pub struct ActiveBlocks(ShieldBlocks);
143
144impl ActiveBlocks {
145    #[must_use]
146    pub fn list(&self) -> Vec<ActiveBlock> {
147        self.0.active()
148    }
149}
150
151impl<A: Area, M> Resolve<RequestContext<A, M>> for ActiveBlocks {
152    fn resolve(context: &mut RequestContext<A, M>) -> impl Future<Output = Result<Self>> + Send {
153        std::future::ready(Ok(Self(ShieldBlocks::of(context))))
154    }
155}