Skip to main content

acme_proxy_protocol/
profile.rs

1//! [`Profile`]: one ACME endpoint — its identity, its URLs and the subsystems
2//! that answer for it. How a generation builds every one it mounts is
3//! `server::profile`'s.
4
5use std::sync::Arc;
6
7use acme_proxy_core::config;
8use acme_proxy_core::routes;
9use acme_proxy_jobs::notify::NotifyDispatcher;
10use acme_proxy_net::challenge::ChallengeRegistry;
11use acme_proxy_policy::filter::FilterPolicy;
12use acme_proxy_signer::SignerInfo;
13
14/// One ACME endpoint: its identity, its URLs, and the three subsystems that
15/// answer for it.
16///
17/// Everything per-endpoint lives here rather than beside the global config in
18/// [`AppState`](crate::router::AppState), so a handler cannot pair one profile's signer
19/// with another's base URL — the two always travel together.
20pub struct Profile {
21    /// The configured name (`[profiles.<name>]`), also the URL segment and the
22    /// value stored in `accounts.profile` / `orders.profile`.
23    pub name: String,
24    /// Where the router mounts it: `/profile/<name>`.
25    pub path: String,
26    /// The public base for every URL this endpoint hands out and for the
27    /// RFC 8555 §6.4 `url` check: `server.base_url` + [`Profile::path`].
28    pub base_url: String,
29    /// What this endpoint's signer publishes — its CRL, anchor, renewal
30    /// opinion and `http-01` tokens — and where its revocations go. Built from
31    /// public material, so every role has one.
32    ///
33    /// **There is deliberately no backend here.** Signing and revoking need
34    /// the key, which only the `worker` role holds, and only the job handlers
35    /// it runs are handed one (`GenerationParts::signers`). A profile is what
36    /// a request is served from, so leaving the backend off it is what makes
37    /// "a request never signs" a type error rather than a convention.
38    pub signer_info: Arc<dyn SignerInfo>,
39    pub filter: Arc<FilterPolicy>,
40    pub challenges: Arc<ChallengeRegistry>,
41    pub order: config::OrderConfig,
42    pub eab: config::EabConfig,
43    /// The optional `meta` members this endpoint's directory advertises
44    /// (RFC 8555 §7.1.1). Per-profile, like everything else here: two endpoints
45    /// on one process can have different terms of service.
46    pub meta: config::MetaConfig,
47    pub notify: Arc<NotifyDispatcher>,
48}
49
50/// The subsystems and per-endpoint sections a [`Profile`] is assembled from.
51///
52/// A struct because [`Profile::new`] took nine positional parameters, four of
53/// them `Arc<dyn …>` or config sections that a reader has to count commas to
54/// tell apart.
55///
56/// `name` and `base_url` stay positional: they are what the constructor
57/// *derives* from rather than stores, and keeping them out of here is what
58/// makes "the path is never configured" visible in the signature.
59pub struct ProfileParts {
60    pub signer_info: Arc<dyn SignerInfo>,
61    pub filter: Arc<FilterPolicy>,
62    pub challenges: Arc<ChallengeRegistry>,
63    pub order: config::OrderConfig,
64    pub eab: config::EabConfig,
65    pub meta: config::MetaConfig,
66    pub notify: Arc<NotifyDispatcher>,
67}
68
69impl Profile {
70    /// Assembles a profile, deriving its path and base URL from its name —
71    /// the two are never configured, so they cannot drift from each other or
72    /// from what the database records.
73    pub fn new(name: &str, base_url: &str, parts: ProfileParts) -> Self {
74        Self {
75            name: name.to_string(),
76            base_url: routes::profile_base_url(base_url, name),
77            path: routes::profile_path(name),
78            signer_info: parts.signer_info,
79            filter: parts.filter,
80            challenges: parts.challenges,
81            order: parts.order,
82            eab: parts.eab,
83            meta: parts.meta,
84            notify: parts.notify,
85        }
86    }
87
88    /// This endpoint's directory URL — where a client starts.
89    ///
90    /// Derived here rather than `format!`-ed at each of the three call sites
91    /// (the startup log line, the admin API's profile listing, and anything
92    /// added later), all of which have to agree with what `build_router`
93    /// actually mounts.
94    #[must_use]
95    pub fn directory_url(&self) -> String {
96        format!("{}{}", self.base_url, routes::DIRECTORY)
97    }
98}