mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-05-26 20:49:55 +00:00
feat: Add a page for viewing a device's details
This commit is contained in:
Generated
+1
@@ -1119,6 +1119,7 @@ dependencies = [
|
|||||||
"rand 0.10.1",
|
"rand 0.10.1",
|
||||||
"ruma",
|
"ruma",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
|
|||||||
+12
-19
@@ -48,14 +48,14 @@ struct Services {
|
|||||||
users: Dep<users::Service>,
|
users: Dep<users::Service>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
struct SessionInfo {
|
pub struct SessionInfo {
|
||||||
client_id: String,
|
pub client_id: String,
|
||||||
|
pub scopes: BTreeSet<Scope>,
|
||||||
current_refresh_token: String,
|
current_refresh_token: String,
|
||||||
scopes: BTreeSet<Scope>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
struct RefreshTokenInfo {
|
struct RefreshTokenInfo {
|
||||||
client_id: String,
|
client_id: String,
|
||||||
user_id: OwnedUserId,
|
user_id: OwnedUserId,
|
||||||
@@ -166,18 +166,17 @@ impl Service {
|
|||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_client_id_for_device(
|
pub async fn get_session_info_for_device(
|
||||||
&self,
|
&self,
|
||||||
user_id: &UserId,
|
user_id: &UserId,
|
||||||
device_id: &DeviceId,
|
device_id: &DeviceId,
|
||||||
) -> Option<String> {
|
) -> Option<SessionInfo> {
|
||||||
self.db
|
self.db
|
||||||
.userdeviceid_oauthsessioninfo
|
.userdeviceid_oauthsessioninfo
|
||||||
.qry(&(user_id, device_id))
|
.qry(&(user_id, device_id))
|
||||||
.await
|
.await
|
||||||
.deserialized::<SessionInfo>()
|
.deserialized::<SessionInfo>()
|
||||||
.ok()
|
.ok()
|
||||||
.map(|session| session.client_id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn request_authorization_code(
|
pub async fn request_authorization_code(
|
||||||
@@ -417,11 +416,11 @@ impl Service {
|
|||||||
assert_eq!(&client_id, &refresh_token_info.client_id, "refresh token client id mismatch");
|
assert_eq!(&client_id, &refresh_token_info.client_id, "refresh token client id mismatch");
|
||||||
|
|
||||||
let mut session_info = self
|
let mut session_info = self
|
||||||
.db
|
.get_session_info_for_device(
|
||||||
.userdeviceid_oauthsessioninfo
|
&refresh_token_info.user_id,
|
||||||
.qry(&(&refresh_token_info.user_id, &refresh_token_info.device_id))
|
&refresh_token_info.device_id,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.deserialized::<SessionInfo>()
|
|
||||||
.expect("session info should exist");
|
.expect("session info should exist");
|
||||||
|
|
||||||
assert_eq!(&client_id, &session_info.client_id, "session info client id mismatch");
|
assert_eq!(&client_id, &session_info.client_id, "session info client id mismatch");
|
||||||
@@ -464,13 +463,7 @@ impl Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn remove_session(&self, user_id: &UserId, device_id: &DeviceId) {
|
pub async fn remove_session(&self, user_id: &UserId, device_id: &DeviceId) {
|
||||||
let session_info = self
|
let session_info = self.get_session_info_for_device(user_id, device_id).await;
|
||||||
.db
|
|
||||||
.userdeviceid_oauthsessioninfo
|
|
||||||
.qry(&(user_id, device_id))
|
|
||||||
.await
|
|
||||||
.deserialized::<SessionInfo>()
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
if let Some(session_info) = session_info {
|
if let Some(session_info) = session_info {
|
||||||
self.db
|
self.db
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ impl Service {
|
|||||||
self
|
self
|
||||||
.services
|
.services
|
||||||
.oauth
|
.oauth
|
||||||
.get_client_id_for_device(initiator.user_id, device_id)
|
.get_session_info_for_device(initiator.user_id, device_id)
|
||||||
.await
|
.await
|
||||||
.is_some()
|
.is_some()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -139,7 +139,6 @@ pub async fn get_dehydrated_device_id(&self, user_id: &UserId) -> Result<OwnedDe
|
|||||||
level = "debug",
|
level = "debug",
|
||||||
skip_all,
|
skip_all,
|
||||||
fields(%user_id),
|
fields(%user_id),
|
||||||
ret,
|
|
||||||
)]
|
)]
|
||||||
pub async fn get_dehydrated_device(&self, user_id: &UserId) -> Result<DehydratedDevice> {
|
pub async fn get_dehydrated_device(&self, user_id: &UserId) -> Result<DehydratedDevice> {
|
||||||
self.db
|
self.db
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ ruma.workspace = true
|
|||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
tower-http.workspace = true
|
tower-http.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
serde_json.workspace = true
|
||||||
lettre.workspace = true
|
lettre.workspace = true
|
||||||
memory-serve = "2.1.0"
|
memory-serve = "2.1.0"
|
||||||
validator = { version = "0.20.0", features = ["derive"] }
|
validator = { version = "0.20.0", features = ["derive"] }
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use axum::{
|
use axum::{
|
||||||
Router,
|
Router,
|
||||||
extract::{Path, State},
|
extract::{Path, State},
|
||||||
routing::on,
|
routing::{get, on},
|
||||||
};
|
};
|
||||||
|
use conduwuit_service::oauth::{SessionInfo, client_metadata::ClientMetadata};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use ruma::OwnedDeviceId;
|
use ruma::OwnedDeviceId;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -10,14 +11,61 @@ use serde::{Deserialize, Serialize};
|
|||||||
use crate::{
|
use crate::{
|
||||||
WebError,
|
WebError,
|
||||||
extract::{Expect, PostForm},
|
extract::{Expect, PostForm},
|
||||||
pages::{GET_POST, Result, components::DeviceCard},
|
pages::{
|
||||||
|
GET_POST, Result,
|
||||||
|
components::{ClientScopes, DeviceCard, DeviceCardStyle},
|
||||||
|
},
|
||||||
response,
|
response,
|
||||||
session::{LoginTarget, User},
|
session::{LoginTarget, User},
|
||||||
template,
|
template,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) fn build() -> Router<crate::State> {
|
pub(crate) fn build() -> Router<crate::State> {
|
||||||
Router::new().route("/{device}/remove", on(GET_POST, route_remove_device))
|
Router::new()
|
||||||
|
.route("/{device}/", get(get_device_info))
|
||||||
|
.route("/{device}/remove", on(GET_POST, route_remove_device))
|
||||||
|
}
|
||||||
|
|
||||||
|
template! {
|
||||||
|
struct DeviceInfo use "device_info.html.j2" {
|
||||||
|
device_card: DeviceCard,
|
||||||
|
client_metadata: Option<(ClientMetadata, SessionInfo)>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_device_info(
|
||||||
|
State(services): State<crate::State>,
|
||||||
|
user: User,
|
||||||
|
Expect(Path(query)): Expect<Path<DevicePath>>,
|
||||||
|
) -> Result {
|
||||||
|
let user_id = user.expect(LoginTarget::RemoveDevice(query.clone()))?;
|
||||||
|
|
||||||
|
let Ok(device) = services
|
||||||
|
.users
|
||||||
|
.get_device_metadata(&user_id, &query.device)
|
||||||
|
.await
|
||||||
|
else {
|
||||||
|
return response!(WebError::BadRequest("Unknown device".to_owned()));
|
||||||
|
};
|
||||||
|
|
||||||
|
let client_metadata = async {
|
||||||
|
let session_info = services
|
||||||
|
.oauth
|
||||||
|
.get_session_info_for_device(&user_id, &device.device_id)
|
||||||
|
.await?;
|
||||||
|
let client_metadata = services
|
||||||
|
.oauth
|
||||||
|
.get_client_metadata(&session_info.client_id)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Some((client_metadata, session_info))
|
||||||
|
}
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let device_card =
|
||||||
|
DeviceCard::for_device(&services, &user_id, device, DeviceCardStyle::Detailed).await;
|
||||||
|
|
||||||
|
response!(DeviceInfo::new(&services, device_card, client_metadata))
|
||||||
}
|
}
|
||||||
|
|
||||||
template! {
|
template! {
|
||||||
@@ -36,14 +84,14 @@ enum RemoveDeviceBody {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub(crate) struct RemoveDevicePath {
|
pub(crate) struct DevicePath {
|
||||||
pub device: OwnedDeviceId,
|
pub device: OwnedDeviceId,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn route_remove_device(
|
async fn route_remove_device(
|
||||||
State(services): State<crate::State>,
|
State(services): State<crate::State>,
|
||||||
user: User,
|
user: User,
|
||||||
Expect(Path(query)): Expect<Path<RemoveDevicePath>>,
|
Expect(Path(query)): Expect<Path<DevicePath>>,
|
||||||
PostForm(form): PostForm<()>,
|
PostForm(form): PostForm<()>,
|
||||||
) -> Result {
|
) -> Result {
|
||||||
let user_id = user.expect(LoginTarget::RemoveDevice(query.clone()))?;
|
let user_id = user.expect(LoginTarget::RemoveDevice(query.clone()))?;
|
||||||
@@ -64,7 +112,8 @@ async fn route_remove_device(
|
|||||||
|
|
||||||
response!(RemoveDevice::new(&services, RemoveDeviceBody::Success))
|
response!(RemoveDevice::new(&services, RemoveDeviceBody::Success))
|
||||||
} else {
|
} else {
|
||||||
let device_card = DeviceCard::for_device(&services, &user_id, device, false).await;
|
let device_card =
|
||||||
|
DeviceCard::for_device(&services, &user_id, device, DeviceCardStyle::Minimal).await;
|
||||||
let last_device = services.users.all_devices_metadata(&user_id).count().await <= 1;
|
let last_device = services.users.all_devices_metadata(&user_id).count().await <= 1;
|
||||||
|
|
||||||
response!(RemoveDevice::new(&services, RemoveDeviceBody::Form {
|
response!(RemoveDevice::new(&services, RemoveDeviceBody::Form {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use axum::{Router, extract::State, response::Response, routing::get};
|
use axum::{Router, extract::State, response::Response, routing::get};
|
||||||
use conduwuit_core::utils::{IterStream, stream::TryExpect};
|
use conduwuit_core::utils::{IterStream, ReadyExt, stream::TryExpect};
|
||||||
use conduwuit_service::threepid::EmailRequirement;
|
use conduwuit_service::threepid::EmailRequirement;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use ruma::{OwnedClientSecret, OwnedSessionId};
|
use ruma::{OwnedClientSecret, OwnedSessionId};
|
||||||
@@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
WebError,
|
WebError,
|
||||||
pages::components::{DeviceCard, UserCard},
|
pages::components::{DeviceCard, DeviceCardStyle, UserCard},
|
||||||
response,
|
response,
|
||||||
session::{LoginTarget, User},
|
session::{LoginTarget, User},
|
||||||
template,
|
template,
|
||||||
@@ -64,6 +64,8 @@ async fn get_account(
|
|||||||
|
|
||||||
let user_card = UserCard::for_local_user(&services, user_id.clone()).await;
|
let user_card = UserCard::for_local_user(&services, user_id.clone()).await;
|
||||||
|
|
||||||
|
let dehydrated_device_id = services.users.get_dehydrated_device_id(&user_id).await.ok();
|
||||||
|
|
||||||
let mut devices: Vec<_> = services
|
let mut devices: Vec<_> = services
|
||||||
.users
|
.users
|
||||||
.all_device_ids(&user_id)
|
.all_device_ids(&user_id)
|
||||||
@@ -74,6 +76,11 @@ async fn get_account(
|
|||||||
.await
|
.await
|
||||||
})
|
})
|
||||||
.expect_ok()
|
.expect_ok()
|
||||||
|
.ready_filter(|device| {
|
||||||
|
dehydrated_device_id
|
||||||
|
.as_ref()
|
||||||
|
.is_none_or(|id| device.device_id != *id)
|
||||||
|
})
|
||||||
.collect()
|
.collect()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@@ -82,7 +89,9 @@ async fn get_account(
|
|||||||
let device_cards = devices
|
let device_cards = devices
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.stream()
|
.stream()
|
||||||
.then(async |device| DeviceCard::for_device(&services, &user_id, device, true).await)
|
.then(async |device| {
|
||||||
|
DeviceCard::for_device(&services, &user_id, device, DeviceCardStyle::Minimal).await
|
||||||
|
})
|
||||||
.collect()
|
.collect()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
use std::time::SystemTime;
|
use std::{collections::BTreeSet, time::SystemTime};
|
||||||
|
|
||||||
use askama::{Template, filters::HtmlSafe};
|
use askama::{Template, filters::HtmlSafe};
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use conduwuit_core::{result::FlatOk, utils};
|
use conduwuit_core::{result::FlatOk, utils};
|
||||||
use conduwuit_service::{Services, media::mxc::Mxc, oauth::client_metadata::ClientMetadata};
|
use conduwuit_service::{
|
||||||
|
Services,
|
||||||
|
media::mxc::Mxc,
|
||||||
|
oauth::{client_metadata::ClientMetadata, grant::Scope},
|
||||||
|
};
|
||||||
use ruma::{OwnedDeviceId, OwnedUserId, UserId, api::client::device::Device};
|
use ruma::{OwnedDeviceId, OwnedUserId, UserId, api::client::device::Device};
|
||||||
|
|
||||||
pub(super) mod form;
|
pub(super) mod form;
|
||||||
@@ -59,6 +63,29 @@ impl Avatar {
|
|||||||
|
|
||||||
Self { avatar_type }
|
Self { avatar_type }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn for_device(
|
||||||
|
oauth_metadata: Option<&ClientMetadata>,
|
||||||
|
display_name: Option<&str>,
|
||||||
|
) -> Self {
|
||||||
|
let avatar_src = oauth_metadata
|
||||||
|
.and_then(|metadata| metadata.logo_uri.as_ref())
|
||||||
|
.map(|uri| uri.as_str().to_owned());
|
||||||
|
|
||||||
|
let avatar_type = if let Some(avatar_src) = avatar_src {
|
||||||
|
AvatarType::Image(avatar_src)
|
||||||
|
} else if let Some(initial) = display_name.and_then(|name| name.chars().next()) {
|
||||||
|
if oauth_metadata.is_some() {
|
||||||
|
AvatarType::Initial(initial)
|
||||||
|
} else {
|
||||||
|
AvatarType::Initial('❖')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
AvatarType::Initial('?')
|
||||||
|
};
|
||||||
|
|
||||||
|
Self { avatar_type }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Template)]
|
#[derive(Debug, Template)]
|
||||||
@@ -88,28 +115,34 @@ pub(super) struct DeviceCard {
|
|||||||
pub avatar: Avatar,
|
pub avatar: Avatar,
|
||||||
pub last_active: String,
|
pub last_active: String,
|
||||||
pub oauth_metadata: Option<ClientMetadata>,
|
pub oauth_metadata: Option<ClientMetadata>,
|
||||||
pub show_actions: bool,
|
pub style: DeviceCardStyle,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HtmlSafe for DeviceCard {}
|
impl HtmlSafe for DeviceCard {}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub(super) enum DeviceCardStyle {
|
||||||
|
Minimal,
|
||||||
|
Detailed,
|
||||||
|
}
|
||||||
|
|
||||||
impl DeviceCard {
|
impl DeviceCard {
|
||||||
pub(super) async fn for_device(
|
pub(super) async fn for_device(
|
||||||
services: &Services,
|
services: &Services,
|
||||||
user_id: &UserId,
|
user_id: &UserId,
|
||||||
device: Device,
|
device: Device,
|
||||||
show_actions: bool,
|
style: DeviceCardStyle,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let oauth_metadata = async {
|
let oauth_metadata = async {
|
||||||
let client_id = services
|
let session_info = services
|
||||||
.oauth
|
.oauth
|
||||||
.get_client_id_for_device(user_id, &device.device_id)
|
.get_session_info_for_device(user_id, &device.device_id)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
services
|
services
|
||||||
.oauth
|
.oauth
|
||||||
.get_client_metadata(&client_id)
|
.get_client_metadata(&session_info.client_id)
|
||||||
.await
|
.await
|
||||||
.expect("client should exist"),
|
.expect("client should exist"),
|
||||||
)
|
)
|
||||||
@@ -121,28 +154,7 @@ impl DeviceCard {
|
|||||||
.and_then(|metadata| metadata.client_name.clone())
|
.and_then(|metadata| metadata.client_name.clone())
|
||||||
.or_else(|| device.display_name.clone());
|
.or_else(|| device.display_name.clone());
|
||||||
|
|
||||||
let avatar = {
|
let avatar = Avatar::for_device(oauth_metadata.as_ref(), display_name.as_deref());
|
||||||
let avatar_src = oauth_metadata
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|metadata| metadata.logo_uri.as_ref())
|
|
||||||
.map(|uri| uri.as_str().to_owned());
|
|
||||||
|
|
||||||
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_active = device.last_seen_ts.map_or_else(
|
let last_active = device.last_seen_ts.map_or_else(
|
||||||
|| "unknown".to_owned(),
|
|| "unknown".to_owned(),
|
||||||
@@ -163,7 +175,15 @@ impl DeviceCard {
|
|||||||
avatar,
|
avatar,
|
||||||
last_active,
|
last_active,
|
||||||
oauth_metadata,
|
oauth_metadata,
|
||||||
show_actions,
|
style,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Template)]
|
||||||
|
#[template(path = "_components/client_scopes.html.j2")]
|
||||||
|
pub(super) struct ClientScopes {
|
||||||
|
pub scopes: BTreeSet<Scope>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HtmlSafe for ClientScopes {}
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
use std::collections::BTreeSet;
|
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
Router,
|
Router,
|
||||||
extract::{Query, State},
|
extract::{Query, State},
|
||||||
response::Redirect,
|
response::Redirect,
|
||||||
routing::on,
|
routing::on,
|
||||||
};
|
};
|
||||||
use conduwuit_service::oauth::grant::{AuthorizationCodeQuery, Scope};
|
use conduwuit_service::oauth::grant::AuthorizationCodeQuery;
|
||||||
use ruma::OwnedUserId;
|
use ruma::OwnedUserId;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
@@ -15,7 +13,7 @@ use crate::{
|
|||||||
extract::{Expect, PostForm},
|
extract::{Expect, PostForm},
|
||||||
pages::{
|
pages::{
|
||||||
GET_POST, Result,
|
GET_POST, Result,
|
||||||
components::{Avatar, AvatarType},
|
components::{Avatar, AvatarType, ClientScopes},
|
||||||
},
|
},
|
||||||
response,
|
response,
|
||||||
session::{LoginQuery, LoginTarget, User},
|
session::{LoginQuery, LoginTarget, User},
|
||||||
@@ -36,7 +34,7 @@ template! {
|
|||||||
client_avatar: Avatar,
|
client_avatar: Avatar,
|
||||||
policy_uri: Option<Url>,
|
policy_uri: Option<Url>,
|
||||||
tos_uri: Option<Url>,
|
tos_uri: Option<Url>,
|
||||||
scopes: BTreeSet<Scope>
|
scopes: ClientScopes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,6 +99,6 @@ async fn route_authorization_code(
|
|||||||
client_avatar,
|
client_avatar,
|
||||||
client.policy_uri.clone(),
|
client.policy_uri.clone(),
|
||||||
client.tos_uri.clone(),
|
client.tos_uri.clone(),
|
||||||
scopes,
|
ClientScopes { scopes },
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<ul>
|
||||||
|
{% for scope in scopes %}
|
||||||
|
{% match scope %}
|
||||||
|
{% when Scope::ClientApi %}
|
||||||
|
<li>Interact with Matrix on your behalf</li>
|
||||||
|
{% when Scope::Device(_) %}
|
||||||
|
<li>Connect to your Matrix account</li>
|
||||||
|
{% endmatch %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
@@ -3,28 +3,30 @@
|
|||||||
<div class="info">
|
<div class="info">
|
||||||
<p class="name">
|
<p class="name">
|
||||||
{% if let Some(display_name) = display_name %}
|
{% if let Some(display_name) = display_name %}
|
||||||
{% if let Some(metadata) = oauth_metadata %}
|
{{ display_name }}
|
||||||
<a href="{{ metadata.client_uri }}">{{ display_name }}</a>
|
|
||||||
{% else %}
|
|
||||||
{{ display_name }}
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
{% else %}
|
||||||
Unknown device
|
Unknown device
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<span class="id">
|
{% if style == DeviceCardStyle::Detailed %}
|
||||||
• {{ device_id }}
|
<span class="id">
|
||||||
{% if oauth_metadata.is_none() %}
|
• {{ device_id }}
|
||||||
(legacy)
|
{% if let Some(metadata) = oauth_metadata %}
|
||||||
|
• <a href="{{ metadata.client_uri }}">Client website</a>
|
||||||
|
{% else %}
|
||||||
|
(legacy)
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</span>
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Last active: {{ last_active }}
|
Last active: {{ last_active }}
|
||||||
</p>
|
</p>
|
||||||
{% if show_actions %}
|
<p>
|
||||||
<p>
|
{% if style == DeviceCardStyle::Detailed %}
|
||||||
<a href="device/{{ device_id }}/remove">Remove</a>
|
<a href="{{ crate::ROUTE_PREFIX }}/account/device/{{ device_id }}/remove">Remove</a>
|
||||||
</p>
|
{% else %}
|
||||||
|
<a href="{{ crate::ROUTE_PREFIX }}/account/device/{{ device_id }}/">Details</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
{% extends "_layout.html.j2" %}
|
||||||
|
|
||||||
|
{%- block title -%}
|
||||||
|
Device information
|
||||||
|
{%- endblock -%}
|
||||||
|
|
||||||
|
{%- block content -%}
|
||||||
|
<div class="panel">
|
||||||
|
<h1>About Device <a class="back" href="{{ crate::ROUTE_PREFIX }}/account/">Back</a></h1>
|
||||||
|
{{ device_card }}
|
||||||
|
{% if let Some((client_metadata, _)) = client_metadata %}
|
||||||
|
<section>
|
||||||
|
<p>
|
||||||
|
{% if let Some(tos_uri) = &client_metadata.tos_uri %}
|
||||||
|
<a href="{{ tos_uri }}">Terms of service</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if let Some(policy_uri) = &client_metadata.policy_uri %}
|
||||||
|
• <a href="{{ policy_uri }}">Policies</a>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
<section>
|
||||||
|
<p>
|
||||||
|
{% if let Some((_, session_info)) = client_metadata %}
|
||||||
|
This device has permission to:
|
||||||
|
{{ ClientScopes { scopes: session_info.scopes.clone() } }}
|
||||||
|
{% else %}
|
||||||
|
This device can access and control all features of your Matrix account.
|
||||||
|
<br>
|
||||||
|
<small>❖ <i>This is a legacy device. Legacy devices
|
||||||
|
always have full access to your account.</i></small>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
{% if let Some((client_metadata, session_info)) = client_metadata %}
|
||||||
|
<details>
|
||||||
|
<summary>Client registration</summary>
|
||||||
|
<p>
|
||||||
|
OAuth client ID: <code>{{ session_info.client_id }}</code>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Scopes: <code>
|
||||||
|
{% for scope in session_info.scopes %}
|
||||||
|
{{ scope | safe }}
|
||||||
|
{% endfor %}
|
||||||
|
</code>
|
||||||
|
</p>
|
||||||
|
<pre><code>{{ serde_json::to_string_pretty(&client_metadata).unwrap() }}</code></pre>
|
||||||
|
</details>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{%- endblock -%}
|
||||||
@@ -26,16 +26,7 @@ Authorize client
|
|||||||
<p>
|
<p>
|
||||||
<b>{{ client_name }}</b> (<a href="{{ client_uri }}">{{ client_uri.domain().unwrap() }}</a>) would like
|
<b>{{ client_name }}</b> (<a href="{{ client_uri }}">{{ client_uri.domain().unwrap() }}</a>) would like
|
||||||
your permission to:
|
your permission to:
|
||||||
<ul>
|
{{ scopes }}
|
||||||
{% for scope in scopes %}
|
|
||||||
{% match scope %}
|
|
||||||
{% when Scope::ClientApi %}
|
|
||||||
<li>Interact with Matrix on your behalf</li>
|
|
||||||
{% when Scope::Device(_) %}
|
|
||||||
<li>Connect to your Matrix account</li>
|
|
||||||
{% endmatch %}
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</p>
|
</p>
|
||||||
{% match (&policy_uri, &tos_uri) %}
|
{% match (&policy_uri, &tos_uri) %}
|
||||||
{% when (Some(policy_uri), Some(tos_uri)) %}
|
{% when (Some(policy_uri), Some(tos_uri)) %}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use ruma::{OwnedUserId, UserId};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tower_sessions::Session;
|
use tower_sessions::Session;
|
||||||
|
|
||||||
use crate::{ROUTE_PREFIX, WebError, pages::account::device::RemoveDevicePath};
|
use crate::{ROUTE_PREFIX, WebError, pages::account::device::DevicePath};
|
||||||
|
|
||||||
pub(crate) mod store;
|
pub(crate) mod store;
|
||||||
|
|
||||||
@@ -32,7 +32,8 @@ pub(crate) enum LoginTarget {
|
|||||||
ChangeEmail,
|
ChangeEmail,
|
||||||
CrossSigningReset,
|
CrossSigningReset,
|
||||||
Deactivate,
|
Deactivate,
|
||||||
RemoveDevice(RemoveDevicePath),
|
DeviceInfo(DevicePath),
|
||||||
|
RemoveDevice(DevicePath),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for LoginTarget {
|
impl PartialEq for LoginTarget {
|
||||||
@@ -52,8 +53,8 @@ impl LoginTarget {
|
|||||||
| Self::ChangeEmail => "account/email/change/".into(),
|
| Self::ChangeEmail => "account/email/change/".into(),
|
||||||
| Self::CrossSigningReset => "account/cross_signing_reset".into(),
|
| Self::CrossSigningReset => "account/cross_signing_reset".into(),
|
||||||
| Self::Deactivate => "account/deactivate".into(),
|
| Self::Deactivate => "account/deactivate".into(),
|
||||||
| Self::RemoveDevice(path) =>
|
| Self::DeviceInfo(path) => format!("account/device/{}/", path.device).into(),
|
||||||
format!("account/device/{}/remove", path.device,).into(),
|
| Self::RemoveDevice(path) => format!("account/device/{}/remove", path.device).into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
format!("{ROUTE_PREFIX}/{path}")
|
format!("{ROUTE_PREFIX}/{path}")
|
||||||
|
|||||||
Reference in New Issue
Block a user