feat: Implement oauth auth code and refresh token flows

This commit is contained in:
Ginger
2026-04-30 08:54:55 -04:00
parent f269fb5cfc
commit 13917bb5c3
37 changed files with 1057 additions and 157 deletions
+75 -69
View File
@@ -4,36 +4,26 @@ use askama::{Template, filters::HtmlSafe};
use base64::Engine;
use conduwuit_core::{result::FlatOk, utils};
use conduwuit_service::{Services, media::mxc::Mxc, oauth::client_metadata::ClientMetadata};
use ruma::{OwnedDeviceId, OwnedUserId, UserId};
use ruma::{MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedUserId, UserId};
pub(super) mod form;
#[derive(Debug)]
pub(super) enum AvatarType<'a> {
pub(super) enum AvatarType {
Initial(char),
Image(&'a str),
Image(String),
}
#[derive(Debug, Template)]
#[template(path = "_components/avatar.html.j2")]
pub(super) struct Avatar<'a> {
pub(super) avatar_type: AvatarType<'a>,
pub(super) struct Avatar {
pub(super) avatar_type: AvatarType,
}
impl HtmlSafe for Avatar<'_> {}
impl HtmlSafe for Avatar {}
#[derive(Debug, Template)]
#[template(path = "_components/user_card.html.j2")]
pub(super) struct UserCard {
pub user_id: OwnedUserId,
pub display_name: Option<String>,
pub avatar_src: Option<String>,
}
impl HtmlSafe for UserCard {}
impl UserCard {
pub(super) async fn for_local_user(services: &Services, user_id: OwnedUserId) -> Self {
impl Avatar {
pub(super) async fn for_local_user(services: &Services, user_id: &UserId) -> Self {
let display_name = services.users.displayname(&user_id).await.ok();
let avatar_src = async {
@@ -56,33 +46,48 @@ impl UserCard {
}
.await;
Self { user_id, display_name, avatar_src }
}
fn avatar(&self) -> Avatar<'_> {
let avatar_type = if let Some(ref avatar_src) = self.avatar_src {
let avatar_type = if let Some(avatar_src) = avatar_src {
AvatarType::Image(avatar_src)
} else if let Some(initial) = self
.display_name
} else if let Some(initial) = display_name
.as_ref()
.and_then(|display_name| display_name.chars().next())
{
AvatarType::Initial(initial)
} else {
AvatarType::Initial(self.user_id.localpart().chars().next().unwrap())
AvatarType::Initial(user_id.localpart().chars().next().unwrap())
};
Avatar { avatar_type }
}
}
#[derive(Debug, Template)]
#[template(path = "_components/user_card.html.j2")]
pub(super) struct UserCard {
pub user_id: OwnedUserId,
pub display_name: Option<String>,
pub avatar: Avatar,
}
impl HtmlSafe for UserCard {}
impl UserCard {
pub(super) async fn for_local_user(services: &Services, user_id: OwnedUserId) -> Self {
let display_name = services.users.displayname(&user_id).await.ok();
let avatar = Avatar::for_local_user(services, &user_id).await;
Self { user_id, display_name, avatar }
}
}
#[derive(Debug, Template)]
#[template(path = "_components/device_card.html.j2")]
pub(super) struct DeviceCard {
pub device_id: OwnedDeviceId,
pub display_name: Option<String>,
pub avatar_src: Option<String>,
pub avatar: Avatar,
pub last_active: String,
pub last_seen_ts: Option<u64>,
pub oauth_metadata: Option<ClientMetadata>,
}
@@ -101,12 +106,15 @@ impl DeviceCard {
.ok();
let oauth_metadata = async {
let client_id = services.oauth.get_client_id_for_device(&device_id).await?;
let client_id = services
.oauth
.get_client_id_for_device(user_id, &device_id)
.await?;
Some(
services
.oauth
.get_client_registration(&client_id)
.get_client_metadata(&client_id)
.await
.expect("client should exist"),
)
@@ -122,53 +130,51 @@ impl DeviceCard {
.and_then(|device| device.display_name.clone())
});
let avatar_src = oauth_metadata
.as_ref()
.and_then(|metadata| metadata.logo_uri.as_ref())
.map(|uri| uri.as_str().to_owned());
let avatar = {
let avatar_src = oauth_metadata
.as_ref()
.and_then(|metadata| metadata.logo_uri.as_ref())
.map(|uri| uri.as_str().to_owned());
let last_active = device
.as_ref()
.and_then(|device| device.last_seen_ts)
.map_or_else(
|| "unknown".to_owned(),
|active| {
active
.to_system_time()
.and_then(|t| SystemTime::now().duration_since(t).ok())
.map_or_else(
|| "now".to_owned(),
|duration| format!("{} ago", utils::time::pretty(duration)),
)
},
);
let avatar_type = if let Some(avatar_src) = avatar_src {
AvatarType::Image(avatar_src)
} else if let Some(initial) =
display_name.as_ref().and_then(|name| name.chars().next())
{
if oauth_metadata.is_some() {
AvatarType::Initial(initial)
} else {
AvatarType::Initial('❖')
}
} else {
AvatarType::Initial('?')
};
Avatar { avatar_type }
};
let last_seen_ts = device.as_ref().and_then(|device| device.last_seen_ts);
let last_active = last_seen_ts.map_or_else(
|| "unknown".to_owned(),
|last_seen_ts| {
last_seen_ts
.to_system_time()
.and_then(|t| SystemTime::now().duration_since(t).ok())
.map_or_else(
|| "now".to_owned(),
|duration| format!("{} ago", utils::time::pretty(duration)),
)
},
);
Self {
device_id,
display_name,
avatar_src,
avatar,
last_active,
last_seen_ts: last_seen_ts.map(|last_seen_ts| last_seen_ts.as_secs().into()),
oauth_metadata,
}
}
fn avatar(&self) -> Avatar<'_> {
let avatar_type = if let Some(avatar_src) = &self.avatar_src {
AvatarType::Image(avatar_src.as_str())
} else if let Some(initial) = self
.display_name
.as_ref()
.and_then(|name| name.chars().next())
{
if self.oauth_metadata.is_some() {
AvatarType::Initial(initial)
} else {
AvatarType::Initial('❖')
}
} else {
AvatarType::Initial('?')
};
Avatar { avatar_type }
}
}