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}