Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c3ac68328 |
+2
-1
@@ -24,5 +24,6 @@
|
||||
"basename": "/"
|
||||
},
|
||||
"gifApiKey": "",
|
||||
"webAppUrl": "https://chat.lotusguild.org"
|
||||
"webAppUrl": "https://chat.lotusguild.org",
|
||||
"desktopCallOrigin": "http://127.0.0.1:44548"
|
||||
}
|
||||
|
||||
+11
-1
@@ -955,7 +955,17 @@ pub fn run() {
|
||||
native::hotkeys::global_hotkeys_supported,
|
||||
native::hotkeys::set_global_hotkeys,
|
||||
])
|
||||
.plugin(tauri_plugin_localhost::Builder::new(port).build())
|
||||
// Bound to 127.0.0.1 explicitly (cinny #43). The app is still loaded as
|
||||
// http://localhost:{port} (its storage lives under that origin, and the
|
||||
// engines try 127.0.0.1 for `localhost`); the bundled call page is
|
||||
// loaded as http://127.0.0.1:{port}, a separate origin on the same
|
||||
// server. Binding the name `localhost` could pick ::1 only (Windows
|
||||
// lists it first), and then the call page wouldn't load.
|
||||
.plugin(
|
||||
tauri_plugin_localhost::Builder::new(port)
|
||||
.host("127.0.0.1")
|
||||
.build(),
|
||||
)
|
||||
.plugin(
|
||||
// DECORATIONS is excluded: the custom-chrome toggle (set_custom_chrome)
|
||||
// owns the decorated flag. Letting window-state restore a saved
|
||||
|
||||
@@ -12,8 +12,13 @@
|
||||
//! - WebKitGTK doesn't say which frame asked; the check is on the page loaded
|
||||
//! in the window. Frames are gated before the request gets this far by the
|
||||
//! Permissions Policy: cinny only puts `microphone; camera` in the `allow=`
|
||||
//! of the call frame (same origin on desktop), and cross-origin frames get
|
||||
//! neither location nor notifications.
|
||||
//! of the call frame, and cross-origin frames get neither location nor
|
||||
//! notifications.
|
||||
//!
|
||||
//! The call frame (cinny #43): the bundled Element Call page is loaded from
|
||||
//! `http://127.0.0.1:{port}`, the same local server on a second origin, so it
|
||||
//! can't reach the app's storage. WebView2 reports that origin for the call's
|
||||
//! microphone/camera requests; it gets media and nothing else.
|
||||
|
||||
use tauri::Url;
|
||||
|
||||
@@ -57,6 +62,14 @@ pub(crate) fn decide(kind: Kind, uri: &str, app: &AppOrigins, grants: &[Kind]) -
|
||||
if kind == Kind::Other {
|
||||
return Decision::Default;
|
||||
}
|
||||
if app.is_call_frame(uri) {
|
||||
// The call page: microphone/camera/screen only.
|
||||
return if kind == Kind::Media && grants.contains(&kind) {
|
||||
Decision::Allow
|
||||
} else {
|
||||
Decision::Deny
|
||||
};
|
||||
}
|
||||
if !app.contains(uri) {
|
||||
return Decision::Deny;
|
||||
}
|
||||
@@ -76,9 +89,12 @@ fn origin_of(uri: &str) -> Option<Origin> {
|
||||
Some((url.scheme().to_owned(), host, url.port_or_known_default()))
|
||||
}
|
||||
|
||||
/// The origins the app's own page is served from.
|
||||
/// The origins the app's own page is served from, and the call page's.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct AppOrigins(Vec<Origin>);
|
||||
pub(crate) struct AppOrigins {
|
||||
app: Vec<Origin>,
|
||||
call: Option<Origin>,
|
||||
}
|
||||
|
||||
impl AppOrigins {
|
||||
/// Release builds load `http://localhost:{port}` (tauri-plugin-localhost).
|
||||
@@ -93,11 +109,20 @@ impl AppOrigins {
|
||||
uris.push(dev.to_string());
|
||||
}
|
||||
}
|
||||
Self(uris.iter().filter_map(|u| origin_of(u)).collect())
|
||||
Self {
|
||||
app: uris.iter().filter_map(|u| origin_of(u)).collect(),
|
||||
call: origin_of(&format!("http://127.0.0.1:{port}/")),
|
||||
}
|
||||
}
|
||||
|
||||
/// The app's own page.
|
||||
pub(crate) fn contains(&self, uri: &str) -> bool {
|
||||
origin_of(uri).is_some_and(|o| self.0.contains(&o))
|
||||
origin_of(uri).is_some_and(|o| self.app.contains(&o))
|
||||
}
|
||||
|
||||
/// The call page on its own origin (`http://127.0.0.1:{port}`).
|
||||
pub(crate) fn is_call_frame(&self, uri: &str) -> bool {
|
||||
origin_of(uri).is_some_and(|o| self.call.as_ref() == Some(&o))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,6 +229,33 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_frame_gets_media_only() {
|
||||
let app = app();
|
||||
let call = "http://127.0.0.1:44548/public/element-call/index.html?widgetId=x";
|
||||
assert!(app.is_call_frame(call));
|
||||
assert!(!app.contains(call));
|
||||
for grants in [LINUX_GRANTS, WINDOWS_GRANTS] {
|
||||
assert_eq!(decide(Kind::Media, call, &app, grants), Decision::Allow);
|
||||
for kind in [Kind::DeviceInfo, Kind::Notifications, Kind::Geolocation] {
|
||||
assert_eq!(decide(kind, call, &app, grants), Decision::Deny, "{kind:?}");
|
||||
}
|
||||
assert_eq!(decide(Kind::Other, call, &app, grants), Decision::Default);
|
||||
}
|
||||
for not_call in [
|
||||
"http://127.0.0.1:44549/",
|
||||
"https://127.0.0.1:44548/",
|
||||
"http://127.0.0.2:44548/",
|
||||
"http://[::1]:44548/",
|
||||
] {
|
||||
assert!(!app.is_call_frame(not_call), "{not_call}");
|
||||
assert_eq!(
|
||||
decide(Kind::Media, not_call, &app, WINDOWS_GRANTS),
|
||||
Decision::Deny
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn other_kinds_are_left_to_the_engine() {
|
||||
let app = app();
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
},
|
||||
"app": {
|
||||
"security": {
|
||||
"csp": "default-src 'self'; script-src 'self' 'unsafe-eval' 'sha256-dT6noyex1I8o5CS9Sx/y8UOqwpZYIridpGz92gcObIM='; style-src 'self' 'unsafe-inline'; font-src 'self' data:; img-src 'self' data: blob: http: https:; media-src 'self' blob: data: mediastream: http: https:; worker-src 'self' blob:; frame-src 'self' blob: https://www.openstreetmap.org https://www.youtube-nocookie.com https://www.youtube.com https://player.vimeo.com https://www.tiktok.com https://www.dailymotion.com https://geo.dailymotion.com https://streamable.com https://player.twitch.tv https://clips.twitch.tv https://open.spotify.com https://w.soundcloud.com https://embed.music.apple.com https://platform.twitter.com https://www.instagram.com https://embed.tidal.com https://www.redditmedia.com https://embed.reddit.com https://embed.bsky.app https://www.loom.com https://player.kick.com https://www.mixcloud.com https://widget.deezer.com https://store.steampowered.com; connect-src 'self' blob: data: ipc: ws: wss: http: https: http://ipc.localhost; object-src 'none'; base-uri 'self'"
|
||||
"csp": "default-src 'self'; script-src 'self' 'unsafe-eval' 'sha256-dT6noyex1I8o5CS9Sx/y8UOqwpZYIridpGz92gcObIM='; style-src 'self' 'unsafe-inline'; font-src 'self' data:; img-src 'self' data: blob: http: https:; media-src 'self' blob: data: mediastream: http: https:; worker-src 'self' blob:; frame-src 'self' blob: http://127.0.0.1:44548 https://www.openstreetmap.org https://www.youtube-nocookie.com https://www.youtube.com https://player.vimeo.com https://www.tiktok.com https://www.dailymotion.com https://geo.dailymotion.com https://streamable.com https://player.twitch.tv https://clips.twitch.tv https://open.spotify.com https://w.soundcloud.com https://embed.music.apple.com https://platform.twitter.com https://www.instagram.com https://embed.tidal.com https://www.redditmedia.com https://embed.reddit.com https://embed.bsky.app https://www.loom.com https://player.kick.com https://www.mixcloud.com https://widget.deezer.com https://store.steampowered.com; connect-src 'self' blob: data: ipc: ws: wss: http: https: http://ipc.localhost; object-src 'none'; base-uri 'self'"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user