Skip to main content

securitydept_oidc_client/
models.rs

1use std::collections::HashMap;
2
3use chrono::{DateTime, Utc};
4use openidconnect::{
5    AdditionalClaims, CsrfToken, EmptyExtraTokenFields, IdTokenClaims, IdTokenFields, Nonce,
6    UserInfoClaims,
7    core::{CoreGenderClaim, CoreJweContentEncryptionAlgorithm, CoreJwsSigningAlgorithm},
8};
9use serde::{Deserialize, Serialize};
10use url::{Url, form_urlencoded};
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct ClaimsCheckResult {
14    pub display_name: String,
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub picture: Option<String>,
17    pub claims: HashMap<String, serde_json::Value>,
18}
19
20/// Additional claims we accept from the OIDC provider (open-ended).
21#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
22pub struct ExtraOidcClaims {
23    #[serde(flatten)]
24    pub extra: HashMap<String, serde_json::Value>,
25}
26
27impl AdditionalClaims for ExtraOidcClaims {}
28
29pub type UserInfoClaimsWithExtra = UserInfoClaims<ExtraOidcClaims, CoreGenderClaim>;
30pub type IdTokenClaimsWithExtra = IdTokenClaims<ExtraOidcClaims, CoreGenderClaim>;
31
32pub type IdTokenFieldsWithExtra = IdTokenFields<
33    ExtraOidcClaims,
34    EmptyExtraTokenFields,
35    CoreGenderClaim,
36    CoreJweContentEncryptionAlgorithm,
37    CoreJwsSigningAlgorithm,
38>;
39
40#[derive(Debug)]
41pub struct OidcCodeFlowAuthorizationRequest {
42    pub authorization_url: Url,
43    pub csrf_token: CsrfToken,
44    pub nonce: Nonce,
45    pub pkce_verifier_secret: Option<String>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct OidcDeviceAuthorizationResult {
50    pub device_code: String,
51    pub user_code: String,
52    pub verification_uri: String,
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub verification_uri_complete: Option<String>,
55    #[serde(with = "humantime_serde")]
56    pub expires_in: std::time::Duration,
57    #[serde(
58        default,
59        skip_serializing_if = "Option::is_none",
60        with = "humantime_serde::option"
61    )]
62    pub interval: Option<std::time::Duration>,
63}
64
65impl OidcDeviceAuthorizationResult {
66    pub fn poll_interval(&self, fallback: std::time::Duration) -> std::time::Duration {
67        self.interval.unwrap_or(fallback)
68    }
69}
70
71#[derive(Debug, Clone, Serialize)]
72#[serde(tag = "status", rename_all = "snake_case")]
73pub enum OidcDeviceTokenPollResult {
74    Pending {
75        #[serde(with = "humantime_serde")]
76        interval: std::time::Duration,
77    },
78    SlowDown {
79        #[serde(with = "humantime_serde")]
80        interval: std::time::Duration,
81    },
82    Denied {
83        #[serde(skip_serializing_if = "Option::is_none")]
84        error_description: Option<String>,
85    },
86    Expired {
87        #[serde(skip_serializing_if = "Option::is_none")]
88        error_description: Option<String>,
89    },
90    Complete {
91        token_result: Box<OidcDeviceTokenResult>,
92    },
93}
94
95#[derive(Debug, Clone, Serialize)]
96pub struct OidcDeviceTokenResult {
97    pub access_token: String,
98    pub access_token_expiration: Option<DateTime<Utc>>,
99    pub id_token: String,
100    pub refresh_token: Option<String>,
101    pub id_token_claims: IdTokenClaimsWithExtra,
102    pub user_info_claims: Option<UserInfoClaimsWithExtra>,
103    pub claims_check_result: ClaimsCheckResult,
104}
105
106impl TokenSetTrait for OidcDeviceTokenResult {
107    fn access_token(&self) -> &str {
108        &self.access_token
109    }
110    fn id_token(&self) -> Option<&str> {
111        Some(&self.id_token)
112    }
113    fn refresh_token(&self) -> Option<&str> {
114        self.refresh_token.as_deref()
115    }
116    fn access_token_expiration(&self) -> Option<&DateTime<Utc>> {
117        self.access_token_expiration.as_ref()
118    }
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
122#[serde(tag = "kind", content = "token", rename_all = "snake_case")]
123pub enum OidcRevocableToken {
124    AccessToken(String),
125    RefreshToken(String),
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct OidcTokenSet {
130    pub access_token: String,
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub id_token: Option<String>,
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub refresh_token: Option<String>,
135}
136
137pub trait TokenSetTrait {
138    fn access_token(&self) -> &str;
139    fn id_token(&self) -> Option<&str>;
140    fn refresh_token(&self) -> Option<&str>;
141    fn access_token_expiration(&self) -> Option<&DateTime<Utc>>;
142
143    fn to_fragment(&self) -> String {
144        let mut fragment = &mut form_urlencoded::Serializer::new(String::new());
145
146        fragment = fragment.append_pair("access_token", self.access_token());
147
148        if let Some(refresh_token) = self.refresh_token() {
149            fragment = fragment.append_pair("refresh_token", refresh_token)
150        };
151
152        if let Some(id_token) = self.id_token() {
153            fragment = fragment.append_pair("id_token", id_token)
154        }
155
156        if let Some(access_token_expiration) = self.access_token_expiration() {
157            fragment = fragment.append_pair("expires_at", &access_token_expiration.to_rfc3339())
158        }
159
160        fragment.finish()
161    }
162}
163
164#[derive(Deserialize)]
165pub struct OidcCodeCallbackSearchParams {
166    pub code: String,
167    /// OAuth state (CSRF token); required for callback validation.
168    pub state: Option<String>,
169}
170
171pub struct OidcCodeExchangeResult {
172    pub access_token: String,
173    pub access_token_expiration: Option<DateTime<Utc>>,
174    pub id_token: String,
175    pub refresh_token: Option<String>,
176    pub id_token_claims: IdTokenClaimsWithExtra,
177    pub user_info_claims: Option<UserInfoClaimsWithExtra>,
178}
179
180pub struct OidcCodeCallbackResult {
181    pub code: String,
182    pub pkce_verifier_secret: Option<String>,
183    pub state: Option<String>,
184    pub nonce: String,
185    pub pending_extra_data: Option<serde_json::Value>,
186    pub access_token: String,
187    pub access_token_expiration: Option<DateTime<Utc>>,
188    pub id_token: String,
189    pub refresh_token: Option<String>,
190    pub id_token_claims: IdTokenClaimsWithExtra,
191    pub user_info_claims: Option<UserInfoClaimsWithExtra>,
192    pub claims_check_result: ClaimsCheckResult,
193}
194
195impl TokenSetTrait for OidcCodeCallbackResult {
196    fn access_token(&self) -> &str {
197        &self.access_token
198    }
199    fn id_token(&self) -> Option<&str> {
200        Some(&self.id_token)
201    }
202    fn refresh_token(&self) -> Option<&str> {
203        self.refresh_token.as_deref()
204    }
205    fn access_token_expiration(&self) -> Option<&DateTime<Utc>> {
206        self.access_token_expiration.as_ref()
207    }
208}
209
210pub struct OidcRefreshTokenResult {
211    pub access_token: String,
212    pub access_token_expiration: Option<DateTime<Utc>>,
213    pub id_token: Option<String>,
214    pub refresh_token: Option<String>,
215    pub id_token_claims: Option<IdTokenClaimsWithExtra>,
216    pub user_info_claims: Option<UserInfoClaimsWithExtra>,
217    pub claims_check_result: Option<ClaimsCheckResult>,
218}
219
220impl TokenSetTrait for OidcRefreshTokenResult {
221    fn access_token(&self) -> &str {
222        &self.access_token
223    }
224    fn id_token(&self) -> Option<&str> {
225        self.id_token.as_deref()
226    }
227    fn refresh_token(&self) -> Option<&str> {
228        self.refresh_token.as_deref()
229    }
230    fn access_token_expiration(&self) -> Option<&DateTime<Utc>> {
231        self.access_token_expiration.as_ref()
232    }
233}
234
235/// Normalized result from the shared `user_info` exchange helper.
236///
237/// Produced by
238/// [`OidcClient::handle_user_info_exchange`](crate::OidcClient::handle_user_info_exchange).
239/// Backend modes convert this into their mode-qualified `UserInfoResponse`
240/// transport type.
241#[derive(Debug, Clone, Serialize, Deserialize)]
242pub struct UserInfoExchangeResult {
243    /// The subject identifier from the ID token.
244    pub subject: String,
245    /// Display name (derived from preferred_username, nickname, or subject).
246    pub display_name: String,
247    /// Profile picture URL, if available.
248    #[serde(skip_serializing_if = "Option::is_none")]
249    pub picture: Option<String>,
250    /// The token issuer.
251    #[serde(skip_serializing_if = "Option::is_none")]
252    pub issuer: Option<String>,
253    /// Merged and post-processed claims from id_token + userinfo.
254    #[serde(skip_serializing_if = "Option::is_none")]
255    pub claims: Option<HashMap<String, serde_json::Value>>,
256}