pake-cli 3.15.6 → 3.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli.js +574 -168
- package/llms.txt +15 -2
- package/package.json +3 -4
- package/src-tauri/Cargo.lock +6 -5
- package/src-tauri/Cargo.toml +7 -2
- package/src-tauri/src/app/auth.rs +478 -0
- package/src-tauri/src/app/config.rs +5 -0
- package/src-tauri/src/app/mod.rs +1 -1
- package/src-tauri/src/app/window.rs +93 -32
- package/src-tauri/src/inject/event.js +65 -89
- package/src-tauri/src/inject/find.js +39 -24
- package/src-tauri/src/inject/frame_links.js +62 -0
- package/src-tauri/src/inject/fullscreen.js +9 -4
- package/src-tauri/src/inject/link_policy.js +60 -0
- package/src-tauri/src/inject/style.js +28 -18
- package/src-tauri/src/inject/styles.js +100 -0
- package/src-tauri/tauri.conf.json +1 -1
- package/src-tauri/src/app/cert.rs +0 -187
|
@@ -240,20 +240,18 @@ window.addEventListener("DOMContentLoaded", (_event) => {
|
|
|
240
240
|
position: fixed !important;
|
|
241
241
|
top: 12px !important;
|
|
242
242
|
right: 16px !important;
|
|
243
|
+
left: auto !important;
|
|
244
|
+
width: 182px !important;
|
|
243
245
|
}
|
|
244
246
|
|
|
245
|
-
#react-root [data-testid="sidebarColumn"] input[
|
|
246
|
-
width:
|
|
247
|
+
#react-root [data-testid="sidebarColumn"] form[role="search"] input[data-testid="SearchBox_Search_Input"] {
|
|
248
|
+
width: 100% !important;
|
|
247
249
|
}
|
|
248
250
|
|
|
249
251
|
#react-root [data-testid="sidebarColumn"] form[role="search"]:focus-within {
|
|
250
252
|
width: 280px !important;
|
|
251
253
|
backdrop-filter: blur(12px) !important;
|
|
252
254
|
}
|
|
253
|
-
|
|
254
|
-
#react-root [data-testid="sidebarColumn"] input[placeholder="Search"]:focus {
|
|
255
|
-
width: 234px !important;
|
|
256
|
-
}
|
|
257
255
|
}
|
|
258
256
|
|
|
259
257
|
@media only screen and (min-width: 1265px) {
|
|
@@ -262,10 +260,12 @@ window.addEventListener("DOMContentLoaded", (_event) => {
|
|
|
262
260
|
position: fixed !important;
|
|
263
261
|
top: 12px !important;
|
|
264
262
|
right: 16px !important;
|
|
263
|
+
left: auto !important;
|
|
264
|
+
width: 182px !important;
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
-
#react-root [data-testid="sidebarColumn"] input[
|
|
268
|
-
width:
|
|
267
|
+
#react-root [data-testid="sidebarColumn"] form[role="search"] input[data-testid="SearchBox_Search_Input"] {
|
|
268
|
+
width: 100% !important;
|
|
269
269
|
}
|
|
270
270
|
|
|
271
271
|
#react-root [data-testid="sidebarColumn"] form[role="search"]:focus-within {
|
|
@@ -273,10 +273,6 @@ window.addEventListener("DOMContentLoaded", (_event) => {
|
|
|
273
273
|
backdrop-filter: blur(12px) !important;
|
|
274
274
|
}
|
|
275
275
|
|
|
276
|
-
#react-root [data-testid="sidebarColumn"] input[placeholder="Search"]:focus {
|
|
277
|
-
width: 328px !important;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
276
|
#react-root div[style*="left: -12px"] {
|
|
281
277
|
left: unset !important;
|
|
282
278
|
}
|
|
@@ -324,9 +320,14 @@ window.addEventListener("DOMContentLoaded", (_event) => {
|
|
|
324
320
|
padding-top: 36px;
|
|
325
321
|
}
|
|
326
322
|
`;
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
323
|
+
if (typeof window.__PAKE_INJECT_STYLE__ === "function") {
|
|
324
|
+
window.__PAKE_INJECT_STYLE__(contentCSS, "pake-content-style");
|
|
325
|
+
} else {
|
|
326
|
+
const contentStyleElement = document.createElement("style");
|
|
327
|
+
contentStyleElement.id = "pake-content-style";
|
|
328
|
+
contentStyleElement.textContent = contentCSS;
|
|
329
|
+
document.head.appendChild(contentStyleElement);
|
|
330
|
+
}
|
|
330
331
|
|
|
331
332
|
// Top spacing adapts to head-hiding scenarios
|
|
332
333
|
const topPaddingCSS = `
|
|
@@ -503,8 +504,7 @@ window.addEventListener("DOMContentLoaded", (_event) => {
|
|
|
503
504
|
`;
|
|
504
505
|
const isMac = /Mac/i.test(navigator.userAgent);
|
|
505
506
|
if (hasImmersiveHeader(window["pakeConfig"])) {
|
|
506
|
-
const
|
|
507
|
-
topPaddingStyleElement.textContent = isMac
|
|
507
|
+
const topPaddingCSSForPlatform = isMac
|
|
508
508
|
? topPaddingCSS
|
|
509
509
|
: `
|
|
510
510
|
#pake-top-dom:active {
|
|
@@ -525,6 +525,16 @@ window.addEventListener("DOMContentLoaded", (_event) => {
|
|
|
525
525
|
z-index: 99999;
|
|
526
526
|
}
|
|
527
527
|
`;
|
|
528
|
-
|
|
528
|
+
if (typeof window.__PAKE_INJECT_STYLE__ === "function") {
|
|
529
|
+
window.__PAKE_INJECT_STYLE__(
|
|
530
|
+
topPaddingCSSForPlatform,
|
|
531
|
+
"pake-top-padding-style",
|
|
532
|
+
);
|
|
533
|
+
} else {
|
|
534
|
+
const topPaddingStyleElement = document.createElement("style");
|
|
535
|
+
topPaddingStyleElement.id = "pake-top-padding-style";
|
|
536
|
+
topPaddingStyleElement.textContent = topPaddingCSSForPlatform;
|
|
537
|
+
document.head.appendChild(topPaddingStyleElement);
|
|
538
|
+
}
|
|
529
539
|
}
|
|
530
540
|
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
const INJECT_STYLE_KEY = "__PAKE_INJECT_STYLE__";
|
|
3
|
+
const adoptedSheetsById = new Map();
|
|
4
|
+
|
|
5
|
+
if (typeof window[INJECT_STYLE_KEY] === "function") {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function containsImport(css) {
|
|
10
|
+
// Skip comments, strings and escaped delimiters; decode at-keyword escapes. Import
|
|
11
|
+
// rules are case-insensitive and need not begin a line. replaceSync silently
|
|
12
|
+
// drops them, so a blocked sheet containing imports must keep its DOM path.
|
|
13
|
+
const tokens =
|
|
14
|
+
/\/\*[\s\S]*?(?:\*\/|$)|"(?:\\[\s\S]|[^"\\])*"|'(?:\\[\s\S]|[^'\\])*'|\\(?:[0-9a-f]{1,6}[\t\n\f\r ]?|[^\n\r\f])|@((?:[-\w\u0080-\uffff]|\\(?:[0-9a-f]{1,6}[\t\n\f\r ]?|[^\n\r\f]))+)/gi;
|
|
15
|
+
for (const token of css.matchAll(tokens)) {
|
|
16
|
+
if (!token[1]) continue;
|
|
17
|
+
const keyword = token[1].replace(
|
|
18
|
+
/\\([0-9a-f]{1,6})[\t\n\f\r ]?|\\([^\n\r\f])/gi,
|
|
19
|
+
(_escape, hex, character) => {
|
|
20
|
+
const code = hex ? parseInt(hex, 16) : 0;
|
|
21
|
+
return hex
|
|
22
|
+
? String.fromCodePoint(code > 0 && code <= 0x10ffff ? code : 0xfffd)
|
|
23
|
+
: character;
|
|
24
|
+
},
|
|
25
|
+
);
|
|
26
|
+
if (keyword.toLowerCase() === "import") return true;
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function injectWithAdoptedStyleSheet(css, id) {
|
|
32
|
+
try {
|
|
33
|
+
if (
|
|
34
|
+
typeof window.CSSStyleSheet !== "function" ||
|
|
35
|
+
!("adoptedStyleSheets" in document) ||
|
|
36
|
+
containsImport(css)
|
|
37
|
+
) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const currentSheets = document.adoptedStyleSheets;
|
|
42
|
+
const sheet = new window.CSSStyleSheet();
|
|
43
|
+
sheet.replaceSync(css);
|
|
44
|
+
document.adoptedStyleSheets = Array.from(currentSheets).concat(sheet);
|
|
45
|
+
|
|
46
|
+
if (!Array.from(document.adoptedStyleSheets).includes(sheet)) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (id) {
|
|
51
|
+
adoptedSheetsById.set(id, sheet);
|
|
52
|
+
}
|
|
53
|
+
return sheet;
|
|
54
|
+
} catch (_error) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
window[INJECT_STYLE_KEY] = function (css, id) {
|
|
60
|
+
if (id) {
|
|
61
|
+
const existingElement = document.getElementById(id);
|
|
62
|
+
if (existingElement) {
|
|
63
|
+
return existingElement;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const existingSheet = adoptedSheetsById.get(id);
|
|
67
|
+
if (existingSheet) {
|
|
68
|
+
try {
|
|
69
|
+
if (Array.from(document.adoptedStyleSheets).includes(existingSheet)) {
|
|
70
|
+
return existingSheet;
|
|
71
|
+
}
|
|
72
|
+
} catch (_error) {
|
|
73
|
+
// Recreate the sheet when the document's adopted-sheet list is unavailable.
|
|
74
|
+
}
|
|
75
|
+
adoptedSheetsById.delete(id);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const style = document.createElement("style");
|
|
80
|
+
if (id) {
|
|
81
|
+
style.id = id;
|
|
82
|
+
}
|
|
83
|
+
style.textContent = css;
|
|
84
|
+
(document.head || document.body || document.documentElement)?.appendChild(
|
|
85
|
+
style,
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
// Preserve normal DOM cascade order, including custom CSS with imports.
|
|
89
|
+
// CSP-blocked style elements have no associated sheet. Only those need the
|
|
90
|
+
// constructable-sheet fallback; imports remain subject to the page's CSP.
|
|
91
|
+
if (!style.sheet) {
|
|
92
|
+
const sheet = injectWithAdoptedStyleSheet(css, id);
|
|
93
|
+
if (sheet) {
|
|
94
|
+
style.remove();
|
|
95
|
+
return sheet;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return style;
|
|
99
|
+
};
|
|
100
|
+
})();
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
// macOS TLS certificate bypass for `--ignore-certificate-errors`.
|
|
2
|
-
//
|
|
3
|
-
// On macOS the webview is a WKWebView, which ignores the Chromium
|
|
4
|
-
// `--ignore-certificate-errors` flag entirely, so Pake's flag was a no-op here
|
|
5
|
-
// (see the Windows/Linux vs macOS split in window.rs). wry's own
|
|
6
|
-
// `WryNavigationDelegate` does not implement
|
|
7
|
-
// `webView:didReceiveAuthenticationChallenge:completionHandler:`, so WKWebView
|
|
8
|
-
// falls back to default validation and rejects self-signed certs.
|
|
9
|
-
//
|
|
10
|
-
// This installs a thin navigation-delegate proxy that accepts server trust only
|
|
11
|
-
// for the configured target host and forwards every other selector to wry's
|
|
12
|
-
// original delegate, so navigation policy, downloads, and page-load callbacks
|
|
13
|
-
// keep working untouched. It is installed only when the user opts in via
|
|
14
|
-
// `--ignore-certificate-errors`.
|
|
15
|
-
|
|
16
|
-
use objc2::rc::{Retained, Weak};
|
|
17
|
-
use objc2::runtime::{AnyObject, Bool, NSObject, NSObjectProtocol, Sel};
|
|
18
|
-
use objc2::{class, define_class, msg_send, DefinedClass, MainThreadMarker, MainThreadOnly};
|
|
19
|
-
use objc2_foundation::NSString;
|
|
20
|
-
use std::ffi::c_void;
|
|
21
|
-
|
|
22
|
-
// NSURLSessionAuthChallengeDisposition values.
|
|
23
|
-
const USE_CREDENTIAL: isize = 0;
|
|
24
|
-
const PERFORM_DEFAULT_HANDLING: isize = 1;
|
|
25
|
-
|
|
26
|
-
pub struct PakeCertDelegateIvars {
|
|
27
|
-
// wry's real navigation delegate; every non-challenge selector forwards here.
|
|
28
|
-
inner: Weak<AnyObject>,
|
|
29
|
-
allowed_host: String,
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
static CERT_BYPASS_ASSOCIATION_KEY: u8 = 0;
|
|
33
|
-
|
|
34
|
-
fn hosts_match(allowed_host: &str, challenge_host: &str) -> bool {
|
|
35
|
-
allowed_host
|
|
36
|
-
.trim_end_matches('.')
|
|
37
|
-
.eq_ignore_ascii_case(challenge_host.trim_end_matches('.'))
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
define_class!(
|
|
41
|
-
#[unsafe(super(NSObject))]
|
|
42
|
-
#[name = "PakeCertBypassDelegate"]
|
|
43
|
-
#[thread_kind = MainThreadOnly]
|
|
44
|
-
#[ivars = PakeCertDelegateIvars]
|
|
45
|
-
struct PakeCertBypassDelegate;
|
|
46
|
-
|
|
47
|
-
unsafe impl NSObjectProtocol for PakeCertBypassDelegate {}
|
|
48
|
-
|
|
49
|
-
impl PakeCertBypassDelegate {
|
|
50
|
-
#[unsafe(method(webView:didReceiveAuthenticationChallenge:completionHandler:))]
|
|
51
|
-
fn did_receive_challenge(
|
|
52
|
-
&self,
|
|
53
|
-
_webview: &AnyObject,
|
|
54
|
-
challenge: &AnyObject,
|
|
55
|
-
handler: &block2::Block<dyn Fn(isize, *mut AnyObject)>,
|
|
56
|
-
) {
|
|
57
|
-
unsafe {
|
|
58
|
-
let space: *mut AnyObject = msg_send![challenge, protectionSpace];
|
|
59
|
-
if space.is_null() {
|
|
60
|
-
handler.call((PERFORM_DEFAULT_HANDLING, core::ptr::null_mut()));
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
let host: *mut NSString = msg_send![space, host];
|
|
64
|
-
if host.is_null()
|
|
65
|
-
|| !hosts_match(&self.ivars().allowed_host, &(&*host).to_string())
|
|
66
|
-
{
|
|
67
|
-
handler.call((PERFORM_DEFAULT_HANDLING, core::ptr::null_mut()));
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
// Only server-trust challenges carry a non-null serverTrust; for
|
|
71
|
-
// anything else (e.g. HTTP basic auth) defer to default handling.
|
|
72
|
-
let server_trust: *mut AnyObject = msg_send![space, serverTrust];
|
|
73
|
-
if server_trust.is_null() {
|
|
74
|
-
handler.call((PERFORM_DEFAULT_HANDLING, core::ptr::null_mut()));
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
let credential: *mut AnyObject =
|
|
78
|
-
msg_send![class!(NSURLCredential), credentialForTrust: server_trust];
|
|
79
|
-
handler.call((USE_CREDENTIAL, credential));
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// WKWebView probes respondsToSelector: before calling optional delegate
|
|
84
|
-
// methods, so report our own methods plus everything wry implements.
|
|
85
|
-
#[unsafe(method(respondsToSelector:))]
|
|
86
|
-
fn responds_to_selector(&self, selector: Sel) -> Bool {
|
|
87
|
-
let responds: Bool = unsafe { msg_send![super(self), respondsToSelector: selector] };
|
|
88
|
-
if responds.as_bool() {
|
|
89
|
-
return Bool::YES;
|
|
90
|
-
}
|
|
91
|
-
self.ivars()
|
|
92
|
-
.inner
|
|
93
|
-
.load()
|
|
94
|
-
.map(|inner| unsafe { msg_send![&*inner, respondsToSelector: selector] })
|
|
95
|
-
.unwrap_or(Bool::NO)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Fast-forward any selector we do not implement to wry's delegate.
|
|
99
|
-
#[unsafe(method(forwardingTargetForSelector:))]
|
|
100
|
-
fn forwarding_target(&self, _selector: Sel) -> *mut AnyObject {
|
|
101
|
-
self.ivars()
|
|
102
|
-
.inner
|
|
103
|
-
.load()
|
|
104
|
-
.map(|inner| Retained::as_ptr(&inner) as *mut AnyObject)
|
|
105
|
-
.unwrap_or(core::ptr::null_mut())
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
impl PakeCertBypassDelegate {
|
|
111
|
-
fn new(
|
|
112
|
-
inner: &Retained<AnyObject>,
|
|
113
|
-
allowed_host: String,
|
|
114
|
-
mtm: MainThreadMarker,
|
|
115
|
-
) -> Retained<Self> {
|
|
116
|
-
let this = mtm
|
|
117
|
-
.alloc::<PakeCertBypassDelegate>()
|
|
118
|
-
.set_ivars(PakeCertDelegateIvars {
|
|
119
|
-
inner: Weak::from_retained(inner),
|
|
120
|
-
allowed_host,
|
|
121
|
-
});
|
|
122
|
-
unsafe { msg_send![super(this), init] }
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/// Replace the WKWebView's navigation delegate with a proxy that accepts
|
|
127
|
-
/// invalid TLS certificates for the configured target host, then navigate to
|
|
128
|
-
/// that target. `webview_ptr` is the raw `WKWebView` from
|
|
129
|
-
/// `PlatformWebview::inner()`. Returns false if setup cannot be completed.
|
|
130
|
-
pub fn install_cert_bypass_and_navigate(
|
|
131
|
-
webview_ptr: *mut c_void,
|
|
132
|
-
allowed_host: String,
|
|
133
|
-
target_url: String,
|
|
134
|
-
) -> bool {
|
|
135
|
-
if webview_ptr.is_null() || allowed_host.is_empty() || target_url.is_empty() {
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
let Some(mtm) = MainThreadMarker::new() else {
|
|
139
|
-
return false;
|
|
140
|
-
};
|
|
141
|
-
unsafe {
|
|
142
|
-
let webview: &AnyObject = &*(webview_ptr as *const AnyObject);
|
|
143
|
-
let existing: *mut AnyObject = msg_send![webview, navigationDelegate];
|
|
144
|
-
if existing.is_null() {
|
|
145
|
-
return false;
|
|
146
|
-
}
|
|
147
|
-
let Some(inner) = Retained::retain(existing) else {
|
|
148
|
-
return false;
|
|
149
|
-
};
|
|
150
|
-
let proxy = PakeCertBypassDelegate::new(&inner, allowed_host, mtm);
|
|
151
|
-
objc2::ffi::objc_setAssociatedObject(
|
|
152
|
-
webview as *const AnyObject as *mut AnyObject,
|
|
153
|
-
std::ptr::addr_of!(CERT_BYPASS_ASSOCIATION_KEY).cast(),
|
|
154
|
-
Retained::as_ptr(&proxy) as *mut AnyObject,
|
|
155
|
-
objc2::ffi::OBJC_ASSOCIATION_RETAIN_NONATOMIC,
|
|
156
|
-
);
|
|
157
|
-
let _: () = msg_send![webview, setNavigationDelegate: &*proxy];
|
|
158
|
-
|
|
159
|
-
let target_url = NSString::from_str(&target_url);
|
|
160
|
-
let ns_url: *mut AnyObject = msg_send![class!(NSURL), URLWithString: &*target_url];
|
|
161
|
-
if ns_url.is_null() {
|
|
162
|
-
return false;
|
|
163
|
-
}
|
|
164
|
-
let request: *mut AnyObject = msg_send![class!(NSURLRequest), requestWithURL: ns_url];
|
|
165
|
-
if request.is_null() {
|
|
166
|
-
return false;
|
|
167
|
-
}
|
|
168
|
-
let _: () = msg_send![webview, loadRequest: request];
|
|
169
|
-
true
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
#[cfg(test)]
|
|
174
|
-
mod tests {
|
|
175
|
-
use super::hosts_match;
|
|
176
|
-
|
|
177
|
-
#[test]
|
|
178
|
-
fn accepts_the_configured_host_case_insensitively() {
|
|
179
|
-
assert!(hosts_match("INTERNAL.EXAMPLE.COM", "internal.example.com"));
|
|
180
|
-
assert!(hosts_match("internal.example.com.", "internal.example.com"));
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
#[test]
|
|
184
|
-
fn rejects_other_hosts() {
|
|
185
|
-
assert!(!hosts_match("internal.example.com", "login.example.com"));
|
|
186
|
-
}
|
|
187
|
-
}
|