mas_data_model/personal/
session.rs1use std::net::IpAddr;
7
8use chrono::{DateTime, Utc};
9use oauth2_types::scope::Scope;
10use serde::Serialize;
11use ulid::Ulid;
12
13use crate::{Client, Device, InvalidTransitionError, User};
14
15#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
16pub enum SessionState {
17    #[default]
18    Valid,
19    Revoked {
20        revoked_at: DateTime<Utc>,
21    },
22}
23
24impl SessionState {
25    #[must_use]
29    pub fn is_valid(&self) -> bool {
30        matches!(self, Self::Valid)
31    }
32
33    #[must_use]
37    pub fn is_revoked(&self) -> bool {
38        matches!(self, Self::Revoked { .. })
39    }
40
41    pub fn revoke(self, revoked_at: DateTime<Utc>) -> Result<Self, InvalidTransitionError> {
53        match self {
54            Self::Valid => Ok(Self::Revoked { revoked_at }),
55            Self::Revoked { .. } => Err(InvalidTransitionError),
56        }
57    }
58
59    #[must_use]
65    pub fn revoked_at(&self) -> Option<DateTime<Utc>> {
66        match self {
67            Self::Valid => None,
68            Self::Revoked { revoked_at } => Some(*revoked_at),
69        }
70    }
71}
72
73#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
74pub struct PersonalSession {
75    pub id: Ulid,
76    pub state: SessionState,
77    pub owner: PersonalSessionOwner,
78    pub actor_user_id: Ulid,
79    pub human_name: String,
80    pub scope: Scope,
84    pub created_at: DateTime<Utc>,
85    pub last_active_at: Option<DateTime<Utc>>,
86    pub last_active_ip: Option<IpAddr>,
87}
88
89#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize)]
90pub enum PersonalSessionOwner {
91    User(Ulid),
93    OAuth2Client(Ulid),
96}
97
98impl<'a> From<&'a User> for PersonalSessionOwner {
99    fn from(value: &'a User) -> Self {
100        PersonalSessionOwner::User(value.id)
101    }
102}
103
104impl<'a> From<&'a Client> for PersonalSessionOwner {
105    fn from(value: &'a Client) -> Self {
106        PersonalSessionOwner::OAuth2Client(value.id)
107    }
108}
109
110impl std::ops::Deref for PersonalSession {
111    type Target = SessionState;
112
113    fn deref(&self) -> &Self::Target {
114        &self.state
115    }
116}
117
118impl PersonalSession {
119    pub fn finish(mut self, revoked_at: DateTime<Utc>) -> Result<Self, InvalidTransitionError> {
129        self.state = self.state.revoke(revoked_at)?;
130        Ok(self)
131    }
132
133    #[must_use]
136    pub fn has_device(&self) -> bool {
137        self.scope
138            .iter()
139            .any(|scope_token| Device::from_scope_token(scope_token).is_some())
140    }
141}