diff --git a/crates/data-model/src/oauth2/session.rs b/crates/data-model/src/oauth2/session.rs index 054495b6..0a30ee11 100644 --- a/crates/data-model/src/oauth2/session.rs +++ b/crates/data-model/src/oauth2/session.rs @@ -64,6 +64,19 @@ impl SessionState { Self::Finished { .. } => Err(InvalidTransitionError), } } + + /// Returns the time the session was finished, if any + /// + /// Returns `None` if the session is still [`Valid`]. + /// + /// [`Valid`]: SessionState::Valid + #[must_use] + pub fn finished_at(&self) -> Option> { + match self { + Self::Valid => None, + Self::Finished { finished_at } => Some(*finished_at), + } + } } #[derive(Debug, Clone, PartialEq, Eq, Serialize)] diff --git a/crates/handlers/src/admin/model.rs b/crates/handlers/src/admin/model.rs index df8e20e8..5f13db2f 100644 --- a/crates/handlers/src/admin/model.rs +++ b/crates/handlers/src/admin/model.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::net::IpAddr; + use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::Serialize; @@ -104,3 +106,110 @@ impl Resource for User { self.id } } + +/// A OAuth 2.0 session +#[derive(Serialize, JsonSchema)] +pub struct OAuth2Session { + #[serde(skip)] + id: Ulid, + + /// When the object was created + created_at: DateTime, + + /// When the session was finished + finished_at: Option>, + + /// The ID of the user who owns the session + #[schemars(with = "Option")] + user_id: Option, + + /// The ID of the browser session which started this session + #[schemars(with = "Option")] + user_session_id: Option, + + /// The ID of the client which requested this session + #[schemars(with = "super::schema::Ulid")] + client_id: Ulid, + + /// The scope granted for this session + scope: String, + + /// The user agent string of the client which started this session + user_agent: Option, + + /// The last time the session was active + last_active_at: Option>, + + /// The last IP address used by the session + last_active_ip: Option, +} + +impl From for OAuth2Session { + fn from(session: mas_data_model::Session) -> Self { + Self { + id: session.id, + created_at: session.created_at, + finished_at: session.finished_at(), + user_id: session.user_id, + user_session_id: session.user_session_id, + client_id: session.client_id, + scope: session.scope.to_string(), + user_agent: session.user_agent.map(|ua| ua.raw), + last_active_at: session.last_active_at, + last_active_ip: session.last_active_ip, + } + } +} + +impl OAuth2Session { + /// Samples of OAuth 2.0 sessions + pub fn samples() -> [Self; 3] { + [ + Self { + id: Ulid::from_bytes([0x01; 16]), + created_at: DateTime::default(), + finished_at: None, + user_id: Some(Ulid::from_bytes([0x02; 16])), + user_session_id: Some(Ulid::from_bytes([0x03; 16])), + client_id: Ulid::from_bytes([0x04; 16]), + scope: "openid".to_owned(), + user_agent: Some("Mozilla/5.0".to_owned()), + last_active_at: Some(DateTime::default()), + last_active_ip: Some("127.0.0.1".parse().unwrap()), + }, + Self { + id: Ulid::from_bytes([0x02; 16]), + created_at: DateTime::default(), + finished_at: None, + user_id: None, + user_session_id: None, + client_id: Ulid::from_bytes([0x05; 16]), + scope: "urn:mas:admin".to_owned(), + user_agent: None, + last_active_at: None, + last_active_ip: None, + }, + Self { + id: Ulid::from_bytes([0x03; 16]), + created_at: DateTime::default(), + finished_at: Some(DateTime::default()), + user_id: Some(Ulid::from_bytes([0x04; 16])), + user_session_id: Some(Ulid::from_bytes([0x05; 16])), + client_id: Ulid::from_bytes([0x06; 16]), + scope: "urn:matrix:org.matrix.msc2967.client:api:*".to_owned(), + user_agent: Some("Mozilla/5.0".to_owned()), + last_active_at: Some(DateTime::default()), + last_active_ip: Some("127.0.0.1".parse().unwrap()), + }, + ] + } +} + +impl Resource for OAuth2Session { + const KIND: &'static str = "oauth2-session"; + const PATH: &'static str = "/api/admin/v1/oauth2-sessions"; + + fn id(&self) -> Ulid { + self.id + } +}