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