tuwunel_api/oidc/account/
session_list.rs1use std::cmp;
2
3use const_str::format as const_format;
4use futures::StreamExt;
5use ruma::{MilliSecondsSinceUnixEpoch, UserId};
6use tuwunel_core::{Result, utils::html::escape as html_escape};
7use tuwunel_service::Services;
8
9use super::{ACCOUNT_HEAD, ACCOUNT_JS_INCLUDE, ts_cell, url_encode};
10
11pub(super) async fn sessions_list_html(services: &Services, user_id: &UserId) -> Result<String> {
12 let mut devices: Vec<_> = services
13 .users
14 .all_devices_metadata(user_id)
15 .collect()
16 .await;
17
18 devices.sort_by_key(|b| cmp::Reverse(b.last_seen_ts));
20
21 let mut rows = Vec::new();
22 for device in &devices {
23 let device_display_name = device
24 .display_name
25 .as_deref()
26 .unwrap_or("Unknown device");
27
28 let name = html_escape(device_display_name);
29 let id_enc = url_encode(device.device_id.as_str());
30 let id = html_escape(device.device_id.as_str());
31 let ip = html_escape(device.last_seen_ip.as_deref().unwrap_or("—"));
32 let ts_cell = device
33 .last_seen_ts
34 .as_ref()
35 .map(MilliSecondsSinceUnixEpoch::as_secs)
36 .map(u64::from)
37 .map(ts_cell)
38 .unwrap_or_default();
39
40 rows.push(format!(
41 r#"
42 <tr>
43 <td>{name}</td>
44 <td><code>{id}</code></td>
45 <td>{ip}</td>
46 <td>{ts_cell}</td>
47 <td class="center">
48 <a href="/_tuwunel/oidc/account?action=org.matrix.session_view&device_id={id_enc}">
49 View
50 </a>
51 <span class="sep"> | </span>
52 <a
53 href="/_tuwunel/oidc/account?action=org.matrix.session_end&device_id={id_enc}"
54 class="err"
55 >
56 Sign out
57 </a>
58 </td>
59 </tr>"#
60 ));
61 }
62
63 Ok(PAGE_HTML
64 .replace("{uid}", &html_escape(user_id.as_str()))
65 .replace("{dlen}", &devices.len().to_string())
66 .replace("{rows}", &rows.join("")))
67}
68
69static PAGE_HTML: &str = const_format!(
70 r#"
71<!DOCTYPE html>
72<html lang="en">
73 <head>
74 {ACCOUNT_HEAD}
75 <title>Active Sessions</title>
76 </head>
77 <body class="wide">
78 <h1>Active Sessions</h1>
79 <p>
80 Signed in as <strong>{{uid}}</strong>. {{dlen}} active session(s).
81 </p>
82 <table>
83 <tr>
84 <th>Name</th>
85 <th>Device ID</th>
86 <th>Last seen IP</th>
87 <th>Last seen</th>
88 <th class="center">Actions</th>
89 </tr>
90 {{rows}}
91 </table>
92 <div class="nav">
93 <a href="/_tuwunel/oidc/account?action=org.matrix.profile">View Profile</a>
94 </div>
95 {ACCOUNT_JS_INCLUDE}
96 </body>
97</html>"#
98);