// Copyright 2022 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use chrono::{DateTime, Utc}; use oauth2_types::scope::ScopeToken; use rand::{ distributions::{Alphanumeric, DistString}, Rng, }; use serde::Serialize; use thiserror::Error; use url::Url; use crate::{StorageBackend, StorageBackendMarker, User}; static DEVICE_ID_LENGTH: usize = 10; #[derive(Debug, Clone, PartialEq, Eq, Serialize)] #[serde(transparent)] pub struct Device { id: String, } #[derive(Debug, Error)] pub enum InvalidDeviceID { #[error("Device ID does not have the right size")] InvalidLength, #[error("Device ID contains invalid characters")] InvalidCharacters, } impl Device { /// Get the corresponding [`ScopeToken`] for that device #[must_use] pub fn to_scope_token(&self) -> ScopeToken { // SAFETY: the inner id should only have valid scope characters format!("urn:matrix:org.matrix.msc2967.client:device:{}", self.id) .parse() .unwrap() } /// Generate a random device ID pub fn generate(rng: &mut R) -> Self { let id: String = Alphanumeric.sample_string(rng, DEVICE_ID_LENGTH); Self { id } } /// Get the inner device ID as [`&str`] #[must_use] pub fn as_str(&self) -> &str { &self.id } } impl TryFrom for Device { type Error = InvalidDeviceID; /// Create a [`Device`] out of an ID, validating the ID has the right shape fn try_from(id: String) -> Result { if id.len() != DEVICE_ID_LENGTH { return Err(InvalidDeviceID::InvalidLength); } if !id.chars().all(|c| c.is_ascii_alphanumeric()) { return Err(InvalidDeviceID::InvalidCharacters); } Ok(Self { id }) } } #[derive(Debug, Clone, PartialEq, Serialize)] #[serde(bound = "T: StorageBackend")] pub struct CompatSession { #[serde(skip_serializing)] pub data: T::CompatSessionData, pub user: User, pub device: Device, pub created_at: DateTime, pub finished_at: Option>, } impl From> for CompatSession<()> { fn from(t: CompatSession) -> Self { Self { data: (), user: t.user, device: t.device, created_at: t.created_at, finished_at: t.finished_at, } } } #[derive(Debug, Clone, PartialEq)] pub struct CompatAccessToken { pub data: T::CompatAccessTokenData, pub token: String, pub created_at: DateTime, pub expires_at: Option>, } impl From> for CompatAccessToken<()> { fn from(t: CompatAccessToken) -> Self { Self { data: (), token: t.token, created_at: t.created_at, expires_at: t.expires_at, } } } #[derive(Debug, Clone, PartialEq)] pub struct CompatRefreshToken { pub data: T::CompatRefreshTokenData, pub token: String, pub created_at: DateTime, } impl From> for CompatRefreshToken<()> { fn from(t: CompatRefreshToken) -> Self { Self { data: (), token: t.token, created_at: t.created_at, } } } #[derive(Debug, Clone, PartialEq, Serialize)] #[serde(bound = "T: StorageBackend")] pub enum CompatSsoLoginState { Pending, Fulfilled { fulfilled_at: DateTime, session: CompatSession, }, Exchanged { fulfilled_at: DateTime, exchanged_at: DateTime, session: CompatSession, }, } impl From> for CompatSsoLoginState<()> { fn from(t: CompatSsoLoginState) -> Self { match t { CompatSsoLoginState::Pending => Self::Pending, CompatSsoLoginState::Fulfilled { fulfilled_at, session, } => Self::Fulfilled { fulfilled_at, session: session.into(), }, CompatSsoLoginState::Exchanged { fulfilled_at, exchanged_at, session, } => Self::Exchanged { fulfilled_at, exchanged_at, session: session.into(), }, } } } #[derive(Debug, Clone, PartialEq, Serialize)] #[serde(bound = "T: StorageBackend")] pub struct CompatSsoLogin { #[serde(skip_serializing)] pub data: T::CompatSsoLoginData, pub redirect_uri: Url, pub login_token: String, pub created_at: DateTime, pub state: CompatSsoLoginState, } impl From> for CompatSsoLogin<()> { fn from(t: CompatSsoLogin) -> Self { Self { data: (), redirect_uri: t.redirect_uri, login_token: t.login_token, created_at: t.created_at, state: t.state.into(), } } }