heddle_thread_api/
authority.rs1use std::sync::RwLock;
6
7use api::{heddle::api::common::CallContext, v2::MethodDescriptor};
8use base64::Engine as _;
9use biscuit_verifier::{BiscuitFacts, PublicKey};
10use chrono::{DateTime, Utc};
11
12use crate::transport::Error;
13
14pub struct RootAuthority {
15 roots: RwLock<Vec<PublicKey>>,
16 spool_path: String,
17}
18impl RootAuthority {
19 pub fn new(roots: Vec<PublicKey>, spool_path: String) -> Result<Self, Error> {
20 if roots.is_empty() || spool_path.is_empty() {
21 return Err(Error::Protocol(
22 "root attachment and resolved spool path required",
23 ));
24 }
25 Ok(Self {
26 roots: RwLock::new(roots),
27 spool_path,
28 })
29 }
30 pub fn verify(
32 &self,
33 context: &CallContext,
34 method: &'static MethodDescriptor,
35 body: &[u8],
36 right: &str,
37 registry: &repo::thread_replication::ThreadReplica,
38 ) -> Result<VerifiedCall, Error> {
39 let now = Utc::now();
40 let facts = self.check(context, method, right, now)?;
41 let cnf = facts
42 .cnf
43 .as_deref()
44 .ok_or(Error::Protocol("Biscuit must bind a request signing key"))?;
45 let mut key = [0; 32];
46 hex::decode_to_slice(cnf, &mut key)
47 .map_err(|_| Error::Protocol("invalid Biscuit proof key"))?;
48 let proof =
49 crate::request_proof::verify(context, method, body, &key, now.timestamp_millis())?;
50 if !registry
51 .claim_request_nonce(proof.identity(), proof.nonce(), now.timestamp_millis())
52 .map_err(|e| Error::Io(e.to_string()))?
53 {
54 return Err(Error::Protocol("request nonce already consumed"));
55 }
56 Ok(VerifiedCall {
57 context: context.clone(),
58 method,
59 right: right.into(),
60 principal: facts.sub,
61 })
62 }
63 fn check(
64 &self,
65 context: &CallContext,
66 method: &'static MethodDescriptor,
67 right: &str,
68 now: DateTime<Utc>,
69 ) -> Result<BiscuitFacts, Error> {
70 if context
71 .deadline
72 .as_ref()
73 .is_some_and(|deadline| deadline.seconds <= now.timestamp())
74 {
75 return Err(Error::Protocol("request deadline expired"));
76 }
77 let bearer = base64::engine::general_purpose::URL_SAFE.encode(&context.bearer_capability);
80 let envelope = if context.bearer_grant_envelope.is_empty() {
81 None
82 } else {
83 Some(
84 std::str::from_utf8(&context.bearer_grant_envelope)
85 .map_err(|_| Error::Protocol("invalid grant envelope encoding"))?,
86 )
87 };
88 let operation = method
89 .path
90 .rsplit('/')
91 .next()
92 .ok_or(Error::Protocol("invalid method path"))?;
93 let facts = biscuit_verifier::verify_any_at_with_resource(
94 &bearer,
95 envelope,
96 &self
97 .roots
98 .read()
99 .map_err(|_| Error::Protocol("root registry unavailable"))?,
100 &[],
101 operation,
102 Some(("spool", &self.spool_path)),
103 now,
104 )
105 .map_err(|_| Error::Protocol("Biscuit does not authorize this operation"))?;
106 if !facts.has_right("spool", &self.spool_path, right) {
107 return Err(Error::Protocol("Biscuit does not grant this spool right"));
108 }
109 Ok(facts)
110 }
111 pub fn recheck(&self, call: &VerifiedCall) -> Result<(), Error> {
112 self.check(&call.context, call.method, &call.right, Utc::now())
113 .map(|_| ())
114 }
115
116 pub fn replace_roots(&self, roots: Vec<PublicKey>) -> Result<(), Error> {
119 *self
120 .roots
121 .write()
122 .map_err(|_| Error::Protocol("root registry unavailable"))? = roots;
123 Ok(())
124 }
125}
126
127#[derive(Clone)]
128pub struct VerifiedCall {
129 context: CallContext,
130 method: &'static MethodDescriptor,
131 right: String,
132 pub principal: String,
133}