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}