Skip to main content

m4a_agent/backend/
server.rs

1//! Tier 2: our server through its own product API (no Matrix).
2
3use std::sync::Arc;
4
5use zeroize::Zeroizing;
6
7use super::wire::{HttpWire, Wire};
8use super::{Backend, BackendKind, Capabilities, Session};
9use crate::error::{AgentError, Result};
10use crate::identity::{IdentityStore, SessionIdentity};
11use crate::keyauth;
12
13pub struct ServerBackend {
14    base: String,
15    wire: Arc<dyn Wire>,
16    #[cfg(feature = "engine")]
17    live: super::live::Live,
18}
19
20impl ServerBackend {
21    pub fn new(base_url: &str) -> Result<Self> {
22        Self::with_wire(base_url, Box::new(HttpWire::new(base_url)?))
23    }
24
25    /// Over any transport (tests).
26    pub fn with_wire(base_url: &str, wire: Box<dyn Wire>) -> Result<Self> {
27        Ok(Self {
28            base: base_url.trim_end_matches('/').to_string(),
29            wire: Arc::from(wire),
30            #[cfg(feature = "engine")]
31            live: super::live::Live::new(crate::engine::HttpExec::new(base_url)?),
32        })
33    }
34
35    #[allow(unused_variables)]
36    fn established(&self, ids: &IdentityStore, id: &SessionIdentity, token: &str) {
37        #[cfg(feature = "engine")]
38        {
39            self.live.set_token(token);
40            // The product's own proxy serves the Matrix paths without the spec prefix.
41            self.live.with_exec(|e| e.set_keep_prefix(false));
42            // A long-running client outlives its token (server restart, revocation, expiry): on
43            // M_UNKNOWN_TOKEN log in again by key signature instead of failing every request
44            // (and missing DM invites) until the process is restarted.
45            if id.enrolled {
46                let (ids, id, wire) = (ids.clone(), id.clone(), Arc::clone(&self.wire));
47                self.live.set_refresher(Arc::new(move || Ok(keyauth::login(&*wire, &ids, &id)?.1)));
48            }
49        }
50        let _ = token;
51    }
52}
53
54impl Backend for ServerBackend {
55    fn kind(&self) -> BackendKind {
56        BackendKind::Server
57    }
58    fn capabilities(&self) -> Capabilities {
59        Capabilities { rooms: false, federation: false }
60    }
61    fn server_ref(&self) -> &str {
62        &self.base
63    }
64
65    fn ensure_session(&self, ids: &IdentityStore, id: &mut SessionIdentity, invite: Option<&str>) -> Result<Session> {
66        if id.tier != BackendKind::Server || id.server_ref != self.base {
67            return Err(AgentError::Identity("identity belongs to another tier or server".into()));
68        }
69        if let Some(t) = keyauth::stored_token(ids, id)? {
70            let (st, v) = self.wire.get("/product/v1/me", Some(&t))?;
71            if st == 200 {
72                let nick = v.get("nick").and_then(|n| n.as_str()).unwrap_or_default().to_string();
73                self.established(ids, id, &t);
74                return Ok(Session { token: Zeroizing::new(t), nick, cred_ref: id.key_id.clone(), user_id: None, device_id: None });
75            }
76        }
77        let (nick, token) = if id.enrolled {
78            keyauth::login(&*self.wire, ids, id)?
79        } else {
80            keyauth::enroll(&*self.wire, ids, id, invite.ok_or(AgentError::NeedsInvite)?)?
81        };
82        self.established(ids, id, &token);
83        Ok(Session { token: Zeroizing::new(token), nick, cred_ref: id.key_id.clone(), user_id: None, device_id: None })
84    }
85
86    #[cfg(feature = "engine")]
87    fn execute(&self, request: &mail4agent_messenger::OutgoingRequest) -> Result<mail4agent_messenger::HttpResponseDescriptor> {
88        self.live.execute(request)
89    }
90
91    #[cfg(feature = "engine")]
92    fn wait_for_news(&self, timeout: std::time::Duration) -> Result<crate::engine::News> {
93        self.live.wait_for_news(timeout)
94    }
95
96    #[cfg(feature = "engine")]
97    fn whoami(&self) -> Result<(String, String)> {
98        self.live.whoami()
99    }
100
101    #[cfg(feature = "engine")]
102    fn keep_prefix(&self) -> bool {
103        self.live.keep_prefix()
104    }
105}