mas_data_model/upstream_oauth2/
session.rs1use chrono::{DateTime, Utc};
8use serde::Serialize;
9use ulid::Ulid;
10
11use super::UpstreamOAuthLink;
12use crate::InvalidTransitionError;
13
14#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
15pub enum UpstreamOAuthAuthorizationSessionState {
16 #[default]
17 Pending,
18 Completed {
19 completed_at: DateTime<Utc>,
20 link_id: Ulid,
21 id_token: Option<String>,
22 extra_callback_parameters: Option<serde_json::Value>,
23 userinfo: Option<serde_json::Value>,
24 },
25 Consumed {
26 completed_at: DateTime<Utc>,
27 consumed_at: DateTime<Utc>,
28 link_id: Ulid,
29 id_token: Option<String>,
30 extra_callback_parameters: Option<serde_json::Value>,
31 userinfo: Option<serde_json::Value>,
32 },
33 Unlinked {
34 completed_at: DateTime<Utc>,
35 consumed_at: Option<DateTime<Utc>>,
36 unlinked_at: DateTime<Utc>,
37 id_token: Option<String>,
38 },
39}
40
41impl UpstreamOAuthAuthorizationSessionState {
42 pub fn complete(
51 self,
52 completed_at: DateTime<Utc>,
53 link: &UpstreamOAuthLink,
54 id_token: Option<String>,
55 extra_callback_parameters: Option<serde_json::Value>,
56 userinfo: Option<serde_json::Value>,
57 ) -> Result<Self, InvalidTransitionError> {
58 match self {
59 Self::Pending => Ok(Self::Completed {
60 completed_at,
61 link_id: link.id,
62 id_token,
63 extra_callback_parameters,
64 userinfo,
65 }),
66 Self::Completed { .. } | Self::Consumed { .. } | Self::Unlinked { .. } => {
67 Err(InvalidTransitionError)
68 }
69 }
70 }
71
72 pub fn consume(self, consumed_at: DateTime<Utc>) -> Result<Self, InvalidTransitionError> {
81 match self {
82 Self::Completed {
83 completed_at,
84 link_id,
85 id_token,
86 extra_callback_parameters,
87 userinfo,
88 } => Ok(Self::Consumed {
89 completed_at,
90 link_id,
91 consumed_at,
92 id_token,
93 extra_callback_parameters,
94 userinfo,
95 }),
96 Self::Pending | Self::Consumed { .. } | Self::Unlinked { .. } => {
97 Err(InvalidTransitionError)
98 }
99 }
100 }
101
102 #[must_use]
109 pub fn link_id(&self) -> Option<Ulid> {
110 match self {
111 Self::Pending | Self::Unlinked { .. } => None,
112 Self::Completed { link_id, .. } | Self::Consumed { link_id, .. } => Some(*link_id),
113 }
114 }
115
116 #[must_use]
124 pub fn completed_at(&self) -> Option<DateTime<Utc>> {
125 match self {
126 Self::Pending => None,
127 Self::Completed { completed_at, .. }
128 | Self::Consumed { completed_at, .. }
129 | Self::Unlinked { completed_at, .. } => Some(*completed_at),
130 }
131 }
132
133 #[must_use]
140 pub fn id_token(&self) -> Option<&str> {
141 match self {
142 Self::Pending => None,
143 Self::Completed { id_token, .. }
144 | Self::Consumed { id_token, .. }
145 | Self::Unlinked { id_token, .. } => id_token.as_deref(),
146 }
147 }
148
149 #[must_use]
156 pub fn extra_callback_parameters(&self) -> Option<&serde_json::Value> {
157 match self {
158 Self::Pending | Self::Unlinked { .. } => None,
159 Self::Completed {
160 extra_callback_parameters,
161 ..
162 }
163 | Self::Consumed {
164 extra_callback_parameters,
165 ..
166 } => extra_callback_parameters.as_ref(),
167 }
168 }
169
170 #[must_use]
171 pub fn userinfo(&self) -> Option<&serde_json::Value> {
172 match self {
173 Self::Pending | Self::Unlinked { .. } => None,
174 Self::Completed { userinfo, .. } | Self::Consumed { userinfo, .. } => userinfo.as_ref(),
175 }
176 }
177
178 #[must_use]
186 pub fn consumed_at(&self) -> Option<DateTime<Utc>> {
187 match self {
188 Self::Pending | Self::Completed { .. } => None,
189 Self::Consumed { consumed_at, .. } => Some(*consumed_at),
190 Self::Unlinked { consumed_at, .. } => *consumed_at,
191 }
192 }
193
194 #[must_use]
202 pub fn unlinked_at(&self) -> Option<DateTime<Utc>> {
203 match self {
204 Self::Pending | Self::Completed { .. } | Self::Consumed { .. } => None,
205 Self::Unlinked { unlinked_at, .. } => Some(*unlinked_at),
206 }
207 }
208
209 #[must_use]
214 pub fn is_pending(&self) -> bool {
215 matches!(self, Self::Pending)
216 }
217
218 #[must_use]
223 pub fn is_completed(&self) -> bool {
224 matches!(self, Self::Completed { .. })
225 }
226
227 #[must_use]
232 pub fn is_consumed(&self) -> bool {
233 matches!(self, Self::Consumed { .. })
234 }
235
236 #[must_use]
241 pub fn is_unlinked(&self) -> bool {
242 matches!(self, Self::Unlinked { .. })
243 }
244}
245
246#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
247pub struct UpstreamOAuthAuthorizationSession {
248 pub id: Ulid,
249 pub state: UpstreamOAuthAuthorizationSessionState,
250 pub provider_id: Ulid,
251 pub state_str: String,
252 pub code_challenge_verifier: Option<String>,
253 pub nonce: String,
254 pub created_at: DateTime<Utc>,
255}
256
257impl std::ops::Deref for UpstreamOAuthAuthorizationSession {
258 type Target = UpstreamOAuthAuthorizationSessionState;
259
260 fn deref(&self) -> &Self::Target {
261 &self.state
262 }
263}
264
265impl UpstreamOAuthAuthorizationSession {
266 pub fn complete(
276 mut self,
277 completed_at: DateTime<Utc>,
278 link: &UpstreamOAuthLink,
279 id_token: Option<String>,
280 extra_callback_parameters: Option<serde_json::Value>,
281 userinfo: Option<serde_json::Value>,
282 ) -> Result<Self, InvalidTransitionError> {
283 self.state = self.state.complete(
284 completed_at,
285 link,
286 id_token,
287 extra_callback_parameters,
288 userinfo,
289 )?;
290 Ok(self)
291 }
292
293 pub fn consume(mut self, consumed_at: DateTime<Utc>) -> Result<Self, InvalidTransitionError> {
303 self.state = self.state.consume(consumed_at)?;
304 Ok(self)
305 }
306}