use std::borrow::Cow;
use std::{fmt, time::Duration};
use salvo::prelude::*;
use serde::{
Deserialize, Deserializer, Serialize,
de::{self, DeserializeOwned},
};
use serde_json::Value as JsonValue;
use crate::client::uiaa::{AuthData, UserIdentifier};
use crate::serde::{JsonObject, StringEnum};
use crate::{OwnedDeviceId, OwnedMxcUri, OwnedUserId, PrivOwnedStr};
#[derive(ToSchema, Deserialize, Debug)]
pub struct RefreshTokenReqBody {
pub refresh_token: String,
}
#[derive(ToSchema, Serialize, Debug)]
pub struct RefreshTokenResBody {
pub access_token: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub refresh_token: Option<String>,
#[serde(
with = "crate::serde::duration::opt_ms",
default,
skip_serializing_if = "Option::is_none"
)]
pub expires_in_ms: Option<Duration>,
}
impl RefreshTokenResBody {
pub fn new(access_token: String) -> Self {
Self {
access_token,
refresh_token: None,
expires_in_ms: None,
}
}
}
#[derive(ToSchema, Deserialize, Debug)]
pub struct LoginReqBody {
#[serde(flatten)]
pub login_info: LoginInfo,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub device_id: Option<OwnedDeviceId>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub initial_device_display_name: Option<String>,
#[serde(default, skip_serializing_if = "crate::serde::is_default")]
pub refresh_token: bool,
}
#[derive(ToSchema, Serialize, Debug)]
pub struct LoginResBody {
pub user_id: OwnedUserId,
pub access_token: String,
pub device_id: OwnedDeviceId,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub well_known: Option<DiscoveryInfo>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub refresh_token: Option<String>,
#[serde(
with = "crate::serde::duration::opt_ms",
default,
skip_serializing_if = "Option::is_none",
rename = "expires_in_ms"
)]
pub expires_in: Option<Duration>,
}
impl LoginResBody {
pub fn new(user_id: OwnedUserId, access_token: String, device_id: OwnedDeviceId) -> Self {
Self {
user_id,
access_token,
device_id,
well_known: None,
refresh_token: None,
expires_in: None,
}
}
}
#[derive(ToSchema, Clone, Serialize)]
#[serde(untagged)]
pub enum LoginInfo {
Password(Password),
Token(Token),
Appservice(Appservice),
#[doc(hidden)]
_Custom(CustomLoginInfo),
}
impl LoginInfo {
pub fn new(login_type: &str, data: JsonObject) -> serde_json::Result<Self> {
Ok(match login_type {
"m.login.password" => Self::Password(serde_json::from_value(JsonValue::Object(data))?),
"m.login.token" => Self::Token(serde_json::from_value(JsonValue::Object(data))?),
"m.login.application_service" => Self::Appservice(serde_json::from_value(JsonValue::Object(data))?),
_ => Self::_Custom(CustomLoginInfo {
login_type: login_type.into(),
extra: data,
}),
})
}
}
impl fmt::Debug for LoginInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Password(inner) => inner.fmt(f),
Self::Token(inner) => inner.fmt(f),
Self::Appservice(inner) => inner.fmt(f),
Self::_Custom(inner) => inner.fmt(f),
}
}
}
impl<'de> Deserialize<'de> for LoginInfo {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
fn from_json_value<T: DeserializeOwned, E: de::Error>(val: JsonValue) -> Result<T, E> {
serde_json::from_value(val).map_err(E::custom)
}
let json = JsonValue::deserialize(deserializer)?;
let login_type = json["type"].as_str().ok_or_else(|| de::Error::missing_field("type"))?;
match login_type {
"m.login.password" => from_json_value(json).map(Self::Password),
"m.login.token" => from_json_value(json).map(Self::Token),
"m.login.application_service" => from_json_value(json).map(Self::Appservice),
_ => from_json_value(json).map(Self::_Custom),
}
}
}
#[derive(ToSchema, Clone, Deserialize, Serialize)]
#[serde(tag = "type", rename = "m.login.password")]
pub struct Password {
pub identifier: UserIdentifier,
pub password: String,
}
impl Password {
pub fn new(identifier: UserIdentifier, password: String) -> Self {
Self { identifier, password }
}
}
impl fmt::Debug for Password {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
identifier,
password: _,
} = self;
f.debug_struct("Password")
.field("identifier", identifier)
.finish_non_exhaustive()
}
}
#[derive(ToSchema, Clone, Deserialize, Serialize)]
#[serde(tag = "type", rename = "m.login.token")]
pub struct Token {
pub token: String,
}
impl Token {
pub fn new(token: String) -> Self {
Self { token }
}
}
impl fmt::Debug for Token {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { token: _ } = self;
f.debug_struct("Token").finish_non_exhaustive()
}
}
#[derive(ToSchema, Deserialize, Serialize, Clone, Debug)]
#[serde(tag = "type", rename = "m.login.application_service")]
pub struct Appservice {
pub identifier: UserIdentifier,
}
impl Appservice {
pub fn new(identifier: UserIdentifier) -> Self {
Self { identifier }
}
}
#[doc(hidden)]
#[derive(ToSchema, Clone, Deserialize, Serialize)]
#[non_exhaustive]
pub struct CustomLoginInfo {
#[serde(rename = "type")]
login_type: String,
#[serde(flatten)]
#[salvo(schema(value_type = Object, additional_properties = true))]
extra: JsonObject,
}
impl fmt::Debug for CustomLoginInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { login_type, extra: _ } = self;
f.debug_struct("CustomLoginInfo")
.field("login_type", login_type)
.finish_non_exhaustive()
}
}
#[derive(ToSchema, Deserialize, Serialize, Clone, Debug)]
pub struct DiscoveryInfo {
#[serde(rename = "m.homeserver")]
pub homeserver: HomeServerInfo,
#[serde(default, rename = "m.identity_server")]
pub identity_server: Option<IdentityServerInfo>,
}
impl DiscoveryInfo {
pub fn new(homeserver: HomeServerInfo) -> Self {
Self {
homeserver,
identity_server: None,
}
}
}
#[derive(ToSchema, Deserialize, Serialize, Clone, Debug)]
pub struct HomeServerInfo {
pub base_url: String,
}
impl HomeServerInfo {
pub fn new(base_url: String) -> Self {
Self { base_url }
}
}
#[derive(ToSchema, Deserialize, Serialize, Clone, Debug)]
pub struct IdentityServerInfo {
pub base_url: String,
}
impl IdentityServerInfo {
pub fn new(base_url: String) -> Self {
Self { base_url }
}
}
#[derive(ToSchema, Serialize, Debug)]
pub struct LoginTypesResBody {
pub flows: Vec<LoginType>,
}
impl LoginTypesResBody {
pub fn new(flows: Vec<LoginType>) -> Self {
Self { flows }
}
}
#[derive(ToSchema, Clone, Debug, Serialize)]
#[serde(untagged)]
pub enum LoginType {
Password(PasswordLoginType),
Token(TokenLoginType),
Sso(SsoLoginType),
Appservice(AppserviceLoginType),
#[doc(hidden)]
_Custom(Box<CustomLoginType>),
}
impl LoginType {
pub fn password() -> Self {
Self::Password(PasswordLoginType::new())
}
pub fn appservice() -> Self {
Self::Appservice(AppserviceLoginType::new())
}
pub fn new(login_type: &str, data: JsonObject) -> serde_json::Result<Self> {
fn from_json_object<T: DeserializeOwned>(obj: JsonObject) -> serde_json::Result<T> {
serde_json::from_value(JsonValue::Object(obj))
}
Ok(match login_type {
"m.login.password" => Self::Password(from_json_object(data)?),
"m.login.token" => Self::Token(from_json_object(data)?),
"m.login.sso" => Self::Sso(from_json_object(data)?),
"m.login.application_service" => Self::Appservice(from_json_object(data)?),
_ => Self::_Custom(Box::new(CustomLoginType {
type_: login_type.to_owned(),
data,
})),
})
}
pub fn login_type(&self) -> &str {
match self {
Self::Password(_) => "m.login.password",
Self::Token(_) => "m.login.token",
Self::Sso(_) => "m.login.sso",
Self::Appservice(_) => "m.login.application_service",
Self::_Custom(c) => &c.type_,
}
}
pub fn data(&self) -> Cow<'_, JsonObject> {
fn serialize<T: Serialize>(obj: &T) -> JsonObject {
match serde_json::to_value(obj).expect("login type serialization to succeed") {
JsonValue::Object(obj) => obj,
_ => panic!("all login types must serialize to objects"),
}
}
match self {
Self::Password(d) => Cow::Owned(serialize(d)),
Self::Token(d) => Cow::Owned(serialize(d)),
Self::Sso(d) => Cow::Owned(serialize(d)),
Self::Appservice(d) => Cow::Owned(serialize(d)),
Self::_Custom(c) => Cow::Borrowed(&c.data),
}
}
}
#[derive(ToSchema, Clone, Debug, Default, Deserialize, Serialize)]
#[serde(tag = "type", rename = "m.login.password")]
pub struct PasswordLoginType {}
impl PasswordLoginType {
pub fn new() -> Self {
Self {}
}
}
#[derive(ToSchema, Clone, Debug, Default, Deserialize, Serialize)]
#[serde(tag = "type", rename = "m.login.token")]
pub struct TokenLoginType {
#[serde(default, skip_serializing_if = "crate::serde::is_default")]
pub get_login_token: bool,
}
impl TokenLoginType {
pub fn new() -> Self {
Self { get_login_token: false }
}
}
#[derive(ToSchema, Clone, Debug, Default, Deserialize, Serialize)]
#[serde(tag = "type", rename = "m.login.sso")]
pub struct SsoLoginType {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub identity_providers: Vec<IdentityProvider>,
}
impl SsoLoginType {
pub fn new() -> Self {
Self::default()
}
}
#[derive(ToSchema, Deserialize, Serialize, Clone, Debug)]
pub struct IdentityProvider {
pub id: String,
pub name: String,
pub icon: Option<OwnedMxcUri>,
pub brand: Option<IdentityProviderBrand>,
}
impl IdentityProvider {
pub fn new(id: String, name: String) -> Self {
Self {
id,
name,
icon: None,
brand: None,
}
}
}
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
#[derive(ToSchema, Clone, PartialEq, Eq, StringEnum)]
pub enum IdentityProviderBrand {
#[palpo_enum(rename = "apple")]
Apple,
#[palpo_enum(rename = "facebook")]
Facebook,
#[palpo_enum(rename = "github")]
GitHub,
#[palpo_enum(rename = "gitlab")]
GitLab,
#[palpo_enum(rename = "google")]
Google,
#[palpo_enum(rename = "twitter")]
Twitter,
#[doc(hidden)]
#[salvo(schema(value_type = String))]
_Custom(PrivOwnedStr),
}
#[derive(ToSchema, Clone, Debug, Default, Deserialize, Serialize)]
#[serde(tag = "type", rename = "m.login.application_service")]
pub struct AppserviceLoginType {}
impl AppserviceLoginType {
pub fn new() -> Self {
Self::default()
}
}
#[doc(hidden)]
#[derive(ToSchema, Deserialize, Serialize, Clone, Debug)]
#[allow(clippy::exhaustive_structs)]
pub struct CustomLoginType {
#[serde(rename = "type")]
pub type_: String,
#[serde(flatten)]
#[salvo(schema(value_type = Object, additional_properties = true))]
pub data: JsonObject,
}
#[derive(ToSchema, Deserialize, Debug)]
pub struct TokenReqBody {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub auth: Option<AuthData>,
}
#[derive(ToSchema, Serialize, Debug)]
pub struct TokenResBody {
#[serde(with = "crate::serde::duration::ms", rename = "expires_in_ms")]
pub expires_in: Duration,
pub login_token: String,
}
impl TokenResBody {
pub fn new(expires_in: Duration, login_token: String) -> Self {
Self {
expires_in,
login_token,
}
}
pub fn with_default_expiration_duration(login_token: String) -> Self {
Self::new(Self::default_expiration_duration(), login_token)
}
fn default_expiration_duration() -> Duration {
Duration::from_secs(2 * 60)
}
}