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}
81
82#[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 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
141pub 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}