securitydept_token_set_context/frontend_oidc_mode/
runtime.rs1use securitydept_utils::observability::{
12 AuthFlowDiagnosis, AuthFlowDiagnosisField, AuthFlowOperation, DiagnosedResult,
13};
14
15use super::{
16 capabilities::FrontendOidcModeCapabilities, config::ResolvedFrontendOidcModeConfig,
17 contracts::FrontendOidcModeConfigProjection,
18};
19
20#[derive(Debug, Clone)]
31pub struct FrontendOidcModeRuntime {
32 config: ResolvedFrontendOidcModeConfig,
33}
34
35impl FrontendOidcModeRuntime {
36 pub fn new(config: ResolvedFrontendOidcModeConfig) -> Self {
40 config.capabilities.warn_unsafe();
41 Self { config }
42 }
43
44 pub fn config(&self) -> &ResolvedFrontendOidcModeConfig {
46 &self.config
47 }
48
49 pub fn capabilities(&self) -> &FrontendOidcModeCapabilities {
51 &self.config.capabilities
52 }
53
54 pub async fn config_projection(&self) -> std::io::Result<FrontendOidcModeConfigProjection> {
64 self.config_projection_with_diagnosis().await.into_result()
65 }
66
67 pub async fn config_projection_with_diagnosis(
69 &self,
70 ) -> DiagnosedResult<FrontendOidcModeConfigProjection, std::io::Error> {
71 let base_diagnosis = AuthFlowDiagnosis::started(AuthFlowOperation::PROJECTION_CONFIG_FETCH)
72 .field(AuthFlowDiagnosisField::MODE, "frontend_oidc")
73 .field("client_id", self.config.oidc_client.client_id.clone())
74 .field("pkce_enabled", self.config.oidc_client.pkce_enabled)
75 .field(
76 "claims_check_script_configured",
77 self.config.oidc_client.claims_check_script.is_some(),
78 );
79
80 match self.config.to_config_projection().await {
81 Ok(projection) => DiagnosedResult::success(
82 base_diagnosis
83 .with_outcome(
84 securitydept_utils::observability::AuthFlowDiagnosisOutcome::Succeeded,
85 )
86 .field("has_client_secret", projection.client_secret.is_some())
87 .field(
88 "has_claims_check_script",
89 projection.claims_check_script.is_some(),
90 ),
91 projection,
92 ),
93 Err(error) => DiagnosedResult::failure(
94 base_diagnosis
95 .with_outcome(
96 securitydept_utils::observability::AuthFlowDiagnosisOutcome::Failed,
97 )
98 .field(
99 AuthFlowDiagnosisField::FAILURE_STAGE,
100 "projection_generation",
101 ),
102 error,
103 ),
104 }
105 }
106}
107
108#[cfg(test)]
113mod tests {
114 use securitydept_oauth_provider::{OAuthProviderRemoteConfig, OidcSharedConfig};
115
116 use super::*;
117 use crate::frontend_oidc_mode::{
118 capabilities::UnsafeFrontendClientSecret,
119 config::{FrontendOidcModeConfig, FrontendOidcModeConfigSource},
120 };
121
122 fn test_runtime() -> FrontendOidcModeRuntime {
123 let shared = OidcSharedConfig {
124 remote: OAuthProviderRemoteConfig {
125 well_known_url: Some(
126 "https://auth.example.com/.well-known/openid-configuration".to_string(),
127 ),
128 ..Default::default()
129 },
130 client_id: Some("spa-client".to_string()),
131 ..Default::default()
132 };
133
134 let config = FrontendOidcModeConfig::default()
135 .resolve_all(&shared)
136 .expect("should resolve");
137 FrontendOidcModeRuntime::new(config)
138 }
139
140 #[tokio::test]
141 async fn runtime_produces_config_projection() {
142 let runtime = test_runtime();
143 let projection = runtime
144 .config_projection()
145 .await
146 .expect("projection should succeed");
147 assert_eq!(projection.client_id, "spa-client");
148 assert_eq!(
149 projection.well_known_url.as_deref(),
150 Some("https://auth.example.com/.well-known/openid-configuration")
151 );
152 assert!(projection.client_secret.is_none());
154 }
155
156 #[tokio::test]
157 async fn runtime_exposes_client_secret_when_capability_enabled() {
158 let shared = OidcSharedConfig {
159 remote: OAuthProviderRemoteConfig {
160 well_known_url: Some(
161 "https://auth.example.com/.well-known/openid-configuration".to_string(),
162 ),
163 ..Default::default()
164 },
165 client_id: Some("spa-client".to_string()),
166 ..Default::default()
167 };
168
169 let config = FrontendOidcModeConfig {
170 oidc_client: securitydept_oidc_client::OidcClientRawConfig {
171 client_secret: Some("test-secret".to_string()),
172 ..Default::default()
173 },
174 capabilities: FrontendOidcModeCapabilities {
175 unsafe_frontend_client_secret: UnsafeFrontendClientSecret::Enabled,
176 },
177 };
178 let resolved = config.resolve_all(&shared).expect("should resolve");
179 let runtime = FrontendOidcModeRuntime::new(resolved);
180 let projection = runtime
181 .config_projection()
182 .await
183 .expect("projection should succeed");
184 assert_eq!(projection.client_secret.as_deref(), Some("test-secret"));
185 }
186
187 #[tokio::test]
188 async fn runtime_reports_projection_diagnosis() {
189 let runtime = test_runtime();
190 let diagnosed = runtime.config_projection_with_diagnosis().await;
191
192 assert!(diagnosed.result().is_ok());
193 assert_eq!(
194 diagnosed.diagnosis().operation,
195 AuthFlowOperation::PROJECTION_CONFIG_FETCH
196 );
197 assert_eq!(diagnosed.diagnosis().outcome.as_str(), "succeeded");
198 assert_eq!(
199 diagnosed.diagnosis().fields[AuthFlowDiagnosisField::MODE],
200 "frontend_oidc"
201 );
202 assert_eq!(diagnosed.diagnosis().fields["client_id"], "spa-client");
203 }
204}