mirror of
https://github.com/BetterCorp/BetterFrame.git
synced 2026-05-26 17:56:34 +00:00
fix(webview): set kiosk auth cookie for sub-resource requests
WebView "URL can't be shown" — Authorization header only applies to the initial page load. CSS/JS/XHR/WebSocket sub-resources from the loaded page don't inherit it → Angie auth_request rejects → page breaks. Kiosk side: set_kiosk_cookie() injects betterframe_kiosk_key cookie into WebKit's cookie jar via JS bridge before loading the URL. Cookie persists across all sub-resource requests automatically. Server side: extractBearerToken() now checks betterframe_kiosk_key cookie as fallback when no Authorization header present. Same verifyKioskKey path, just different transport.
This commit is contained in:
parent
a513d165dc
commit
592bdad10b
2 changed files with 59 additions and 2 deletions
|
|
@ -1522,6 +1522,14 @@ fn expire_cooling_pipelines() {
|
||||||
|
|
||||||
fn load_webview_url(webview: &webkit6::WebView, url: &str, server_url: &str, kiosk_key: &str) {
|
fn load_webview_url(webview: &webkit6::WebView, url: &str, server_url: &str, kiosk_key: &str) {
|
||||||
if should_attach_kiosk_auth(url, server_url) {
|
if should_attach_kiosk_auth(url, server_url) {
|
||||||
|
// Set a cookie so ALL sub-resource requests (JS, CSS, XHR, WS)
|
||||||
|
// carry auth automatically. The Authorization header only applies
|
||||||
|
// to the initial request — sub-resources from the loaded page
|
||||||
|
// don't inherit it, causing 401 on every CSS/JS/API fetch.
|
||||||
|
set_kiosk_cookie(webview, server_url, kiosk_key);
|
||||||
|
|
||||||
|
// Also set the header on the initial request for the page load
|
||||||
|
// itself (belt + suspenders — server checks cookie OR header).
|
||||||
let request = webkit6::URIRequest::new(url);
|
let request = webkit6::URIRequest::new(url);
|
||||||
if let Some(headers) = request.http_headers() {
|
if let Some(headers) = request.http_headers() {
|
||||||
headers.append("Authorization", &format!("Bearer {kiosk_key}"));
|
headers.append("Authorization", &format!("Bearer {kiosk_key}"));
|
||||||
|
|
@ -1533,6 +1541,45 @@ fn load_webview_url(webview: &webkit6::WebView, url: &str, server_url: &str, kio
|
||||||
webkit6::prelude::WebViewExt::load_uri(webview, url);
|
webkit6::prelude::WebViewExt::load_uri(webview, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set a cookie in WebKit's cookie jar so all requests to the server
|
||||||
|
/// carry the kiosk auth token. Name matches what the server's auth_request
|
||||||
|
/// endpoint checks: `betterframe_kiosk_key`.
|
||||||
|
fn set_kiosk_cookie(webview: &webkit6::WebView, server_url: &str, kiosk_key: &str) {
|
||||||
|
use webkit6::prelude::*;
|
||||||
|
|
||||||
|
let Ok(server) = url::Url::parse(server_url) else { return };
|
||||||
|
let domain = server.host_str().unwrap_or("localhost");
|
||||||
|
let secure = server.scheme() == "https";
|
||||||
|
|
||||||
|
// WebKit's CookieManager handles the cookie jar.
|
||||||
|
let ctx = webview.network_session();
|
||||||
|
let Some(ctx) = ctx else { return };
|
||||||
|
let cm = ctx.cookie_manager();
|
||||||
|
let Some(cm) = cm else { return };
|
||||||
|
|
||||||
|
// Build a SoupCookie and add it.
|
||||||
|
// soup3 crate provides Cookie API used by webkit6.
|
||||||
|
let cookie = webkit6::glib::GString::from(format!(
|
||||||
|
"betterframe_kiosk_key={key}; Domain={domain}; Path=/; {secure}HttpOnly; SameSite=Strict",
|
||||||
|
key = kiosk_key,
|
||||||
|
domain = domain,
|
||||||
|
secure = if secure { "Secure; " } else { "" },
|
||||||
|
));
|
||||||
|
|
||||||
|
// Use the JavaScript bridge to set the cookie since the Rust
|
||||||
|
// CookieManager API varies by webkit6 binding version.
|
||||||
|
let js = format!(
|
||||||
|
"document.cookie = 'betterframe_kiosk_key={key}; path=/; {secure}SameSite=Strict';",
|
||||||
|
key = kiosk_key,
|
||||||
|
secure = if secure { "Secure; " } else { "" },
|
||||||
|
);
|
||||||
|
// Run JS after a tiny delay so the WebView context exists.
|
||||||
|
let wv = webview.clone();
|
||||||
|
gtk::glib::idle_add_local_once(move || {
|
||||||
|
wv.evaluate_javascript(&js, None, None, None::<>k::gio::Cancellable>, |_| {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn should_attach_kiosk_auth(url: &str, server_url: &str) -> bool {
|
fn should_attach_kiosk_auth(url: &str, server_url: &str) -> bool {
|
||||||
let Ok(target) = Url::parse(url) else {
|
let Ok(target) = Url::parse(url) else {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -181,8 +181,18 @@ export class Plugin extends BSBService<InstanceType<typeof Config>, typeof Event
|
||||||
|
|
||||||
function extractBearerToken(event: any): string | null {
|
function extractBearerToken(event: any): string | null {
|
||||||
const hdr = getRequestHeader(event, "authorization");
|
const hdr = getRequestHeader(event, "authorization");
|
||||||
if (!hdr?.startsWith("Bearer ")) return null;
|
if (hdr?.startsWith("Bearer ")) return hdr.slice(7);
|
||||||
return hdr.slice(7);
|
// Fallback: check betterframe_kiosk_key cookie (WebView sub-resource
|
||||||
|
// requests don't carry the Authorization header — only cookies persist).
|
||||||
|
const cookieHeader = getRequestHeader(event, "cookie") ?? "";
|
||||||
|
for (const pair of cookieHeader.split(";")) {
|
||||||
|
const [k, ...rest] = pair.trim().split("=");
|
||||||
|
if (k?.trim() === "betterframe_kiosk_key") {
|
||||||
|
const val = rest.join("=").trim();
|
||||||
|
if (val) return val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getClusterKey(repo: Repository, secrets: SecretsApi): string | undefined {
|
function getClusterKey(repo: Repository, secrets: SecretsApi): string | undefined {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue