1use std::fmt;
4
5use chrono::{DateTime, Utc};
6use uuid::Uuid;
7
8use secrecy::{ExposeSecret, SecretString};
9use zeroize::Zeroizing;
10
11use crate::{event::SessionOrigin, risk::Assessment, store::SessionRecord, subject::Subject};
12
13#[derive(Clone, Debug, PartialEq, Eq)]
15#[non_exhaustive]
16pub struct AuthSession<S> {
17 pub session_id: Uuid,
19 pub subject: S,
21 pub created_at: DateTime<Utc>,
23 pub authenticated_at: DateTime<Utc>,
25 pub expires_at: DateTime<Utc>,
27}
28
29impl<S> AuthSession<S> {
30 pub(crate) fn new(session: &SessionRecord, subject: S) -> Self {
31 Self {
32 session_id: session.id,
33 subject,
34 created_at: session.created_at,
35 authenticated_at: session.authenticated_at,
36 expires_at: session.expires_at,
37 }
38 }
39}
40
41#[derive(Clone)]
45pub struct IssuedToken {
46 pub(crate) value: SecretString,
48 pub(crate) expires_at: DateTime<Utc>,
49 pub(crate) set_cookie: Zeroizing<String>,
51}
52
53impl IssuedToken {
54 pub fn expose(&self) -> &str {
57 self.value.expose_secret()
58 }
59
60 pub fn expires_at(&self) -> DateTime<Utc> {
62 self.expires_at
63 }
64
65 pub fn set_cookie(&self) -> &str {
67 &self.set_cookie
68 }
69}
70
71impl fmt::Debug for IssuedToken {
72 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 f.debug_struct("IssuedToken")
74 .field("token", &"<redacted>")
75 .field("expires_at", &self.expires_at)
76 .finish_non_exhaustive()
77 }
78}
79
80#[derive(Clone, Debug)]
82#[non_exhaustive]
83pub struct NewSession {
84 pub session_id: Uuid,
86 pub token: IssuedToken,
88}
89
90#[derive(Clone, Debug)]
92pub enum Outcome<S> {
93 Authenticated(Authenticated<S>),
95 Rejected(Rejection),
97}
98
99impl<S> Outcome<S> {
100 pub fn session(&self) -> Option<&AuthSession<S>> {
102 match self {
103 Outcome::Authenticated(a) => Some(&a.session),
104 Outcome::Rejected(_) => None,
105 }
106 }
107
108 pub fn rejection(&self) -> Option<Rejection> {
110 match self {
111 Outcome::Authenticated(_) => None,
112 Outcome::Rejected(r) => Some(*r),
113 }
114 }
115}
116
117#[derive(Clone, Debug)]
119#[non_exhaustive]
120pub struct Authenticated<S> {
121 pub session: AuthSession<S>,
123 pub rotated: Option<IssuedToken>,
125 pub assessment: Assessment,
128}
129
130#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
132#[non_exhaustive]
133pub enum Rejection {
134 NotAuthenticated,
136 Expired,
138 Revoked,
140 StepUpRequired,
144}
145
146impl Rejection {
147 pub const fn as_str(self) -> &'static str {
149 match self {
150 Rejection::NotAuthenticated => "not_authenticated",
151 Rejection::Expired => "session_expired",
152 Rejection::Revoked => "session_revoked",
153 Rejection::StepUpRequired => "step_up_required",
154 }
155 }
156
157 pub const fn clears_cookie(self) -> bool {
159 !matches!(self, Rejection::StepUpRequired)
160 }
161
162 pub const fn clears_site_data(self) -> bool {
164 matches!(self, Rejection::Revoked)
165 }
166}
167
168impl fmt::Display for Rejection {
169 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170 f.write_str(self.as_str())
171 }
172}
173
174#[derive(Clone, Debug)]
176pub enum StepUpOutcome {
177 Completed(IssuedToken),
179 SubjectMismatch,
181 Rejected(Rejection),
183}
184
185#[derive(Clone, Debug, PartialEq, Eq)]
188#[non_exhaustive]
189pub struct SessionInfo<S> {
190 pub session_id: Uuid,
192 pub subject: S,
194 pub created_at: DateTime<Utc>,
196 pub authenticated_at: DateTime<Utc>,
198 pub last_seen_at: DateTime<Utc>,
200 pub expires_at: DateTime<Utc>,
202 pub step_up_required: bool,
204 pub browser: Option<String>,
206 pub os: Option<String>,
208 pub country: Option<String>,
210 pub origin: Option<SessionOrigin>,
212}
213
214impl<S: Subject> SessionInfo<S> {
215 pub(crate) fn from_record(record: &SessionRecord) -> Option<Self> {
216 let fp = &record.baseline.fingerprint;
217 Some(Self {
218 session_id: record.id,
219 subject: crate::subject::decode(&record.subject)?,
220 created_at: record.created_at,
221 authenticated_at: record.authenticated_at,
222 last_seen_at: record.last_seen_at,
223 expires_at: record.expires_at,
224 step_up_required: record.step_up_required,
225 browser: fp.browser.clone(),
226 os: fp.os.clone(),
227 country: fp.country.clone(),
228 origin: record.origin.clone(),
229 })
230 }
231}
232
233#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
235#[non_exhaustive]
236pub struct PurgeReport {
237 pub sessions: u64,
239 pub events: u64,
241}