securitydept_token_set_context/
models.rs1use std::collections::HashMap;
2
3use chrono::{DateTime, Utc};
4use http::header::{AUTHORIZATION, HeaderMap, HeaderValue};
5use securitydept_utils::principal::AuthenticatedPrincipal as SharedAuthenticatedPrincipal;
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use typed_builder::TypedBuilder;
9
10use crate::backend_oidc_mode::SealedRefreshMaterial;
11
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
13#[serde(rename_all = "snake_case")]
14pub enum AuthenticationSourceKind {
15 OidcAuthorizationCode,
16 RefreshToken,
17 ForwardedBearer,
18 StaticToken,
19 #[default]
20 Unknown,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TypedBuilder, Default)]
24pub struct AuthenticationSource {
25 #[builder(default = AuthenticationSourceKind::Unknown)]
26 #[serde(default)]
27 pub kind: AuthenticationSourceKind,
28 #[serde(skip_serializing_if = "Option::is_none")]
29 #[builder(default, setter(strip_option, into))]
30 pub provider_id: Option<String>,
31 #[serde(skip_serializing_if = "Option::is_none")]
32 #[builder(default, setter(strip_option, into))]
33 pub issuer: Option<String>,
34 #[serde(default, skip_serializing_if = "Vec::is_empty")]
35 #[builder(default)]
36 pub kind_history: Vec<AuthenticationSourceKind>,
37 #[serde(flatten)]
38 #[builder(default)]
39 pub attributes: HashMap<String, Value>,
40}
41
42pub type AuthenticatedPrincipal = SharedAuthenticatedPrincipal;
43
44#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TypedBuilder)]
45pub struct AuthTokenSnapshot {
46 #[builder(setter(into))]
47 pub access_token: String,
48 #[builder(default, setter(into))]
49 pub id_token: String,
50 #[serde(skip_serializing_if = "Option::is_none")]
51 #[builder(default, setter(strip_option))]
52 pub refresh_material: Option<SealedRefreshMaterial>,
53 #[serde(skip_serializing_if = "Option::is_none")]
54 #[builder(default, setter(strip_option))]
55 pub access_token_expires_at: Option<DateTime<Utc>>,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TypedBuilder)]
59pub struct AuthTokenDelta {
60 #[builder(setter(into))]
61 pub access_token: String,
62 #[serde(skip_serializing_if = "Option::is_none")]
63 #[builder(default, setter(strip_option, into))]
64 pub id_token: Option<String>,
65 #[serde(skip_serializing_if = "Option::is_none")]
66 #[builder(default, setter(strip_option))]
67 pub refresh_material: Option<SealedRefreshMaterial>,
68 #[serde(skip_serializing_if = "Option::is_none")]
69 #[builder(default, setter(strip_option))]
70 pub access_token_expires_at: Option<DateTime<Utc>>,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TypedBuilder, Default)]
74pub struct AuthStateMetadataSnapshot {
75 #[serde(skip_serializing_if = "Option::is_none")]
76 #[builder(default, setter(strip_option))]
77 pub principal: Option<AuthenticatedPrincipal>,
78 #[builder(default)]
79 #[serde(default)]
80 pub source: AuthenticationSource,
81 #[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
82 #[builder(default)]
83 pub attributes: HashMap<String, Value>,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TypedBuilder, Default)]
87pub struct CurrentAuthenticationSourcePartial {
88 #[serde(skip_serializing_if = "Option::is_none")]
89 #[builder(default, setter(strip_option))]
90 pub kind: Option<AuthenticationSourceKind>,
91 #[serde(skip_serializing_if = "Option::is_none")]
92 #[builder(default, setter(strip_option))]
93 pub provider_id: Option<String>,
94 #[serde(skip_serializing_if = "Option::is_none")]
95 #[builder(default, setter(strip_option))]
96 pub issuer: Option<String>,
97 #[serde(default, skip_serializing_if = "Option::is_none")]
98 #[builder(default, setter(strip_option))]
99 pub kind_history: Option<Vec<AuthenticationSourceKind>>,
100 #[serde(default, flatten, skip_serializing_if = "HashMap::is_empty")]
101 #[builder(default)]
102 pub attributes: HashMap<String, Value>,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TypedBuilder, Default)]
106pub struct CurrentAuthStateMetadataSnapshotPartial {
107 #[serde(skip_serializing_if = "Option::is_none")]
108 #[builder(default, setter(strip_option))]
109 pub principal: Option<AuthenticatedPrincipal>,
110 #[serde(skip_serializing_if = "Option::is_none")]
111 #[builder(default, setter(strip_option))]
112 pub source: Option<CurrentAuthenticationSourcePartial>,
113 #[serde(default, flatten, skip_serializing_if = "HashMap::is_empty")]
114 #[builder(default)]
115 pub attributes: HashMap<String, Value>,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TypedBuilder, Default)]
119pub struct AuthStateMetadataDelta {
120 #[serde(skip_serializing_if = "Option::is_none")]
121 #[builder(default, setter(strip_option))]
122 pub principal: Option<AuthenticatedPrincipal>,
123 #[serde(skip_serializing_if = "Option::is_none")]
124 #[builder(default, setter(strip_option))]
125 pub source: Option<AuthenticationSource>,
126 #[serde(default, flatten, skip_serializing_if = "HashMap::is_empty")]
127 #[builder(default)]
128 pub attributes: HashMap<String, Value>,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TypedBuilder)]
132pub struct AuthStateSnapshot {
133 pub tokens: AuthTokenSnapshot,
134 pub metadata: AuthStateMetadataSnapshot,
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TypedBuilder)]
138pub struct AuthStateDelta {
139 pub tokens: AuthTokenDelta,
140 #[builder(default)]
141 #[serde(
142 default,
143 flatten,
144 skip_serializing_if = "AuthStateMetadataDelta::is_empty"
145 )]
146 pub metadata: AuthStateMetadataDelta,
147}
148
149impl AuthTokenSnapshot {
150 pub fn access_token_is_expired_at(&self, now: DateTime<Utc>) -> bool {
151 self.access_token_expires_at
152 .is_some_and(|expires_at| expires_at <= now)
153 }
154
155 pub fn should_refresh_at(&self, now: DateTime<Utc>) -> bool {
156 self.access_token_is_expired_at(now)
157 || self
158 .access_token_expires_at
159 .is_some_and(|expires_at| expires_at <= now + chrono::TimeDelta::minutes(1))
160 }
161
162 pub fn authorization_value(&self) -> String {
163 format!("Bearer {}", self.access_token)
164 }
165
166 pub fn authorization_header_value(
167 &self,
168 ) -> Result<HeaderValue, http::header::InvalidHeaderValue> {
169 HeaderValue::from_str(&self.authorization_value())
170 }
171
172 pub fn apply_authorization_header(
173 &self,
174 headers: &mut HeaderMap,
175 ) -> Result<(), http::header::InvalidHeaderValue> {
176 headers.insert(AUTHORIZATION, self.authorization_header_value()?);
177 Ok(())
178 }
179}
180
181impl AuthStateMetadataDelta {
182 pub fn is_empty(&self) -> bool {
183 self.principal.is_none() && self.source.is_none() && self.attributes.is_empty()
184 }
185}