Skip to main content

liminal_server/auth_lys/
root_presence_start.rs

1//! SRV-039: the root question on a server that has Lys and no grant stream.
2//!
3//! SRV-038's question is built by the grant stream owner and asked at its
4//! ready fences. A server that checks grants by live asks alone starts no
5//! owner, so without this nothing ever asked whether its network has a root.
6//! Here the question is built from the app credential and asked once at
7//! start, in the background so start never waits on Lys; never on a timer and
8//! never per request. A server with
9//! Lys and no credential to ask with says so on `/health` with its cause.
10
11use std::sync::Arc;
12
13use super::root_presence::{LysRoot, RootPresence};
14use crate::ServerError;
15use crate::config::types::LysConfig;
16
17/// What `/health` holds for the root question.
18#[derive(Debug)]
19pub enum RootSlot {
20    /// The question, asked by the grant stream owner or once at start.
21    Asked(Arc<RootPresence>),
22    /// A server with Lys that has nothing to ask with.
23    Unasked {
24        /// The stable cause.
25        cause: &'static str,
26    },
27}
28
29impl RootSlot {
30    /// The `lys_root` value and its `lys_root_cause`.
31    #[must_use]
32    pub fn health(&self) -> (&'static str, Option<&'static str>) {
33        match self {
34            Self::Asked(root) => {
35                let answer = root.answer();
36                (answer.health(), answer.cause())
37            }
38            Self::Unasked { cause } => (LysRoot::NotAsked.health(), Some(cause)),
39        }
40    }
41}
42
43/// Who holds the root question on this server.
44#[derive(Debug)]
45pub enum RootQuestion {
46    /// The grant stream owner builds and asks it (SRV-038).
47    Owner,
48    /// Asked once at start with the app credential.
49    AtStart(Arc<RootPresence>),
50    /// Nothing to ask with: `/health` reports `not_asked` with this cause.
51    Unasked(&'static str),
52}
53
54/// Decides who holds the question for `lys`, building it when this server
55/// asks it at start.
56///
57/// # Errors
58/// The app credential cannot be read, or the question cannot be built.
59pub fn root_question(
60    lys: &LysConfig,
61    handle: Option<tokio::runtime::Handle>,
62) -> Result<RootQuestion, ServerError> {
63    if lys.membership.is_some() || lys.grant_stream.is_some() {
64        return Ok(RootQuestion::Owner);
65    }
66    let Some(path) = lys.app_credential_path.as_ref() else {
67        return Ok(RootQuestion::Unasked("no_asking_credential"));
68    };
69    let Some(handle) = handle else {
70        return Ok(RootQuestion::Unasked("no_ask_runtime"));
71    };
72    let refused = |message: String| ServerError::ConfigValidation { message };
73    let credential = std::fs::read_to_string(path).map_err(|error| {
74        refused(format!(
75            "auth.lys.app_credential_path '{}' cannot be read: {}",
76            path.display(),
77            error.kind()
78        ))
79    })?;
80    let api = lys.api_url.as_deref().unwrap_or(&lys.issuer);
81    let root = RootPresence::new(&lys.network_id, api, credential.trim().to_owned(), handle)
82        .map_err(|_| refused("lys_root_question_invalid".to_owned()))?;
83    Ok(RootQuestion::AtStart(Arc::new(root)))
84}
85
86/// Builds the start question for a server with no grant stream owner, installs
87/// what `/health` reports, and asks once in the background, so start never
88/// waits on Lys.
89///
90/// # Errors
91/// As [`root_question`], or a second installation on the health handle.
92pub fn start(
93    lys: &LysConfig,
94    health: &crate::health::HealthServerHandle,
95    runtime: Option<&tokio::runtime::Runtime>,
96) -> Result<(), ServerError> {
97    match root_question(lys, runtime.map(|runtime| runtime.handle().clone()))? {
98        RootQuestion::Owner => Ok(()),
99        RootQuestion::Unasked(cause) => health.install_lys_root(RootSlot::Unasked { cause }),
100        RootQuestion::AtStart(root) => {
101            health.install_lys_root(RootSlot::Asked(Arc::clone(&root)))?;
102            root.ask_in_background();
103            Ok(())
104        }
105    }
106}