use super::actions::PublishingActions;
use super::budgets::LysBudgets;
use super::context_watch::ContextReporter;
use super::keys::{ChannelKeySender, ConnectionPublisher, ServicePublisher};
use super::lys_http::{HttpLys, LysHttp};
use super::seats::LysSeatStarter;
use super::{ServiceEffectError, Services};
use crate::auth_lys::LysPassConsumer;
use crate::config::types::LysConfig;
use crate::server::connection::LiminalConnectionServices;
use std::sync::Arc;
pub struct ServiceBindings {
seats: LysSeatStarter,
keys: ChannelKeySender,
budgets: LysBudgets,
actions: PublishingActions,
context: Arc<ContextReporter>,
watch_context: bool,
}
impl std::fmt::Debug for ServiceBindings {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("ServiceBindings")
.field("seats", &self.seats)
.field("keys", &self.keys)
.field("budgets", &self.budgets)
.field("actions", &self.actions)
.field("watch_context", &self.watch_context)
.finish_non_exhaustive()
}
}
impl ServiceBindings {
#[must_use]
pub fn new(
http: &Arc<dyn LysHttp>,
bearer: &str,
machine: String,
publisher: &Arc<dyn ServicePublisher>,
) -> Self {
let context = Arc::new(ContextReporter::new(Arc::clone(http), bearer.to_owned()));
Self {
seats: LysSeatStarter::new(Arc::clone(http), bearer.to_owned(), machine),
keys: ChannelKeySender::new(Arc::clone(publisher)),
budgets: LysBudgets::new(Arc::clone(http), bearer.to_owned()),
actions: PublishingActions::new(Arc::clone(publisher), Arc::clone(&context)),
context,
watch_context: false,
}
}
#[must_use]
pub const fn watches_context(&self) -> bool {
self.watch_context
}
#[must_use]
pub fn services(&self) -> Services<'_> {
Services {
seats: &self.seats,
keys: &self.keys,
budgets: &self.budgets,
actions: &self.actions,
}
}
#[must_use]
pub fn context_reporter(&self) -> Arc<ContextReporter> {
Arc::clone(&self.context)
}
pub fn configure(
lys: &LysConfig,
services: Arc<LiminalConnectionServices>,
handle: tokio::runtime::Handle,
now: u64,
) -> Result<Option<Self>, ServiceEffectError> {
let Some(section) = lys.services.as_ref() else {
return Ok(None);
};
let bearer = machine_bearer(lys, now)?;
let http: Arc<dyn LysHttp> = Arc::new(HttpLys::new(&lys.issuer, handle)?);
let publisher: Arc<dyn ServicePublisher> =
Arc::new(ConnectionPublisher::new(services, section.host.clone()));
let mut bindings = Self::new(&http, &bearer, section.machine.clone(), &publisher);
bindings.watch_context = section.context_watch;
Ok(Some(bindings))
}
}
pub fn machine_bearer(lys: &LysConfig, now: u64) -> Result<String, ServiceEffectError> {
let machine = lys.machine.as_ref().ok_or_else(|| {
ServiceEffectError(
"auth.lys.services needs auth.lys.machine: the services ask Lys as this machine"
.to_owned(),
)
})?;
let keys_json = std::fs::read_to_string(&lys.keys_path).map_err(|error| {
ServiceEffectError(format!(
"auth.lys.keys_path {}: {error}",
lys.keys_path.display()
))
})?;
let adapter = crate::auth_lys::lys_pass_adapter::LysPassAdapter::from_keys_json(
&keys_json,
lys.issuer.clone(),
)
.map_err(|error| ServiceEffectError(format!("auth.lys key set refused: {error}")))?;
let pass_bytes = std::fs::read(&machine.pass_path).map_err(|error| {
ServiceEffectError(format!(
"auth.lys.machine.pass_path {}: {error}",
machine.pass_path.display()
))
})?;
adapter
.verify_offline(&pass_bytes, crate::auth_lys::LIMINAL_PASS_AUDIENCE, now)
.map_err(|error| ServiceEffectError(format!("auth.lys.machine pass refused: {error}")))?;
let token = std::str::from_utf8(&pass_bytes)
.map_err(|_| ServiceEffectError("auth.lys.machine pass is not UTF-8".to_owned()))?;
Ok(token.trim().to_owned())
}