Skip to main content

securitydept_token_set_context/backend_oidc_mode/
capabilities.rs

1// ---------------------------------------------------------------------------
2// Backend-OIDC capability axes
3// ---------------------------------------------------------------------------
4//
5// Each axis uses the `Feature + FeatureKind` dual-enum pattern:
6//
7// - `FeatureKind` — simple discriminant for display, telemetry, and presets.
8// - `Feature`     — structured enum carrying axis-specific configuration.
9//
10// `Feature::kind()` bridges the two representations.
11
12use securitydept_utils::secret::SecretString;
13use serde::{Deserialize, Serialize};
14
15use super::redirect::BackendOidcModeRedirectUriConfig;
16
17// ---- Refresh material protection ----
18
19/// Simple discriminant for refresh-material protection.
20#[cfg_attr(feature = "config-schema", derive(schemars::JsonSchema))]
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
22#[serde(rename_all = "snake_case")]
23pub enum RefreshMaterialProtectionKind {
24    /// Refresh tokens are passed through without server-side sealing.
25    #[default]
26    Passthrough,
27    /// Refresh tokens are sealed (AEAD-encrypted) by the server.
28    Sealed,
29}
30
31/// How refresh tokens are stored / transmitted between server and client.
32///
33/// `Sealed` carries the `master_key` instead of scattering it as a sibling
34/// field — the type system guarantees the key is present when sealing is
35/// enabled.
36#[cfg_attr(feature = "config-schema", derive(schemars::JsonSchema))]
37#[derive(Debug, Clone, Deserialize, Default)]
38#[serde(tag = "kind", rename_all = "snake_case")]
39pub enum RefreshMaterialProtection {
40    /// Refresh tokens are passed through without server-side sealing.
41    #[default]
42    Passthrough,
43    /// Refresh tokens are sealed (AEAD-encrypted) by the server.
44    Sealed {
45        /// The AEAD master key used for sealing and unsealing.
46        master_key: SecretString,
47    },
48}
49
50impl RefreshMaterialProtection {
51    pub fn kind(&self) -> RefreshMaterialProtectionKind {
52        match self {
53            Self::Passthrough => RefreshMaterialProtectionKind::Passthrough,
54            Self::Sealed { .. } => RefreshMaterialProtectionKind::Sealed,
55        }
56    }
57
58    /// Extract the master key reference when in `Sealed` mode.
59    pub fn master_key(&self) -> Option<&str> {
60        match self {
61            Self::Sealed { master_key } => Some(master_key.expose_secret()),
62            Self::Passthrough => None,
63        }
64    }
65}
66
67// ---- Metadata delivery ----
68
69/// Simple discriminant for metadata delivery.
70#[cfg_attr(feature = "config-schema", derive(schemars::JsonSchema))]
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
72#[serde(rename_all = "snake_case")]
73pub enum MetadataDeliveryKind {
74    /// No server-side metadata delivery.
75    #[default]
76    None,
77    /// Metadata redeemed via one-time id.
78    Redemption,
79}
80
81/// How auth-state metadata (principal, source) is delivered to the client.
82///
83/// `Redemption` carries the store configuration, eliminating the need for a
84/// separate config field.
85#[cfg_attr(feature = "config-schema", derive(schemars::JsonSchema))]
86#[cfg_attr(
87    feature = "config-schema",
88    schemars(bound = "MC: schemars::JsonSchema")
89)]
90#[derive(Debug, Clone, Deserialize, Default)]
91#[serde(tag = "kind", rename_all = "snake_case")]
92pub enum MetadataDelivery<MC> {
93    /// No server-side metadata delivery — the client extracts what it needs
94    /// from the token set itself.
95    #[default]
96    None,
97    /// Metadata is stored server-side and redeemed by the client via a
98    /// one-time redemption id included in the callback/refresh fragment.
99    Redemption {
100        /// Store configuration (e.g. moka cache capacity / TTL).
101        #[serde(flatten)]
102        config: MC,
103    },
104}
105
106impl<MC> MetadataDelivery<MC> {
107    pub fn kind(&self) -> MetadataDeliveryKind {
108        match self {
109            Self::None => MetadataDeliveryKind::None,
110            Self::Redemption { .. } => MetadataDeliveryKind::Redemption,
111        }
112    }
113}
114
115// ---- Post-auth redirect policy ----
116
117/// Simple discriminant for post-auth redirect policy.
118#[cfg_attr(feature = "config-schema", derive(schemars::JsonSchema))]
119#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
120#[serde(rename_all = "snake_case")]
121pub enum PostAuthRedirectPolicyKind {
122    /// Caller supplies and validates the redirect URI.
123    #[default]
124    CallerValidated,
125    /// Runtime resolves and validates against an allowlist.
126    Resolved,
127}
128
129/// Who validates the `post_auth_redirect_uri` after callback / refresh.
130///
131/// `Resolved` carries the redirect policy configuration, ensuring the
132/// allowlist config is always present when the resolved policy is selected.
133#[cfg_attr(feature = "config-schema", derive(schemars::JsonSchema))]
134#[derive(Debug, Clone, Deserialize, Default)]
135#[serde(tag = "kind", rename_all = "snake_case")]
136pub enum PostAuthRedirectPolicy {
137    /// The caller (route / app glue) is responsible for supplying and
138    /// validating the redirect URI. The service applies no policy.
139    #[default]
140    CallerValidated,
141    /// The service resolves and validates the redirect URI against an
142    /// allowlist / policy configuration owned by the runtime.
143    Resolved {
144        /// Redirect target configuration (default URL, allowed targets).
145        #[serde(flatten)]
146        config: BackendOidcModeRedirectUriConfig,
147    },
148}
149
150impl PostAuthRedirectPolicy {
151    pub fn kind(&self) -> PostAuthRedirectPolicyKind {
152        match self {
153            Self::CallerValidated => PostAuthRedirectPolicyKind::CallerValidated,
154            Self::Resolved { .. } => PostAuthRedirectPolicyKind::Resolved,
155        }
156    }
157}
158
159// ---- Composite capabilities (Kind-level) ----
160
161/// The full capability bundle for a `backend-oidc` deployment (Kind level).
162///
163/// Each field uses the simple discriminant enum. Use this for preset
164/// definitions, display, and telemetry — not for config deserialization.
165///
166/// User info is a baseline capability of every `backend-oidc` deployment and
167/// is not modelled as a separate axis.
168#[cfg_attr(feature = "config-schema", derive(schemars::JsonSchema))]
169#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
170pub struct BackendOidcModeCapabilities {
171    #[serde(default)]
172    pub refresh_material_protection: RefreshMaterialProtectionKind,
173    #[serde(default)]
174    pub metadata_delivery: MetadataDeliveryKind,
175    #[serde(default)]
176    pub post_auth_redirect_policy: PostAuthRedirectPolicyKind,
177}
178
179impl Default for BackendOidcModeCapabilities {
180    fn default() -> Self {
181        Self::pure()
182    }
183}
184
185impl BackendOidcModeCapabilities {
186    /// Pure preset: minimal backend OIDC baseline.
187    pub fn pure() -> Self {
188        Self {
189            refresh_material_protection: RefreshMaterialProtectionKind::Passthrough,
190            metadata_delivery: MetadataDeliveryKind::None,
191            post_auth_redirect_policy: PostAuthRedirectPolicyKind::CallerValidated,
192        }
193    }
194
195    /// Mediated preset: backend OIDC with custody / policy augmentation.
196    pub fn mediated() -> Self {
197        Self {
198            refresh_material_protection: RefreshMaterialProtectionKind::Sealed,
199            metadata_delivery: MetadataDeliveryKind::Redemption,
200            post_auth_redirect_policy: PostAuthRedirectPolicyKind::Resolved,
201        }
202    }
203}
204
205/// Named presets for common `backend-oidc` capability bundles.
206#[cfg_attr(feature = "config-schema", derive(schemars::JsonSchema))]
207#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
208#[serde(rename_all = "snake_case")]
209pub enum BackendOidcModePreset {
210    /// Minimal backend OIDC baseline.
211    Pure,
212    /// Backend OIDC with custody / policy augmentation.
213    Mediated,
214}
215
216impl BackendOidcModePreset {
217    /// Expand a preset into its default capability bundle.
218    pub fn capabilities(self) -> BackendOidcModeCapabilities {
219        match self {
220            Self::Pure => BackendOidcModeCapabilities::pure(),
221            Self::Mediated => BackendOidcModeCapabilities::mediated(),
222        }
223    }
224}