securitydept_basic_auth_context/config/
mod.rs1use securitydept_creds::{BasicAuthCred, BasicAuthCredsConfig};
2use securitydept_realip::{RealIpAccessConfig, RealIpAccessManager};
3use securitydept_utils::redirect::{RedirectTargetConfig, UriRelativeRedirectTargetResolver};
4use serde::{Deserialize, Serialize};
5use snafu::Snafu;
6use typed_builder::TypedBuilder;
7
8use crate::{BasicAuthContextError, BasicAuthContextResult};
9
10pub mod validator;
11
12pub use validator::{
13 BasicAuthContextConfigValidationError, BasicAuthContextConfigValidator,
14 BasicAuthContextFixedPostAuthRedirectValidator, BasicAuthContextFixedSingleZonePathValidator,
15 BasicAuthContextRejectZonePostAuthRedirectOverrideValidator,
16 NoopBasicAuthContextConfigValidator,
17};
18
19#[derive(Debug, Clone, Serialize, Deserialize, TypedBuilder)]
20pub struct BasicAuthZoneConfig {
21 #[builder(default = default_zone_prefix())]
22 #[serde(default = "default_zone_prefix")]
23 pub zone_prefix: String,
24 #[builder(default = default_login_subpath())]
25 #[serde(default = "default_login_subpath")]
26 pub login_subpath: String,
27 #[builder(default = default_logout_subpath())]
28 #[serde(default = "default_logout_subpath")]
29 pub logout_subpath: String,
30 #[builder(default, setter(strip_option))]
31 #[serde(default)]
32 pub realm: Option<String>,
33 #[serde(default)]
34 #[builder(default, setter(strip_option))]
35 pub post_auth_redirect: Option<RedirectTargetConfig>,
36}
37
38impl Default for BasicAuthZoneConfig {
39 fn default() -> Self {
40 Self {
41 zone_prefix: default_zone_prefix(),
42 login_subpath: default_login_subpath(),
43 logout_subpath: default_logout_subpath(),
44 realm: None,
45 post_auth_redirect: None,
46 }
47 }
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize, TypedBuilder)]
51pub struct BasicAuthContextConfig<Creds>
52where
53 Creds: BasicAuthCred + Serialize + for<'a> Deserialize<'a>,
54{
55 #[serde(
56 flatten,
57 bound = "Creds: BasicAuthCred + Serialize + for<'a> Deserialize<'a>",
58 default = "BasicAuthCredsConfig::default"
59 )]
60 #[builder(default = BasicAuthCredsConfig::default())]
61 pub creds: BasicAuthCredsConfig<Creds>,
62 #[serde(default)]
63 #[builder(default, setter(strip_option))]
64 pub real_ip_access: Option<RealIpAccessConfig>,
65 #[serde(default)]
66 #[builder(default = Vec::new())]
67 pub zones: Vec<BasicAuthZoneConfig>,
68 #[serde(default)]
69 #[builder(default, setter(strip_option))]
70 pub realm: Option<String>,
71 #[serde(default = "default_post_auth_redirect")]
72 #[builder(default = default_post_auth_redirect())]
73 pub post_auth_redirect: RedirectTargetConfig,
74}
75
76impl<Creds> Default for BasicAuthContextConfig<Creds>
77where
78 Creds: BasicAuthCred + Serialize + for<'a> Deserialize<'a>,
79{
80 fn default() -> Self {
81 Self {
82 creds: BasicAuthCredsConfig::default(),
83 post_auth_redirect: default_post_auth_redirect(),
84 real_ip_access: None,
85 zones: Vec::new(),
86 realm: None,
87 }
88 }
89}
90
91#[derive(Debug, Clone, PartialEq, Eq)]
92pub struct ResolvedBasicAuthZoneConfig {
93 pub zone_prefix: String,
94 pub login_subpath: String,
95 pub logout_subpath: String,
96 pub realm: String,
97 pub post_auth_redirect: RedirectTargetConfig,
98}
99
100#[derive(Debug, Clone)]
101pub struct ResolvedBasicAuthContextConfig<Creds>
102where
103 Creds: BasicAuthCred + Serialize + for<'a> Deserialize<'a>,
104{
105 pub creds: BasicAuthCredsConfig<Creds>,
106 pub real_ip_access: Option<RealIpAccessConfig>,
107 pub zones: Vec<ResolvedBasicAuthZoneConfig>,
108 pub realm: String,
109 pub post_auth_redirect: RedirectTargetConfig,
110}
111
112#[derive(Debug, Snafu)]
113pub enum BasicAuthContextConfigBuildError {
114 #[snafu(transparent)]
115 Validation {
116 source: BasicAuthContextConfigValidationError,
117 },
118 #[snafu(transparent)]
119 Context { source: BasicAuthContextError },
120}
121
122pub trait BasicAuthContextConfigSource<Creds>
123where
124 Creds: BasicAuthCred + Serialize + for<'a> Deserialize<'a>,
125{
126 fn creds_config(&self) -> &BasicAuthCredsConfig<Creds>;
127 fn real_ip_access_config(&self) -> Option<&RealIpAccessConfig>;
128 fn zones_config(&self) -> &[BasicAuthZoneConfig];
129 fn realm_config(&self) -> Option<&str>;
130 fn post_auth_redirect_config(&self) -> &RedirectTargetConfig;
131
132 fn resolve_creds_config(&self) -> BasicAuthCredsConfig<Creds>
133 where
134 Creds: Clone,
135 {
136 self.creds_config().clone()
137 }
138
139 fn resolve_real_ip_access_config(&self) -> BasicAuthContextResult<Option<RealIpAccessConfig>> {
140 let config = self.real_ip_access_config().cloned();
141 config
142 .clone()
143 .map(RealIpAccessManager::from_config)
144 .transpose()
145 .map_err(|source| BasicAuthContextError::RealIp { source })?;
146 Ok(config)
147 }
148
149 fn resolve_realm_config(&self) -> String {
150 self.realm_config()
151 .map(ToOwned::to_owned)
152 .unwrap_or_else(default_realm)
153 }
154
155 fn resolve_post_auth_redirect_config(&self) -> BasicAuthContextResult<RedirectTargetConfig> {
156 let config = self.post_auth_redirect_config().clone();
157 UriRelativeRedirectTargetResolver::from_config(config.clone())
158 .map_err(|source| BasicAuthContextError::RedirectTarget { source })?;
159 Ok(config)
160 }
161
162 fn resolve_zones_config(
163 &self,
164 default_realm: &str,
165 default_post_auth_redirect: &RedirectTargetConfig,
166 ) -> BasicAuthContextResult<Vec<ResolvedBasicAuthZoneConfig>> {
167 self.zones_config()
168 .iter()
169 .cloned()
170 .map(|zone| {
171 resolve_basic_auth_zone_config(zone, default_realm, default_post_auth_redirect)
172 })
173 .collect()
174 }
175
176 fn resolve_all(
177 &self,
178 ) -> Result<ResolvedBasicAuthContextConfig<Creds>, BasicAuthContextConfigBuildError>
179 where
180 Creds: Clone,
181 {
182 let validator = NoopBasicAuthContextConfigValidator;
183 self.resolve_all_with_validator(&validator)
184 }
185
186 fn resolve_all_with_validator<V>(
187 &self,
188 validator: &V,
189 ) -> Result<ResolvedBasicAuthContextConfig<Creds>, BasicAuthContextConfigBuildError>
190 where
191 Creds: Clone,
192 V: BasicAuthContextConfigValidator<Creds>,
193 {
194 validator
195 .validate_basic_auth_context_config(self)
196 .map_err(|source| BasicAuthContextConfigBuildError::Validation { source })?;
197
198 let realm = self.resolve_realm_config();
199 let post_auth_redirect = self
200 .resolve_post_auth_redirect_config()
201 .map_err(|source| BasicAuthContextConfigBuildError::Context { source })?;
202
203 Ok(ResolvedBasicAuthContextConfig {
204 creds: self.resolve_creds_config(),
205 real_ip_access: self
206 .resolve_real_ip_access_config()
207 .map_err(|source| BasicAuthContextConfigBuildError::Context { source })?,
208 zones: self
209 .resolve_zones_config(&realm, &post_auth_redirect)
210 .map_err(|source| BasicAuthContextConfigBuildError::Context { source })?,
211 realm,
212 post_auth_redirect,
213 })
214 }
215}
216
217impl<Creds> BasicAuthContextConfigSource<Creds> for BasicAuthContextConfig<Creds>
218where
219 Creds: BasicAuthCred + Serialize + for<'a> Deserialize<'a>,
220{
221 fn creds_config(&self) -> &BasicAuthCredsConfig<Creds> {
222 &self.creds
223 }
224
225 fn real_ip_access_config(&self) -> Option<&RealIpAccessConfig> {
226 self.real_ip_access.as_ref()
227 }
228
229 fn zones_config(&self) -> &[BasicAuthZoneConfig] {
230 &self.zones
231 }
232
233 fn realm_config(&self) -> Option<&str> {
234 self.realm.as_deref()
235 }
236
237 fn post_auth_redirect_config(&self) -> &RedirectTargetConfig {
238 &self.post_auth_redirect
239 }
240}
241
242pub(crate) fn default_zone_prefix() -> String {
243 "/basic".to_string()
244}
245
246pub(crate) fn default_login_subpath() -> String {
247 "/login".to_string()
248}
249
250pub(crate) fn default_logout_subpath() -> String {
251 "/logout".to_string()
252}
253
254pub(crate) fn default_post_auth_redirect() -> RedirectTargetConfig {
255 RedirectTargetConfig::strict_default("/")
256}
257
258pub(crate) fn default_realm() -> String {
259 "securitydept".to_string()
260}
261
262pub(crate) fn resolve_basic_auth_zone_config(
263 zone: BasicAuthZoneConfig,
264 default_realm: &str,
265 default_post_auth_redirect: &RedirectTargetConfig,
266) -> BasicAuthContextResult<ResolvedBasicAuthZoneConfig> {
267 let post_auth_redirect = zone
268 .post_auth_redirect
269 .unwrap_or_else(|| default_post_auth_redirect.clone());
270 UriRelativeRedirectTargetResolver::from_config(post_auth_redirect.clone())
271 .map_err(|source| BasicAuthContextError::RedirectTarget { source })?;
272
273 Ok(ResolvedBasicAuthZoneConfig {
274 zone_prefix: zone.zone_prefix,
275 login_subpath: zone.login_subpath,
276 logout_subpath: zone.logout_subpath,
277 realm: zone.realm.unwrap_or_else(|| default_realm.to_string()),
278 post_auth_redirect,
279 })
280}