diff --git a/src/web/pages/account/mod.rs b/src/web/pages/account/mod.rs index 07cb1ad32..ac294cb71 100644 --- a/src/web/pages/account/mod.rs +++ b/src/web/pages/account/mod.rs @@ -70,6 +70,7 @@ enum AccountBody { email_requirement: EmailRequirement, email: Option, devices: Vec, + dehydrated_device_id: Option, }, Locked, } @@ -132,7 +133,8 @@ async fn get_account( oidc_enabled: services.oidc.enabled(), email_requirement, email, - devices: device_cards + devices: device_cards, + dehydrated_device_id, })) } diff --git a/src/web/pages/components/mod.rs b/src/web/pages/components/mod.rs index 6ca61fd7b..49b3a2f1b 100644 --- a/src/web/pages/components/mod.rs +++ b/src/web/pages/components/mod.rs @@ -67,12 +67,15 @@ impl Avatar { pub(super) fn for_device( client_metadata: Option<&ClientMetadata>, display_name: Option<&str>, + dehydrated: bool, ) -> Self { let avatar_src = client_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 { + let avatar_type = if dehydrated { + AvatarType::Initial('⊡') + } else 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 client_metadata.is_some() { @@ -130,6 +133,7 @@ pub(super) struct DeviceCard { pub last_active: String, pub oauth_metadata: Option, pub style: DeviceCardStyle, + pub dehydrated: bool, } impl HtmlSafe for DeviceCard {} @@ -163,12 +167,21 @@ impl DeviceCard { } .await; + let dehydrated_device_id = services + .users + .get_dehydrated_device(user_id) + .await + .ok() + .map(|device| device.device_id); + let dehydrated = dehydrated_device_id.as_ref() == Some(&device.device_id); + let display_name = oauth_metadata .as_ref() .and_then(|metadata| metadata.client_name.clone()) .or_else(|| device.display_name.clone()); - let avatar = Avatar::for_device(oauth_metadata.as_ref(), display_name.as_deref()); + let avatar = + Avatar::for_device(oauth_metadata.as_ref(), display_name.as_deref(), dehydrated); let last_active = device.last_seen_ts.map_or_else( || "unknown".to_owned(), @@ -190,6 +203,7 @@ impl DeviceCard { last_active, oauth_metadata, style, + dehydrated, } } } diff --git a/src/web/pages/templates/_components/device_card.html.j2 b/src/web/pages/templates/_components/device_card.html.j2 index 7477bc00b..93040a6b5 100644 --- a/src/web/pages/templates/_components/device_card.html.j2 +++ b/src/web/pages/templates/_components/device_card.html.j2 @@ -1,37 +1,41 @@
{{ avatar }}
-
- - {% if let Some(display_name) = display_name %} - {{ display_name }} - {% else %} - Unknown device - {% endif %} - - {% if style == DeviceCardStyle::Detailed %} - - • -
    -
  • {{ device_id }}
  • -
  • - {% if let Some(metadata) = oauth_metadata %} - Client website - {% else %} - legacy - {% endif %} -
  • - + {% if dehydrated %} +
    Dehydrated device
    + {% else %} +
    + + {% if let Some(display_name) = display_name %} + {{ display_name }} + {% else %} + Unknown device + {% endif %} + {% if style == DeviceCardStyle::Detailed %} + + • +
      +
    • {{ device_id }}
    • +
    • + {% if let Some(metadata) = oauth_metadata %} + Client website + {% else %} + legacy + {% endif %} +
    • + + + {% endif %} +
    +
    + Last active: {{ last_active }} +
    +
    + {% if style != DeviceCardStyle::Detailed %} + Details {% endif %} -
    -
    - Last active: {{ last_active }} -
    -
    - {% if style != DeviceCardStyle::Detailed %} - Details +
    {% endif %} -
diff --git a/src/web/pages/templates/account.html.j2 b/src/web/pages/templates/account.html.j2 index f2a95e59b..7e63cf949 100644 --- a/src/web/pages/templates/account.html.j2 +++ b/src/web/pages/templates/account.html.j2 @@ -9,7 +9,7 @@ Your account

Manage your account

{{ user_card }} {% match body %} - {% when AccountBody::Unlocked { suspended, email_requirement, email, devices, oidc_enabled } %} + {% when AccountBody::Unlocked { suspended, email_requirement, email, devices, oidc_enabled, dehydrated_device_id } %} {% if suspended %}

⚠️ Your account has been suspended by your homeserver's administrator. @@ -52,6 +52,9 @@ Your account and sign in to start chatting on Matrix. {% endfor %} + {% if let Some(dehydrated_device_id) = dehydrated_device_id %} + ⊡ Your account has a dehydrated device. View details + {% endif %} diff --git a/src/web/pages/templates/device_info.html.j2 b/src/web/pages/templates/device_info.html.j2 index 9cedfc036..53590c6a9 100644 --- a/src/web/pages/templates/device_info.html.j2 +++ b/src/web/pages/templates/device_info.html.j2 @@ -27,6 +27,8 @@ Device information {% if let Some((_, session_info)) = client_metadata %} This device has permission to: {{ ClientScopes { scopes: session_info.scopes.clone() } }} + {% else if device_card.dehydrated %} + This is your dehydrated device. It saves encryption keys for you while you're offline. {% else %} This device can access and control all features of your Matrix account.
diff --git a/src/web/session/mod.rs b/src/web/session/mod.rs index b49d866e8..0a9568098 100644 --- a/src/web/session/mod.rs +++ b/src/web/session/mod.rs @@ -4,6 +4,7 @@ use std::{ time::{Duration, SystemTime}, }; +use askama::filters::urlencode_strict; use axum::{ extract::FromRequestParts, http::request::Parts, @@ -63,8 +64,11 @@ impl LoginTarget { | Self::DeviceCode(code) => format!("oauth2/grant/device_code?{}", serde_urlencoded::to_string(code).unwrap()) .into(), - | Self::DeviceInfo(path) => format!("account/device/{}/", path.device).into(), - | Self::RemoveDevice(path) => format!("account/device/{}/remove", path.device).into(), + | Self::DeviceInfo(path) => + format!("account/device/{}/", urlencode_strict(&path.device).unwrap()).into(), + | Self::RemoveDevice(path) => + format!("account/device/{}/remove", urlencode_strict(&path.device).unwrap()) + .into(), }; format!("{ROUTE_PREFIX}/{path}")