feat: support macos

This commit is contained in:
Kevin Yue 2025-01-04 09:41:32 +08:00
parent bc48a2f12e
commit 03d0d3e08f
No known key found for this signature in database
GPG Key ID: 4D3A6EE977B15AC4
4 changed files with 29 additions and 22 deletions

View File

@ -1,5 +1,5 @@
mod auth_messenger;
mod auth_response;
mod response_reader;
mod webview_auth;
#[cfg_attr(not(target_os = "macos"), path = "webview/unix.rs")]

View File

@ -8,32 +8,29 @@ use objc2::{
use objc2_foundation::{NSError, NSHTTPURLResponse, NSString};
use wry::WebViewExtMacOS;
use super::{auth_messenger::AuthError, navigation_delegate::NavigationDelegate};
use super::{auth_messenger::AuthError, navigation_delegate::NavigationDelegate, response_reader::ResponseReader};
pub struct AuthResponse {
response: Option<Retained<NSHTTPURLResponse>>,
body: Option<String>,
}
impl AuthResponse {
pub fn url(&self) -> Option<String> {
impl ResponseReader for AuthResponse {
fn url(&self) -> Option<String> {
let response = self.response.as_ref()?;
let url = unsafe { response.URL().and_then(|url| url.absoluteString()) };
url.map(|u| u.to_string())
}
pub fn get_header(&self, key: &str) -> Option<String> {
fn get_header(&self, key: &str) -> Option<String> {
let response = self.response.as_ref()?;
let value = unsafe { response.valueForHTTPHeaderField(&NSString::from_str(key)) };
value.map(|v| v.to_string())
}
pub fn get_body<F>(&self, cb: F)
where
F: FnOnce(anyhow::Result<Option<Cow<'_, str>>>) + 'static,
{
fn get_body(&self, cb: Box<dyn FnOnce(anyhow::Result<Option<Cow<'_, str>>>) + 'static>) {
if let Some(body) = self.body.as_deref() {
cb(Ok(Some(Cow::Borrowed(body))));
} else {
@ -84,7 +81,7 @@ where
let proto_delegate = ProtocolObject::from_ref(delegate.as_ref());
unsafe {
wv.setNavigationDelegate(Some(proto_delegate));
// The UI will freeze if we don't call this method
// The UI will freeze if we don't call this method, but it's not clear why.
let _ = wv.navigationDelegate();
};
}

View File

@ -1,3 +1,5 @@
use std::borrow::Cow;
use gpapi::{
auth::{AuthDataParseResult, SamlAuthData},
error::AuthDataParseError,
@ -7,17 +9,25 @@ use regex::Regex;
use crate::webview::auth_messenger::AuthError;
use super::{auth_messenger::AuthResult, platform_impl::AuthResponse};
use super::auth_messenger::AuthResult;
fn is_acs_endpoint(auth_response: &AuthResponse) -> bool {
pub trait ResponseReader {
fn url(&self) -> Option<String>;
fn get_header(&self, key: &str) -> Option<String>;
fn get_body(&self, cb: Box<dyn FnOnce(anyhow::Result<Option<Cow<'_, str>>>) + 'static>);
}
fn is_acs_endpoint(auth_response: &impl ResponseReader) -> bool {
auth_response.url().map_or(false, |url| url.ends_with("/SAML20/SP/ACS"))
}
pub fn read_auth_data<F>(auth_response: AuthResponse, cb: F)
pub fn read_auth_data<F>(auth_response: &impl ResponseReader, cb: F)
where
F: Fn(AuthResult) + 'static,
{
match read_from_headers(&auth_response) {
match read_from_headers(auth_response) {
Ok(auth_data) => {
info!("Found auth data in headers");
cb(Ok(auth_data))
@ -26,8 +36,8 @@ where
Err(header_err) => {
info!("Failed to read auth data from headers: {}", header_err);
let is_acs_endpoint = is_acs_endpoint(&auth_response);
read_from_body(&auth_response, move |auth_result| {
let is_acs_endpoint = is_acs_endpoint(auth_response);
read_from_body(auth_response, move |auth_result| {
// If the endpoint is `/SAML20/SP/ACS` and no auth data found in body, it should be considered as invalid
let auth_result = auth_result.map_err(move |e| {
info!("Failed to read auth data from body: {}", e);
@ -44,7 +54,7 @@ where
}
}
fn read_from_headers(auth_response: &AuthResponse) -> AuthDataParseResult {
fn read_from_headers(auth_response: &impl ResponseReader) -> AuthDataParseResult {
let Some(status) = auth_response.get_header("saml-auth-status") else {
info!("No SAML auth status found in headers");
return Err(AuthDataParseError::NotFound);
@ -65,11 +75,11 @@ fn read_from_headers(auth_response: &AuthResponse) -> AuthDataParseResult {
})
}
fn read_from_body<F>(auth_response: &AuthResponse, cb: F)
fn read_from_body<F>(auth_response: &impl ResponseReader, cb: F)
where
F: FnOnce(AuthDataParseResult) + 'static,
{
auth_response.get_body(|body| match body {
auth_response.get_body(Box::new(|body| match body {
Ok(body) => {
if let Some(html) = body {
cb(read_from_html(&html))
@ -79,7 +89,7 @@ where
info!("Failed to read body: {}", err);
cb(Err(AuthDataParseError::Invalid))
}
});
}));
}
fn read_from_html(html: &str) -> AuthDataParseResult {

View File

@ -7,7 +7,7 @@ use tao::{
};
use wry::WebViewBuilder;
use crate::{auth_prelogin, webview::auth_response::read_auth_data};
use crate::{auth_prelogin, webview::response_reader::read_auth_data};
use super::platform_impl::connect_webview_response;
@ -98,7 +98,7 @@ impl<'a> WebviewAuthenticatorBuilder<'a> {
connect_webview_response(&webview, |response| {
// println!("Received response: {:?}", response.unwrap().url());
match response {
Ok(response) => read_auth_data(response, |auth_result| {
Ok(response) => read_auth_data(&response, |auth_result| {
println!("Auth result: {:?}", auth_result);
}),
Err(err) => todo!(),