Skip to main content

securitydept_token_set_context/
models.rs

1use 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}