Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
032b6e04e7 | ||
|
|
884877a55b | ||
|
|
bf5b6d7b45 | ||
|
|
29f83df53c | ||
|
|
11e102f7da | ||
|
|
8ee90444ee | ||
|
|
bb5364b53c | ||
|
|
5b4528e4e5 |
+1
-1
Submodule cinny updated: e2b23397bd...91f82d60e3
Generated
+13
@@ -498,6 +498,7 @@ checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901"
|
|||||||
name = "cinny"
|
name = "cinny"
|
||||||
version = "4.12.2"
|
version = "4.12.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"keyring",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri",
|
"tauri",
|
||||||
@@ -2090,6 +2091,18 @@ dependencies = [
|
|||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "keyring"
|
||||||
|
version = "3.6.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"log",
|
||||||
|
"windows-sys 0.60.2",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kuchikiki"
|
name = "kuchikiki"
|
||||||
version = "0.8.8-speedreader"
|
version = "0.8.8-speedreader"
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ tauri-plugin-single-instance = "2"
|
|||||||
tauri-plugin-autostart = "2" # P6-1 launch-on-login
|
tauri-plugin-autostart = "2" # P6-1 launch-on-login
|
||||||
# Update retry backoff (already in the tree via tauri; adds only the timer).
|
# Update retry backoff (already in the tree via tauri; adds only the timer).
|
||||||
tokio = { version = "1", features = ["time"] }
|
tokio = { version = "1", features = ["time"] }
|
||||||
|
# cinny #105: login tokens in the OS keychain. Without a platform feature
|
||||||
|
# the crate only has its in-memory mock store (used by the tests); Windows
|
||||||
|
# turns on Credential Manager below.
|
||||||
|
keyring = "3.6"
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
# P6-1 desktop parity: screensaver inhibit (no-sleep in calls) + Unity launcher
|
# P6-1 desktop parity: screensaver inhibit (no-sleep in calls) + Unity launcher
|
||||||
@@ -50,6 +54,7 @@ zbus = "5"
|
|||||||
webkit2gtk = "2.0"
|
webkit2gtk = "2.0"
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
|
keyring = { version = "3.6", features = ["windows-native"] }
|
||||||
webview2-com = "0.38"
|
webview2-com = "0.38"
|
||||||
window-vibrancy = "0.6"
|
window-vibrancy = "0.6"
|
||||||
windows = { version = "0.61", features = [
|
windows = { version = "0.61", features = [
|
||||||
|
|||||||
@@ -1,17 +1,22 @@
|
|||||||
//! WebKitGTK workarounds for NVIDIA's proprietary driver on Linux.
|
//! WebKitGTK workarounds for NVIDIA's proprietary driver on Linux.
|
||||||
//!
|
//!
|
||||||
//! On NVIDIA + Wayland (reported on CachyOS/KDE, driver 615, webkit2gtk 2.52)
|
//! Diagnosed on CachyOS/KDE Wayland, RTX 3070, driver 615.71.09, webkit2gtk
|
||||||
//! the app never shows a window: GDK dies with "Error 71 (Protocol error)
|
//! 2.52.6 (reported 2026-09-27, WAYLAND_DEBUG trace in the PR):
|
||||||
//! dispatching to Wayland display", and under XWayland WebKit's DMA-BUF
|
//! - Native Wayland: the compositor kills the connection with
|
||||||
//! renderer then fails with "Failed to create GBM buffer … Invalid argument".
|
//! `wp_linux_drm_syncobj_surface_v1 … "explicit sync is used, but no acquire
|
||||||
//! `WEBKIT_DISABLE_DMABUF_RENDERER=1 GDK_BACKEND=x11` makes it run normally.
|
//! point is set"` (GDK: "Error 71 (Protocol error)"), i.e. an explicit-sync
|
||||||
|
//! bug between WebKit and the driver, not a buffer-format problem.
|
||||||
|
//! `__NV_DISABLE_EXPLICIT_SYNC=1` fixes it and keeps WebKit's GPU (DMA-BUF)
|
||||||
|
//! renderer, so that is the default on native Wayland.
|
||||||
|
//! - X11 / XWayland: the DMA-BUF renderer can't allocate or import buffers
|
||||||
|
//! ("Failed to create GBM buffer … Invalid argument", "Failed to import
|
||||||
|
//! DMABuf"), so there it is disabled (`WEBKIT_DISABLE_DMABUF_RENDERER=1`,
|
||||||
|
//! shared-memory frames). We never force X11 any more.
|
||||||
//!
|
//!
|
||||||
//! So, before GTK/WebKit initialise, and only when the NVIDIA driver is loaded:
|
//! Opt-ins / opt-outs (anything the user already set always wins):
|
||||||
//! - `WEBKIT_DISABLE_DMABUF_RENDERER=1`;
|
//! - `LOTUS_GPU_SAFE_MODE=1`: last resort, shared-memory rendering on Wayland
|
||||||
//! - on a Wayland session with XWayland available, `GDK_BACKEND=x11`.
|
//! too (slower, especially at high resolutions).
|
||||||
//!
|
//! - `LOTUS_NO_GPU_WORKAROUNDS=1`: change nothing.
|
||||||
//! Anything the user already set wins, and `LOTUS_NO_GPU_WORKAROUNDS=1`
|
|
||||||
//! disables all of it (e.g. once a newer driver/WebKit fixes this).
|
|
||||||
|
|
||||||
/// Environment variables to set: pure, for tests.
|
/// Environment variables to set: pure, for tests.
|
||||||
pub(crate) fn decide(
|
pub(crate) fn decide(
|
||||||
@@ -22,15 +27,30 @@ pub(crate) fn decide(
|
|||||||
if !nvidia || set("LOTUS_NO_GPU_WORKAROUNDS") {
|
if !nvidia || set("LOTUS_NO_GPU_WORKAROUNDS") {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
let mut out = Vec::new();
|
let wayland_session = set("WAYLAND_DISPLAY")
|
||||||
if !set("WEBKIT_DISABLE_DMABUF_RENDERER") {
|
|
||||||
out.push(("WEBKIT_DISABLE_DMABUF_RENDERER", "1"));
|
|
||||||
}
|
|
||||||
let wayland = set("WAYLAND_DISPLAY")
|
|
||||||
|| get("XDG_SESSION_TYPE").is_some_and(|v| v.eq_ignore_ascii_case("wayland"));
|
|| get("XDG_SESSION_TYPE").is_some_and(|v| v.eq_ignore_ascii_case("wayland"));
|
||||||
// Only fall back to X11 when there is an X server (XWayland) to talk to.
|
// GTK uses Wayland unless GDK_BACKEND says otherwise (it may list
|
||||||
if wayland && set("DISPLAY") && !set("GDK_BACKEND") {
|
// several, e.g. "wayland,x11": the first one wins).
|
||||||
out.push(("GDK_BACKEND", "x11"));
|
let native_wayland = wayland_session
|
||||||
|
&& get("GDK_BACKEND").map_or(true, |v| {
|
||||||
|
v.is_empty()
|
||||||
|
|| v.trim_start().to_ascii_lowercase().starts_with("wayland")
|
||||||
|
|| v.trim() == "*"
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut out = Vec::new();
|
||||||
|
let mut want = |k: &'static str, v: &'static str| {
|
||||||
|
if !set(k) {
|
||||||
|
out.push((k, v));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if native_wayland {
|
||||||
|
want("__NV_DISABLE_EXPLICIT_SYNC", "1");
|
||||||
|
if set("LOTUS_GPU_SAFE_MODE") {
|
||||||
|
want("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
want("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
|
||||||
}
|
}
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
@@ -44,9 +64,20 @@ fn nvidia_driver_loaded() -> bool {
|
|||||||
/// Call first thing in `main`, before anything starts GTK or spawns threads.
|
/// Call first thing in `main`, before anything starts GTK or spawns threads.
|
||||||
pub fn apply() {
|
pub fn apply() {
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
for (key, value) in decide(nvidia_driver_loaded(), |k| std::env::var(k).ok()) {
|
{
|
||||||
eprintln!("gpu-workarounds: NVIDIA driver detected, setting {key}={value} (LOTUS_NO_GPU_WORKAROUNDS=1 to disable)");
|
let changes = decide(nvidia_driver_loaded(), |k| std::env::var(k).ok());
|
||||||
std::env::set_var(key, value);
|
if changes.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let list: Vec<String> = changes.iter().map(|(k, v)| format!("{k}={v}")).collect();
|
||||||
|
eprintln!(
|
||||||
|
"gpu-workarounds: NVIDIA driver detected, setting {} \
|
||||||
|
(LOTUS_GPU_SAFE_MODE=1 for shared-memory rendering, LOTUS_NO_GPU_WORKAROUNDS=1 to disable)",
|
||||||
|
list.join(" ")
|
||||||
|
);
|
||||||
|
for (key, value) in changes {
|
||||||
|
std::env::set_var(key, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,51 +102,70 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn nvidia_wayland_gets_both() {
|
fn nvidia_wayland_keeps_gpu_renderer_and_disables_explicit_sync() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
run(true, WAYLAND),
|
run(true, WAYLAND),
|
||||||
vec![
|
vec![("__NV_DISABLE_EXPLICIT_SYNC", "1")]
|
||||||
("WEBKIT_DISABLE_DMABUF_RENDERER", "1"),
|
|
||||||
("GDK_BACKEND", "x11")
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn nvidia_x11_session_only_disables_dmabuf() {
|
fn never_forces_x11() {
|
||||||
assert_eq!(
|
for env in [
|
||||||
run(true, &[("DISPLAY", ":0")]),
|
WAYLAND,
|
||||||
vec![("WEBKIT_DISABLE_DMABUF_RENDERER", "1")]
|
&[("DISPLAY", ":0")][..],
|
||||||
);
|
&[("WAYLAND_DISPLAY", "w")][..],
|
||||||
}
|
] {
|
||||||
|
assert!(run(true, env).iter().all(|(k, _)| *k != "GDK_BACKEND"));
|
||||||
#[test]
|
}
|
||||||
fn wayland_without_xwayland_keeps_wayland() {
|
|
||||||
assert_eq!(
|
|
||||||
run(true, &[("WAYLAND_DISPLAY", "wayland-0")]),
|
|
||||||
vec![("WEBKIT_DISABLE_DMABUF_RENDERER", "1")]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn xdg_session_type_counts_as_wayland() {
|
fn xdg_session_type_counts_as_wayland() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
run(true, &[("XDG_SESSION_TYPE", "wayland"), ("DISPLAY", ":1")]),
|
run(true, &[("XDG_SESSION_TYPE", "wayland")]),
|
||||||
|
vec![("__NV_DISABLE_EXPLICIT_SYNC", "1")]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nvidia_x11_session_disables_dmabuf_renderer() {
|
||||||
|
assert_eq!(
|
||||||
|
run(true, &[("DISPLAY", ":0"), ("XDG_SESSION_TYPE", "x11")]),
|
||||||
|
vec![("WEBKIT_DISABLE_DMABUF_RENDERER", "1")]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn user_forced_x11_on_wayland_counts_as_x11() {
|
||||||
|
let mut env = WAYLAND.to_vec();
|
||||||
|
env.push(("GDK_BACKEND", "x11"));
|
||||||
|
assert_eq!(
|
||||||
|
run(true, &env),
|
||||||
|
vec![("WEBKIT_DISABLE_DMABUF_RENDERER", "1")]
|
||||||
|
);
|
||||||
|
let mut env = WAYLAND.to_vec();
|
||||||
|
env.push(("GDK_BACKEND", "wayland,x11"));
|
||||||
|
assert_eq!(run(true, &env), vec![("__NV_DISABLE_EXPLICIT_SYNC", "1")]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn safe_mode_adds_shared_memory_rendering_on_wayland() {
|
||||||
|
let mut env = WAYLAND.to_vec();
|
||||||
|
env.push(("LOTUS_GPU_SAFE_MODE", "1"));
|
||||||
|
assert_eq!(
|
||||||
|
run(true, &env),
|
||||||
vec![
|
vec![
|
||||||
("WEBKIT_DISABLE_DMABUF_RENDERER", "1"),
|
("__NV_DISABLE_EXPLICIT_SYNC", "1"),
|
||||||
("GDK_BACKEND", "x11")
|
("WEBKIT_DISABLE_DMABUF_RENDERER", "1")
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn user_settings_win() {
|
fn user_settings_win() {
|
||||||
let env = [
|
let mut env = WAYLAND.to_vec();
|
||||||
("WAYLAND_DISPLAY", "wayland-0"),
|
env.push(("__NV_DISABLE_EXPLICIT_SYNC", "0"));
|
||||||
("DISPLAY", ":0"),
|
|
||||||
("WEBKIT_DISABLE_DMABUF_RENDERER", "0"),
|
|
||||||
("GDK_BACKEND", "wayland"),
|
|
||||||
];
|
|
||||||
assert!(run(true, &env).is_empty());
|
assert!(run(true, &env).is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use tauri_plugin_opener::OpenerExt;
|
|||||||
|
|
||||||
pub mod gpu_workarounds;
|
pub mod gpu_workarounds;
|
||||||
mod native;
|
mod native;
|
||||||
|
mod secure_session;
|
||||||
|
|
||||||
/// Bring the main window to the foreground from the tray / a hidden /
|
/// Bring the main window to the foreground from the tray / a hidden /
|
||||||
/// minimized state. Shared by the tray, single-instance, and deep-link paths.
|
/// minimized state. Shared by the tray, single-instance, and deep-link paths.
|
||||||
@@ -952,6 +953,10 @@ pub fn run() {
|
|||||||
native::focus_assist::get_focus_assist,
|
native::focus_assist::get_focus_assist,
|
||||||
native::hotkeys::global_hotkeys_supported,
|
native::hotkeys::global_hotkeys_supported,
|
||||||
native::hotkeys::set_global_hotkeys,
|
native::hotkeys::set_global_hotkeys,
|
||||||
|
secure_session::secure_session_supported,
|
||||||
|
secure_session::secure_session_set,
|
||||||
|
secure_session::secure_session_get,
|
||||||
|
secure_session::secure_session_clear,
|
||||||
])
|
])
|
||||||
.plugin(tauri_plugin_localhost::Builder::new(port).build())
|
.plugin(tauri_plugin_localhost::Builder::new(port).build())
|
||||||
.plugin(
|
.plugin(
|
||||||
|
|||||||
@@ -0,0 +1,181 @@
|
|||||||
|
//! Login tokens in the OS keychain (cinny #105, step 1).
|
||||||
|
//!
|
||||||
|
//! Step 1 only MIRRORS the session tokens into the keychain (Windows
|
||||||
|
//! Credential Manager): the web client keeps reading its session from
|
||||||
|
//! localStorage exactly as before, so nothing about login changes and a
|
||||||
|
//! keychain problem can't log anyone out. Once the mirror has proven itself on
|
||||||
|
//! real installs, step 2 switches reads to the keychain and drops the tokens
|
||||||
|
//! from localStorage.
|
||||||
|
//!
|
||||||
|
//! Only the secrets are stored (user id + device id to match them to the
|
||||||
|
//! session, the access token and the refresh token); the rest of the session
|
||||||
|
//! stays in localStorage. Windows caps a credential at 2560 bytes, so the
|
||||||
|
//! serialized value is limited well below that.
|
||||||
|
//!
|
||||||
|
//! Other platforms: not supported yet (Linux Secret Service can prompt to
|
||||||
|
//! unlock a wallet at startup; that needs its own testing), and the commands
|
||||||
|
//! say so instead of failing.
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[cfg_attr(not(any(target_os = "windows", test)), allow(dead_code))]
|
||||||
|
pub(crate) const SERVICE: &str = "Lotus Chat";
|
||||||
|
#[cfg_attr(not(any(target_os = "windows", test)), allow(dead_code))]
|
||||||
|
pub(crate) const ACCOUNT: &str = "session";
|
||||||
|
/// Serialized-length cap (chars). Windows' limit is 2560 bytes; staying under
|
||||||
|
/// 1200 chars keeps us safe whether the value is stored as UTF-8 or UTF-16.
|
||||||
|
pub(crate) const MAX_LEN: usize = 1200;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct SessionTokens {
|
||||||
|
pub user_id: String,
|
||||||
|
pub device_id: String,
|
||||||
|
pub access_token: String,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub refresh_token: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn store(entry: &keyring::Entry, tokens: &SessionTokens) -> Result<(), String> {
|
||||||
|
if tokens.user_id.is_empty() || tokens.device_id.is_empty() || tokens.access_token.is_empty() {
|
||||||
|
return Err("incomplete session".into());
|
||||||
|
}
|
||||||
|
let value = serde_json::to_string(tokens).map_err(|e| e.to_string())?;
|
||||||
|
if value.chars().count() > MAX_LEN {
|
||||||
|
return Err(format!(
|
||||||
|
"session too large for the keychain ({} chars)",
|
||||||
|
value.chars().count()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
entry.set_password(&value).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn load(entry: &keyring::Entry) -> Result<Option<SessionTokens>, String> {
|
||||||
|
match entry.get_password() {
|
||||||
|
Ok(value) => serde_json::from_str(&value)
|
||||||
|
.map(Some)
|
||||||
|
.map_err(|e| format!("unreadable keychain entry: {e}")),
|
||||||
|
Err(keyring::Error::NoEntry) => Ok(None),
|
||||||
|
Err(e) => Err(e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn clear(entry: &keyring::Entry) -> Result<(), String> {
|
||||||
|
match entry.delete_credential() {
|
||||||
|
Ok(()) | Err(keyring::Error::NoEntry) => Ok(()),
|
||||||
|
Err(e) => Err(e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn entry() -> Result<keyring::Entry, String> {
|
||||||
|
keyring::Entry::new(SERVICE, ACCOUNT).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
fn entry() -> Result<keyring::Entry, String> {
|
||||||
|
Err("not supported on this platform".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Keychain calls can block (credential store, AV scanners): keep them off
|
||||||
|
/// the main thread.
|
||||||
|
async fn blocking<T: Send + 'static>(
|
||||||
|
f: impl FnOnce() -> Result<T, String> + Send + 'static,
|
||||||
|
) -> Result<T, String> {
|
||||||
|
tauri::async_runtime::spawn_blocking(f)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether this platform stores the session in the keychain.
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn secure_session_supported() -> bool {
|
||||||
|
cfg!(target_os = "windows")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn secure_session_set(tokens: SessionTokens) -> Result<(), String> {
|
||||||
|
blocking(move || store(&entry()?, &tokens)).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn secure_session_get() -> Result<Option<SessionTokens>, String> {
|
||||||
|
blocking(|| load(&entry()?)).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn secure_session_clear() -> Result<(), String> {
|
||||||
|
blocking(|| clear(&entry()?)).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn mock_entry() -> keyring::Entry {
|
||||||
|
keyring::set_default_credential_builder(keyring::mock::default_credential_builder());
|
||||||
|
keyring::Entry::new(SERVICE, ACCOUNT).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tokens() -> SessionTokens {
|
||||||
|
SessionTokens {
|
||||||
|
user_id: "@alice:lotusguild.org".into(),
|
||||||
|
device_id: "ABCDEFGHIJ".into(),
|
||||||
|
access_token: "syt_YWxpY2U_abcdefghijklmnopqrst_0AbCdE".into(),
|
||||||
|
refresh_token: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn round_trip_and_clear() {
|
||||||
|
let e = mock_entry();
|
||||||
|
assert_eq!(load(&e).unwrap(), None);
|
||||||
|
store(&e, &tokens()).unwrap();
|
||||||
|
assert_eq!(load(&e).unwrap(), Some(tokens()));
|
||||||
|
let mut oidc = tokens();
|
||||||
|
oidc.refresh_token = Some("mar_refresh_token_value_0123456789".into());
|
||||||
|
store(&e, &oidc).unwrap();
|
||||||
|
assert_eq!(load(&e).unwrap(), Some(oidc));
|
||||||
|
clear(&e).unwrap();
|
||||||
|
assert_eq!(load(&e).unwrap(), None);
|
||||||
|
// Clearing an empty keychain is fine (logout twice, or never mirrored).
|
||||||
|
clear(&e).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_incomplete_or_oversized_sessions() {
|
||||||
|
let e = mock_entry();
|
||||||
|
let mut t = tokens();
|
||||||
|
t.access_token = String::new();
|
||||||
|
assert!(store(&e, &t).is_err());
|
||||||
|
let mut big = tokens();
|
||||||
|
big.access_token = "x".repeat(MAX_LEN);
|
||||||
|
assert!(store(&e, &big).unwrap_err().contains("too large"));
|
||||||
|
assert_eq!(load(&e).unwrap(), None, "nothing written on error");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serialized_shape_matches_the_web_client() {
|
||||||
|
let json = serde_json::to_value(tokens()).unwrap();
|
||||||
|
assert_eq!(json["userId"], "@alice:lotusguild.org");
|
||||||
|
assert_eq!(json["deviceId"], "ABCDEFGHIJ");
|
||||||
|
assert!(json.get("refreshToken").is_none());
|
||||||
|
let back: SessionTokens = serde_json::from_str(
|
||||||
|
r#"{"userId":"@a:b","deviceId":"D","accessToken":"t","refreshToken":"r"}"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(back.refresh_token.as_deref(), Some("r"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_realistic_oidc_session_fits() {
|
||||||
|
let e = mock_entry();
|
||||||
|
let t = SessionTokens {
|
||||||
|
user_id: format!("@{}:matrix.lotusguild.org", "a".repeat(60)),
|
||||||
|
device_id: "X".repeat(40),
|
||||||
|
access_token: "mat_".to_string() + &"A".repeat(200),
|
||||||
|
refresh_token: Some("mar_".to_string() + &"B".repeat(200)),
|
||||||
|
};
|
||||||
|
store(&e, &t).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user