sofie_http/shield/
arguments.rs1use 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
13pub struct SignIns {
15 pub(super) request: Option<RequestShield>,
16}
17
18impl SignIns {
19 #[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 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 keys: Arc<sofie_auth::SigningKeys>,
81}
82
83#[derive(Clone, Debug, PartialEq, Eq)]
85pub struct ActiveBlock {
86 pub id: String,
87 pub caller: String,
88 pub rule: &'static str,
89 pub grade: &'static str,
90 pub until: Timestamp,
91}
92
93impl ShieldBlocks {
94 #[must_use]
95 pub fn active(&self) -> Vec<ActiveBlock> {
96 let Some(sentinel) = &self.sentinel else {
97 return Vec::new();
98 };
99 sentinel
100 .blocks
101 .active(sentinel.now())
102 .into_iter()
103 .map(|(key, block)| ActiveBlock {
104 id: key.id(&self.keys),
105 caller: key.shown(),
106 rule: block.rule.name(),
107 grade: block.grade.name(),
108 until: Timestamp::from_microsecond(block.until).unwrap_or(Timestamp::MAX),
109 })
110 .collect()
111 }
112
113 pub async fn lift(&self, id: &str) -> Result<()> {
115 let sentinel = self.sentinel.as_ref().ok_or(Error::NotFound)?;
116 let key = sentinel
117 .blocks
118 .lift(id, &self.keys)
119 .ok_or(Error::NotFound)?;
120 sentinel.post(Message::Lifted(key));
121 tracing::warn!(shield.caller = %key.shown(), "a shield block was lifted");
122 match &sentinel.shared {
123 Some(db) => effects::lift_shared(db, key).await,
124 None => Ok(()),
125 }
126 }
127
128 fn of<A: Area, M>(context: &RequestContext<A, M>) -> Self {
129 Self {
130 sentinel: context
131 .matched
132 .shield
133 .as_ref()
134 .map(|request| Arc::clone(&request.sentinel)),
135 keys: Arc::clone(&context.matched.keys),
136 }
137 }
138}
139
140impl<A: Area> Resolve<RequestContext<A>> for ShieldBlocks {
141 fn resolve(context: &mut RequestContext<A>) -> impl Future<Output = Result<Self>> + Send {
142 std::future::ready(Ok(Self::of(context)))
143 }
144}
145
146pub struct ActiveBlocks(ShieldBlocks);
148
149impl ActiveBlocks {
150 #[must_use]
151 pub fn list(&self) -> Vec<ActiveBlock> {
152 self.0.active()
153 }
154}
155
156impl<A: Area, M> Resolve<RequestContext<A, M>> for ActiveBlocks {
157 fn resolve(context: &mut RequestContext<A, M>) -> impl Future<Output = Result<Self>> + Send {
158 std::future::ready(Ok(Self(ShieldBlocks::of(context))))
159 }
160}