Skip to main content

pubhubs/servers/config/
core.rs

1//! Configuration (files)
2use core::fmt::Debug;
3use std::ops::{Deref, DerefMut};
4use std::path::{Path, PathBuf};
5
6use anyhow::{Context as _, Result};
7use url::Url;
8
9use crate::misc::serde_ext::bytes_wrapper::{B64, B64UU};
10use crate::servers::{for_all_servers, server::Server as _};
11use crate::{
12    api::{self},
13    attr,
14    common::{elgamal, kem},
15    hub,
16    misc::{jwt, serde_ext, time_ext},
17    servers::yivi,
18};
19
20use super::host_aliases::{HostAliases, UrlPwa};
21use super::log::LogConfig;
22
23/// Configuration for one, or several, of the PubHubs servers
24///
25/// Also used for the `pubhubs admin` cli command.  In that case only `phc_url` needs to be set.
26#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
27#[serde(deny_unknown_fields)]
28pub struct Config {
29    /// URL of the PubHubs Central server.
30    ///
31    /// Any information on the other servers that can be stored at PHC is stored at PHC.
32    pub phc_url: UrlPwa,
33
34    /// Configure logging.  Overwrites what's passed through `RUST_LOG`.
35    #[serde(default)]
36    pub log: Option<LogConfig>,
37
38    #[serde(skip)]
39    pub(crate) preparation_state: PreparationState,
40
41    /// Specify abbreviations for an IP address that are only valid in this configuration file.
42    ///
43    /// Any [UrlPwa] that contains one of the aliases as host name exactly (so no subdomain)
44    /// will be modified to have as host name the associated IP address.
45    #[serde(default)]
46    pub host_aliases: HostAliases,
47
48    /// Path with respect to which relative paths are interpretted.
49    #[serde(default)]
50    pub wd: PathBuf,
51
52    /// Configuration to run PubHubs Central
53    pub phc: Option<ServerConfig<phc::ExtraConfig>>,
54
55    /// Configuration to run the Transcryptor
56    pub transcryptor: Option<ServerConfig<transcryptor::ExtraConfig>>,
57
58    /// Configuration to run the Authentication Server
59    pub auths: Option<ServerConfig<auths::ExtraConfig>>,
60}
61
62/// Represents the level of preparation of a [Config] instance.
63#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
64pub(crate) enum PreparationState {
65    /// State after loading config file from disk.
66    #[default]
67    Unprepared,
68
69    /// [`Config::preliminary_prep`] has been called.
70    Preliminary,
71
72    /// [`Config`] is completely prepared after [`PrepareConfig::prepare`] has been called on it.
73    Complete,
74}
75
76/// Configuration for one server.  Derefs to `ServerSpecific`..
77#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
78#[serde(deny_unknown_fields)]
79pub struct ServerConfig<ServerSpecific> {
80    /// Port to bind this server to
81    #[serde(default)]
82    pub port: u16,
83
84    /// Ip addresses to bind to
85    #[serde(default = "default_ips")]
86    pub ips: Box<[std::net::IpAddr]>,
87
88    /// Deprecated; consumed and ignored on read, omitted on write.
89    // `skip_serializing` rather than `skip`: an old config may still carry the `bind_to` key, and
90    // `skip` would not consume it — which `deny_unknown_fields` then rejects as an unknown field.
91    #[serde(default, skip_serializing)]
92    pub bind_to: serde::de::IgnoredAny,
93
94    /// Random string used by this server to identify itself.  Randomly generated if not set.
95    /// May be set manually when multiple instances of the same server are used.
96    pub self_check_code: Option<String>,
97
98    /// Key used to sign JSON web tokens generated by this server.
99    /// If `None`, one is generated automatically (which is not suitable for production.)
100    ///
101    /// Generate using `cargo run tools generate signing-key`.
102    pub signing_key: Option<api::SigningKeyBytes>,
103
104    /// Deprecated, superseded by [`signing_key`](Self::signing_key); accepted (and ignored) so that
105    /// existing config files still carrying the old ed25519 `jwt_key` continue to load.  Omitted on
106    /// write.
107    #[serde(default, skip_serializing)]
108    pub jwt_key: serde::de::IgnoredAny,
109
110    /// Secret seed used only to derive this server's own non-permanent local secrets.
111    ///
112    /// Formerly an ElGamal private key that was also published (as `enc_key`) and used to establish
113    /// inter-server shared secrets; both of those roles now belong to the post-quantum KEM, so a
114    /// zero placeholder is published in its stead and only the local-seed role remains.
115    ///
116    /// If `None`, one is generated automatically (which is not suitable for production).
117    pub enc_key: Option<B64>,
118
119    /// Symmetric (HMAC) key, hex-encoded, that authenticates requests to the admin endpoints; the
120    /// same secret signs (admin cli) and verifies (server).
121    /// If `None`, one is generated automatically and printed to the log.
122    pub admin_key: Option<serde_ext::bytes_wrapper::B16>,
123
124    /// If the server needs an object store, use this one.
125    pub object_store: Option<ObjectStoreConfig>,
126
127    /// What version (if any) to claim this pubhubs binary is running.
128    /// Uses [`crate::servers::version()`] by default.  Should only be used for
129    /// troubleshooting/debugging.
130    #[serde(default = "default_version")]
131    pub version: Option<String>,
132
133    #[serde(flatten)]
134    /// Can be accessed via [`Deref`].
135    extra: ServerSpecific,
136}
137
138impl<X> Deref for ServerConfig<X> {
139    type Target = X;
140
141    fn deref(&self) -> &X {
142        &self.extra
143    }
144}
145
146impl<X> DerefMut for ServerConfig<X> {
147    fn deref_mut(&mut self) -> &mut X {
148        &mut self.extra
149    }
150}
151
152fn default_version() -> Option<String> {
153    crate::servers::version().map(str::to_string)
154}
155
156fn default_ips() -> Box<[std::net::IpAddr]> {
157    Box::new([
158        // Bind :: first, as this may already bind 0.0.0.0 too;
159        // if 0.0.0.0 is bound first a separate service will be started for ::.
160        std::net::Ipv6Addr::UNSPECIFIED.into(), // ::
161        std::net::Ipv4Addr::UNSPECIFIED.into(), // 0.0.0.0
162    ])
163}
164
165impl<'a, X> std::net::ToSocketAddrs for &'a ServerConfig<X> {
166    type Iter = Box<dyn Iterator<Item = std::net::SocketAddr> + 'a>;
167
168    fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> {
169        Ok(Box::new(self.ips.iter().map(|ip| (*ip, self.port).into())))
170    }
171}
172
173impl Config {
174    /// Loads [Config] from `path` and generates random values.
175    ///
176    /// Returns [None] if there's no file there.
177    pub fn load_from_path(path: &Path) -> Result<Option<Self>> {
178        // NOTE: the toml crate does not have a `from_reader` like `serde_json` does
179        let mut res: Self = toml::from_str(&match std::fs::read_to_string(path) {
180            Ok(contents) => contents,
181            Err(e) => match e.kind() {
182                std::io::ErrorKind::NotFound => return Ok(None),
183                _ => {
184                    return Err(e)
185                        .with_context(|| format!("could not open config file {}", path.display()));
186                }
187            },
188        })
189        .with_context(|| format!("could not parse config file {}", path.display()))?;
190
191        if res.wd.as_os_str().is_empty() {
192            res.wd = path
193                .canonicalize()
194                .with_context(|| format!("failed to canonicalize path {}", path.display()))?
195                .parent()
196                .expect("did not expect a configuration file without a parent directory")
197                .into();
198        }
199
200        if !res.wd.is_absolute() {
201            anyhow::bail!(
202                "if you specify a working directory (`wd` in {}) it must be absolute",
203                path.display()
204            );
205        }
206
207        res.preliminary_prep()?;
208
209        log::info!(
210            "loaded config file from {};  interpretting relative paths in {}",
211            path.display(),
212            res.wd.display()
213        );
214
215        Ok(Some(res))
216    }
217
218    pub fn preliminary_prep(&mut self) -> Result<()> {
219        anyhow::ensure!(
220            self.preparation_state == PreparationState::Unprepared,
221            "configuration already (partially) prepared: {:?}",
222            self.preparation_state
223        );
224
225        self.host_aliases.resolve_all()?;
226        self.host_aliases.dealias(&mut self.phc_url);
227
228        self.preparation_state = PreparationState::Preliminary;
229
230        Ok(())
231    }
232
233    /// Clones this configuration and strips out everything that's not needed to run
234    /// the specified server.  Also generated any random values not yet set.
235    pub async fn prepare_for(&self, server: crate::servers::Name) -> Result<Self> {
236        anyhow::ensure!(
237            self.preparation_state == PreparationState::Preliminary,
238            "configuration not in the correct preparation state"
239        );
240
241        // destruct to make sure we consider every field of Config
242        let Self {
243            log: _,
244            host_aliases,
245            phc_url,
246            wd,
247            preparation_state,
248            phc: _,
249            transcryptor: _,
250            auths: _,
251        } = self;
252
253        let mut config: Config = Config {
254            log: None, // <- servers do not read this
255            host_aliases: host_aliases.clone(),
256            phc_url: phc_url.clone(),
257            wd: wd.clone(),
258            preparation_state: *preparation_state,
259            phc: None,
260            transcryptor: None,
261            auths: None,
262        };
263
264        macro_rules! clone_only_server {
265            ($server:ident) => {
266                if crate::servers::$server::Server::NAME == server {
267                    assert!(self.$server.is_some());
268                    config.$server.clone_from(&self.$server);
269                }
270            };
271        }
272
273        for_all_servers!(clone_only_server);
274
275        config.prepare().await?;
276
277        Ok(config)
278    }
279
280    /// Prepares [`Config`] to be run; used by [`Config::prepare_for`].
281    pub async fn prepare(&mut self) -> anyhow::Result<()> {
282        let pcc = Pcc::new(actix_web::dev::Extensions::new());
283
284        PrepareConfig::prepare(self, pcc).await
285    }
286
287    /// Creates a new [Config] from the current one by updating a specific part
288    pub fn json_updated(&self, pointer: &str, new_value: serde_json::Value) -> Result<Self> {
289        let mut json_config: serde_json::Value =
290            serde_json::to_value(self).context("failed to serialize config")?;
291
292        let to_be_modified: &mut serde_json::Value =
293            json_config.pointer_mut(pointer).with_context(|| {
294                format!(
295                    "wanted to modify {pointer} of the configuration file, but that points nowhere"
296                )
297            })?;
298
299        to_be_modified.clone_from(&new_value);
300
301        let new_config: Config = serde_json::from_value(json_config).with_context(|| {
302            format!(
303                "wanted to change {pointer} of the configuration file to {new_value}, but the new configuration did not deserialize",
304            )
305        })?;
306
307        Ok(new_config)
308    }
309}
310
311#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
312pub struct ObjectStoreConfig {
313    /// E.g. `memory:///` or `s3://bucket`.
314    ///
315    /// Only `memory://` and `s3://` work: we build `object_store` without its default features (see
316    /// Cargo.toml), so the other schemes it lists — including `file://` — are not compiled in:
317    ///
318    ///   <https://docs.rs/object_store/latest/object_store/enum.ObjectStoreScheme.html>
319    pub url: UrlPwa,
320
321    /// Additional options passed to the builder of the object store.
322    #[serde(default)]
323    pub options: std::collections::HashMap<String, String>,
324}
325
326impl Default for ObjectStoreConfig {
327    fn default() -> Self {
328        Self {
329            url: From::<Url>::from("memory:///".try_into().unwrap()),
330            options: Default::default(),
331        }
332    }
333}
334
335pub mod phc {
336    use super::*;
337
338    #[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
339    #[serde(deny_unknown_fields)]
340    pub struct ExtraConfig {
341        /// Where can we reach the transcryptor?
342        pub transcryptor_url: UrlPwa,
343
344        /// Where can we reach the authentication server?
345        pub auths_url: UrlPwa,
346
347        /// The URL to pubhubs used by end-clients.
348        ///
349        /// Currently `https://app.pubhubs.net` for production, `https://main.pubhubs.ihub.ru.nl` for
350        /// acceptance, and `http://localhost:8080` for local development.  
351        pub global_client_url: UrlPwa,
352
353        /// The hubs that are known to us
354        pub hubs: Vec<hub::BasicInfo<UrlPwa>>,
355
356        /// `x_PHC` from the whitepaper; randomly generated if not set
357        ///
358        /// Generate using `cargo run tools generate scalar`.
359        pub master_enc_key_part: Option<elgamal::PrivateKey>,
360
361        /// Secret used to derive [`Attr::id`]s.
362        ///
363        /// Randomly generated if not set, which is not suitable for production.
364        ///
365        /// [`Attr::id`]: crate::attr::Attr::id
366        pub attr_id_secret: Option<B64UU>,
367
368        /// Authentication tokens issued to the global client are valid for this duration.
369        ///
370        /// Auth tokens are validated based on their own contents - there's no list of valid
371        /// authentication tokens in a database somewhere.  This means that when a user is banned,
372        /// the authentication tokens remain valid until they expire.  The validity duration of
373        /// auth tokens should thus not be too long.
374        #[serde(with = "time_ext::human_duration")]
375        #[serde(default = "default_auth_token_validity")]
376        pub auth_token_validity: core::time::Duration,
377
378        /// [`api::phc::user::PpNonce`]s issued to the global client are valid for this duration.
379        #[serde(with = "time_ext::human_duration")]
380        #[serde(default = "default_pp_nonce_validity")]
381        pub pp_nonce_validity: core::time::Duration,
382
383        /// Registration pseudonyms issued to the global client via [`api::phc::user::CardPseudEP`]
384        /// are valid for this duration.
385        #[serde(with = "time_ext::human_duration")]
386        #[serde(default = "default_card_pseud_validity")]
387        pub card_pseud_validity: core::time::Duration,
388
389        /// Secret used to derive `hmac`s for the retrieval of user objects.
390        ///
391        /// Randomly generated if not set.
392        pub user_object_hmac_secret: Option<B64UU>,
393
394        /// Quotas for a user
395        #[serde(default)]
396        pub user_quota: api::phc::user::Quota,
397
398        /// Deprecated; consumed and ignored on read, omitted on write.
399        #[serde(default, skip_serializing)]
400        pub card: serde::de::IgnoredAny,
401
402        #[serde(default)]
403        pub hub_cache: crate::servers::phc::HubCacheConfig,
404    }
405
406    fn default_auth_token_validity() -> core::time::Duration {
407        core::time::Duration::from_secs(60 * 60) // 1 hour - the user might need to add attributes
408        // to their Yivi app
409    }
410
411    fn default_pp_nonce_validity() -> core::time::Duration {
412        core::time::Duration::from_secs(30)
413        // no user interaction required
414    }
415
416    fn default_card_pseud_validity() -> core::time::Duration {
417        core::time::Duration::from_secs(30)
418        // no user interaction required
419    }
420}
421
422pub mod transcryptor {
423    use super::*;
424
425    #[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
426    #[serde(deny_unknown_fields)]
427    pub struct ExtraConfig {
428        /// `x_T` from the whitepaper; randomly generated if not set
429        ///
430        /// Generate using `cargo run tools generate scalar`.
431        pub master_enc_key_part: Option<elgamal::PrivateKey>,
432
433        /// Used to generate the *pseudonymisation factor secret* `g_H` given hub `H`'s identifier.
434        ///
435        /// Should **never be changed** in a production environment.
436        ///
437        /// Randomly generated when not set.t
438        pub pseud_factor_secret: Option<B64UU>,
439
440        /// Hybrid post-quantum [`kem`] decapsulation key, used to establish a shared secret
441        /// with pubhubs central.  Randomly generated when not set.
442        ///
443        /// Generate using `cargo run tools generate decap-key`
444        pub decap_key: Option<kem::DecapKeyBytes>,
445    }
446}
447
448pub mod auths {
449    use super::*;
450
451    #[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
452    #[serde(deny_unknown_fields)]
453    pub struct ExtraConfig {
454        #[serde(default)]
455        pub attribute_types: Vec<attr::Type>,
456
457        /// Yivi configuration.  If `None`, yivi is not supported.
458        pub yivi: Option<YiviConfig>,
459
460        /// Authentication must be completed within this timeframe
461        /// formatted as string understood by [`humantime::parse_duration`] such as `1 week`.
462        #[serde(with = "time_ext::human_duration")]
463        #[serde(default = "default_auth_window")]
464        pub auth_window: core::time::Duration,
465
466        /// Maximum number of attribute types a single [`api::auths::AuthStartReq`] may request,
467        /// and the maximum number of alternatives allowed per attribute type
468        /// (see [`api::auths::AuthStartReq::attr_type_choices`]).
469        ///
470        /// Bounds the work an anonymous authentication-start request can trigger.
471        #[serde(default = "default_max_attr_types_per_req")]
472        pub max_attr_types_per_req: usize,
473
474        /// Used to derive attribute keys (see [`api::auths::AttrKeysEP`])
475        ///
476        /// Randomly generated when not set.  When changed, users loose access to all data
477        /// stored at PHC.
478        pub attr_key_secret: Option<B64UU>,
479
480        /// Hybrid post-quantum [`kem`] decapsulation key, used to establish a shared secret
481        /// with pubhubs central.  Randomly generated when not set.
482        ///
483        /// Generate using `cargo run tools generate decap-key`
484        pub decap_key: Option<kem::DecapKeyBytes>,
485    }
486
487    fn default_auth_window() -> core::time::Duration {
488        core::time::Duration::from_secs(60 * 60) // 1 hour - the user might need to add attributes
489        // to their Yivi app
490    }
491
492    fn default_max_attr_types_per_req() -> usize {
493        4
494    }
495
496    impl ExtraConfig {
497        /// Removes the [`attr::SourceDetails`]s of unsupported sources from
498        /// [`attribute_types`].
499        ///
500        /// [`attribute_types`]: Self::attribute_types
501        pub(super) fn filter_attribute_types(&mut self) {
502            let mut supported_sources: std::collections::HashSet<attr::Source> = Default::default();
503
504            if self.yivi.is_some() {
505                assert!(supported_sources.insert(attr::Source::Yivi));
506            }
507
508            for attr_type in self.attribute_types.iter_mut() {
509                attr_type.filter_sources(|s| supported_sources.contains(&s))
510            }
511        }
512    }
513
514    #[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
515    #[serde(deny_unknown_fields)]
516    pub struct YiviConfig {
517        /// Where can the Yivi server trusted by the authentication server be reached
518        /// by the hub client for starting disclosure requests?
519        pub requestor_url: UrlPwa,
520
521        pub requestor_creds: yivi::Credentials<yivi::SigningKey>,
522
523        /// What server name to expect in signed session results
524        pub server_name: String,
525
526        /// Verify signed session results using this key.  If not set the key is retrieved
527        /// from the yivi server.
528        pub server_key: Option<yivi::VerifyingKey>,
529
530        /// Fine-tune handling of chained sessions, see
531        /// [`api::auths::AuthStartReq::yivi_chained_session`].
532        #[serde(default)]
533        pub chained_sessions: crate::servers::auths::yivi::ChainedSessionsConfig,
534
535        /// Configuration of the pubhubs card issued by the authentication server
536        #[serde(default)]
537        pub card: crate::servers::auths::card::CardConfig,
538    }
539
540    impl YiviConfig {
541        pub fn server_creds(&self) -> yivi::Credentials<yivi::VerifyingKey> {
542            yivi::Credentials {
543                name: self.server_name.clone(),
544                key: self
545                    .server_key
546                    .clone()
547                    .expect("bug: YiviConfig was not properly prepared"),
548            }
549        }
550    }
551}
552
553/// Trait to prepare [`Config`] for use by initializing random values,
554/// and replacing aliases in [`UrlPwa`]s.
555trait PrepareConfig<C> {
556    async fn prepare(&mut self, context: C) -> anyhow::Result<()>;
557}
558
559type Pcc = std::rc::Rc<actix_web::dev::Extensions>;
560
561impl PrepareConfig<Pcc> for Config {
562    async fn prepare(&mut self, mut c: Pcc) -> anyhow::Result<()> {
563        anyhow::ensure!(
564            self.preparation_state == PreparationState::Preliminary,
565            "configuration not properly prepared"
566        );
567
568        // temporarily move `host_aliases` into Pcc
569        Pcc::get_mut(&mut c)
570            .unwrap()
571            .insert(std::mem::take(&mut self.host_aliases));
572
573        macro_rules! prep {
574            ($server:ident) => {
575                if let Some(ref mut server) = self.$server {
576                    server.prepare(c.clone()).await?;
577                }
578            };
579        }
580
581        for_all_servers!(prep);
582
583        // move `host_aliases` back to self, drop the substitute
584        drop(std::mem::replace(
585            &mut self.host_aliases,
586            Pcc::get_mut(&mut c)
587                .unwrap()
588                .remove::<HostAliases>()
589                .unwrap(),
590        ));
591
592        self.preparation_state = PreparationState::Complete;
593
594        Ok(())
595    }
596}
597
598impl<Extra: PrepareConfig<Pcc> + GetServerType> PrepareConfig<Pcc> for ServerConfig<Extra> {
599    async fn prepare(&mut self, c: Pcc) -> anyhow::Result<()> {
600        if self.port == 0 {
601            self.port = Extra::ServerT::default_port();
602        }
603
604        self.self_check_code
605            .get_or_insert_with(crate::misc::crypto::random_alphanumeric);
606
607        if self.signing_key.is_none() {
608            self.signing_key = Some(
609                api::SigningKey::generate()
610                    .map_err(|_| anyhow::anyhow!("failed to generate signing key"))?
611                    .encode(),
612            );
613        }
614        self.enc_key.get_or_insert_with(|| {
615            serde_bytes::ByteBuf::from(crate::misc::crypto::random_32_bytes()).into()
616        });
617
618        if self.admin_key.is_none() {
619            let admin_key =
620                serde_ext::bytes_wrapper::B16::from_bytes(crate::misc::crypto::random_32_bytes());
621
622            log::info!("{} admin key: {admin_key}", Extra::ServerT::NAME);
623
624            self.admin_key = Some(admin_key);
625        }
626
627        if let &mut Some(&mut ref mut osc) = &mut self.object_store.as_mut() {
628            c.get::<HostAliases>()
629                .expect("host aliases were not passed along")
630                .dealias(&mut osc.url);
631        }
632
633        self.extra.prepare(c).await?;
634
635        Ok(())
636    }
637}
638
639/// Populates `slot` with a freshly generated [`kem::DecapKeyBytes`] if it is `None`.
640fn ensure_decap_key(slot: &mut Option<kem::DecapKeyBytes>) -> anyhow::Result<()> {
641    if slot.is_none() {
642        *slot = Some(
643            kem::DecapKey::generate()
644                .and_then(|dk| dk.encode())
645                .map_err(|_| anyhow::anyhow!("generating kem decapsulation key"))?,
646        );
647    }
648    Ok(())
649}
650
651impl PrepareConfig<Pcc> for transcryptor::ExtraConfig {
652    async fn prepare(&mut self, _c: Pcc) -> anyhow::Result<()> {
653        self.master_enc_key_part
654            .get_or_insert_with(elgamal::PrivateKey::random);
655
656        self.pseud_factor_secret.get_or_insert_with(|| {
657            serde_bytes::ByteBuf::from(crate::misc::crypto::random_32_bytes()).into()
658        });
659
660        ensure_decap_key(&mut self.decap_key)?;
661
662        Ok(())
663    }
664}
665
666impl PrepareConfig<Pcc> for phc::ExtraConfig {
667    async fn prepare(&mut self, c: Pcc) -> anyhow::Result<()> {
668        self.master_enc_key_part
669            .get_or_insert_with(elgamal::PrivateKey::random);
670
671        self.attr_id_secret.get_or_insert_with(|| {
672            serde_bytes::ByteBuf::from(crate::misc::crypto::random_32_bytes()).into()
673        });
674
675        self.user_object_hmac_secret.get_or_insert_with(|| {
676            serde_bytes::ByteBuf::from(crate::misc::crypto::random_32_bytes()).into()
677        });
678
679        let ha: &HostAliases = c.get::<HostAliases>().unwrap();
680
681        ha.dealias(&mut self.transcryptor_url);
682        ha.dealias(&mut self.auths_url);
683        ha.dealias(&mut self.global_client_url);
684
685        for hub in self.hubs.iter_mut() {
686            hub.prepare(c.clone()).await?;
687        }
688
689        Ok(())
690    }
691}
692
693impl PrepareConfig<Pcc> for hub::BasicInfo<UrlPwa> {
694    async fn prepare(&mut self, c: Pcc) -> anyhow::Result<()> {
695        let ha: &HostAliases = c.get::<HostAliases>().unwrap();
696
697        ha.dealias(&mut self.url);
698
699        Ok(())
700    }
701}
702
703impl PrepareConfig<Pcc> for auths::ExtraConfig {
704    async fn prepare(&mut self, c: Pcc) -> anyhow::Result<()> {
705        if let Some(ref mut yivi_cfg) = self.yivi {
706            yivi_cfg.prepare(c).await?;
707        }
708
709        self.filter_attribute_types();
710
711        self.attr_key_secret.get_or_insert_with(|| {
712            serde_bytes::ByteBuf::from(crate::misc::crypto::random_32_bytes()).into()
713        });
714
715        ensure_decap_key(&mut self.decap_key)?;
716
717        Ok(())
718    }
719}
720
721impl PrepareConfig<Pcc> for auths::YiviConfig {
722    async fn prepare(&mut self, c: Pcc) -> anyhow::Result<()> {
723        let ha: &HostAliases = c.get::<HostAliases>().unwrap();
724
725        ha.dealias(&mut self.requestor_url);
726
727        if self.server_key.is_none() {
728            let pk_url = self.requestor_url.as_ref().join("publickey")?.to_string();
729            log::debug!("yivi server key not set; retrieving from {pk_url}");
730
731            let client = awc::Client::default();
732            let payload: bytes::Bytes = crate::misc::task::retry(|| async {
733                match client.get(&pk_url).send().await {
734                    Ok(mut res) => match res.body().await {
735                        Ok(body) => Ok::<_, std::convert::Infallible>(Some(body)),
736                        Err(err) => {
737                            log::warn!(
738                                "error reading yivi server response at {}, retrying: {err}",
739                                self.requestor_url
740                            );
741                            Ok(None)
742                        }
743                    },
744                    Err(err) => {
745                        log::warn!(
746                            "could not reach yivi server at {}, retrying: {err}",
747                            self.requestor_url
748                        );
749                        Ok(None)
750                    }
751                }
752            })
753            .await
754            .expect("retry does not fail")
755            .ok_or_else(|| {
756                log::error!("could not reach yivi server at {}", self.requestor_url);
757                anyhow::anyhow!(
758                    "getting Yivi server's public key from {pk_url} failed after retries"
759                )
760            })?;
761
762            self.server_key = Some(yivi::VerifyingKey::RS256(
763                jwt::RS256Vk::from_public_key_pem(std::str::from_utf8(&payload)?)
764                    .context("decoding public key at {pk_url}")?,
765            ));
766        }
767
768        Ok(())
769    }
770}
771
772/// Used to implement the `server_config` method on `crate::servers::<SERVER>::Details`.
773pub trait GetServerConfig {
774    type Extra;
775
776    fn server_config(config: &Config) -> &ServerConfig<Self::Extra>;
777}
778
779macro_rules! implement_get_server_config {
780    ($server:ident) => {
781        impl GetServerConfig for crate::servers::$server::Details {
782            type Extra = crate::servers::config::$server::ExtraConfig;
783
784            fn server_config(config: &Config) -> &ServerConfig<Self::Extra> {
785                &config.$server.as_ref().unwrap()
786            }
787        }
788    };
789}
790
791for_all_servers!(implement_get_server_config);
792
793/// Used to implement the `ServerT` associated type of `<SERVER>::ExtraConfig`.
794trait GetServerType {
795    type ServerT: crate::servers::Server;
796}
797
798macro_rules! implement_server_type {
799    ($server:ident) => {
800        impl GetServerType for $server::ExtraConfig {
801            type ServerT = crate::servers::$server::Server;
802        }
803    };
804}
805
806for_all_servers!(implement_server_type);