v1.8.0 #429
@@ -72,12 +72,21 @@ function createWindow() {
|
|||||||
mainWindow.setMenuBarVisibility(false);
|
mainWindow.setMenuBarVisibility(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
mainWindow.webContents.setUserAgent(
|
const customUserAgent = `Termix-Desktop/${appVersion} (${platform}; Electron/${electronVersion})`;
|
||||||
`Termix-Desktop/${appVersion} (${platform}; Electron/${electronVersion})`,
|
mainWindow.webContents.setUserAgent(customUserAgent);
|
||||||
|
|
||||||
|
mainWindow.webContents.session.webRequest.onBeforeSendHeaders(
|
||||||
|
(details, callback) => {
|
||||||
|
details.requestHeaders["X-Electron-App"] = "true";
|
||||||
|
|
||||||
|
details.requestHeaders["User-Agent"] = customUserAgent;
|
||||||
|
|
||||||
|
callback({ requestHeaders: details.requestHeaders });
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
mainWindow.loadURL("http:://localhost:5173");
|
mainWindow.loadURL("http://localhost:5173");
|
||||||
mainWindow.webContents.openDevTools();
|
mainWindow.webContents.openDevTools();
|
||||||
} else {
|
} else {
|
||||||
const indexPath = path.join(__dirname, "..", "dist", "index.html");
|
const indexPath = path.join(__dirname, "..", "dist", "index.html");
|
||||||
|
|||||||
@@ -241,6 +241,22 @@ export function Auth({
|
|||||||
throw new Error(t("errors.loginFailed"));
|
throw new Error(t("errors.loginFailed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isInElectronWebView() && res.token) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem("jwt", res.token);
|
||||||
|
window.parent.postMessage(
|
||||||
|
{
|
||||||
|
type: "AUTH_SUCCESS",
|
||||||
|
token: res.token,
|
||||||
|
source: "auth_component",
|
||||||
|
platform: "desktop",
|
||||||
|
timestamp: Date.now(),
|
||||||
|
},
|
||||||
|
"*",
|
||||||
|
);
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
const [meRes] = await Promise.all([getUserInfo()]);
|
const [meRes] = await Promise.all([getUserInfo()]);
|
||||||
|
|
||||||
setInternalLoggedIn(true);
|
setInternalLoggedIn(true);
|
||||||
@@ -398,6 +414,22 @@ export function Auth({
|
|||||||
localStorage.setItem("jwt", res.token);
|
localStorage.setItem("jwt", res.token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isInElectronWebView() && res.token) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem("jwt", res.token);
|
||||||
|
window.parent.postMessage(
|
||||||
|
{
|
||||||
|
type: "AUTH_SUCCESS",
|
||||||
|
token: res.token,
|
||||||
|
source: "totp_auth_component",
|
||||||
|
platform: "desktop",
|
||||||
|
timestamp: Date.now(),
|
||||||
|
},
|
||||||
|
"*",
|
||||||
|
);
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
setInternalLoggedIn(true);
|
setInternalLoggedIn(true);
|
||||||
setLoggedIn(true);
|
setLoggedIn(true);
|
||||||
setIsAdmin(!!res.is_admin);
|
setIsAdmin(!!res.is_admin);
|
||||||
@@ -485,6 +517,24 @@ export function Auth({
|
|||||||
|
|
||||||
getUserInfo()
|
getUserInfo()
|
||||||
.then((meRes) => {
|
.then((meRes) => {
|
||||||
|
if (isInElectronWebView()) {
|
||||||
|
const token = getCookie("jwt") || localStorage.getItem("jwt");
|
||||||
|
if (token) {
|
||||||
|
try {
|
||||||
|
window.parent.postMessage(
|
||||||
|
{
|
||||||
|
type: "AUTH_SUCCESS",
|
||||||
|
token: token,
|
||||||
|
source: "oidc_callback",
|
||||||
|
platform: "desktop",
|
||||||
|
timestamp: Date.now(),
|
||||||
|
},
|
||||||
|
"*",
|
||||||
|
);
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setInternalLoggedIn(true);
|
setInternalLoggedIn(true);
|
||||||
setLoggedIn(true);
|
setLoggedIn(true);
|
||||||
setIsAdmin(!!meRes.is_admin);
|
setIsAdmin(!!meRes.is_admin);
|
||||||
@@ -577,7 +627,8 @@ export function Auth({
|
|||||||
if (showServerConfig === null) {
|
if (showServerConfig === null) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`w-[420px] max-w-full p-6 flex flex-col bg-dark-bg border-2 border-dark-border rounded-md ${className || ""}`}
|
className={`w-[420px] max-w-full p-6 flex flex-col bg-dark-bg border-2 border-dark-border rounded-md overflow-y-auto my-2 ${className || ""}`}
|
||||||
|
style={{ maxHeight: "calc(100vh - 1rem)" }}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-center h-32">
|
<div className="flex items-center justify-center h-32">
|
||||||
@@ -590,7 +641,8 @@ export function Auth({
|
|||||||
if (showServerConfig) {
|
if (showServerConfig) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`w-[420px] max-w-full p-6 flex flex-col bg-dark-bg border-2 border-dark-border rounded-md ${className || ""}`}
|
className={`w-[420px] max-w-full p-6 flex flex-col bg-dark-bg border-2 border-dark-border rounded-md overflow-y-auto my-2 ${className || ""}`}
|
||||||
|
style={{ maxHeight: "calc(100vh - 1rem)" }}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ServerConfigComponent
|
<ServerConfigComponent
|
||||||
@@ -645,7 +697,8 @@ export function Auth({
|
|||||||
if (dbHealthChecking && !dbConnectionFailed) {
|
if (dbHealthChecking && !dbConnectionFailed) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`w-[420px] max-w-full p-6 flex flex-col bg-dark-bg border-2 border-dark-border rounded-md ${className || ""}`}
|
className={`w-[420px] max-w-full p-6 flex flex-col bg-dark-bg border-2 border-dark-border rounded-md overflow-y-auto my-2 ${className || ""}`}
|
||||||
|
style={{ maxHeight: "calc(100vh - 1rem)" }}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-center h-32">
|
<div className="flex items-center justify-center h-32">
|
||||||
@@ -663,7 +716,8 @@ export function Auth({
|
|||||||
if (dbConnectionFailed) {
|
if (dbConnectionFailed) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`w-[420px] max-w-full p-6 flex flex-col bg-dark-bg border-2 border-dark-border rounded-md ${className || ""}`}
|
className={`w-[420px] max-w-full p-6 flex flex-col bg-dark-bg border-2 border-dark-border rounded-md overflow-y-auto my-2 ${className || ""}`}
|
||||||
|
style={{ maxHeight: "calc(100vh - 1rem)" }}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<div className="mb-6 text-center">
|
<div className="mb-6 text-center">
|
||||||
@@ -722,7 +776,8 @@ export function Auth({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`w-[420px] max-w-full p-6 flex flex-col bg-dark-bg border-2 border-dark-border rounded-md ${className || ""}`}
|
className={`w-[420px] max-w-full p-6 flex flex-col bg-dark-bg border-2 border-dark-border rounded-md overflow-y-auto my-2 ${className || ""}`}
|
||||||
|
style={{ maxHeight: "calc(100vh - 1rem)" }}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{isInElectronWebView() && (
|
{isInElectronWebView() && (
|
||||||
|
|||||||
@@ -57,16 +57,13 @@ export function ElectronLoginForm({
|
|||||||
|
|
||||||
onAuthSuccess();
|
onAuthSuccess();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[ElectronLoginForm] Error saving JWT:", err);
|
|
||||||
setError(t("errors.authTokenSaveFailed"));
|
setError(t("errors.authTokenSaveFailed"));
|
||||||
setIsAuthenticating(false);
|
setIsAuthenticating(false);
|
||||||
hasAuthenticatedRef.current = false;
|
hasAuthenticatedRef.current = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {}
|
||||||
console.error("[ElectronLoginForm] Error processing message:", err);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener("message", handleMessage);
|
window.addEventListener("message", handleMessage);
|
||||||
@@ -99,7 +96,9 @@ export function ElectronLoginForm({
|
|||||||
let hasNotified = false;
|
let hasNotified = false;
|
||||||
|
|
||||||
function postJWTToParent(token, source) {
|
function postJWTToParent(token, source) {
|
||||||
if (hasNotified) return;
|
if (hasNotified) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
hasNotified = true;
|
hasNotified = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -111,7 +110,6 @@ export function ElectronLoginForm({
|
|||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
}, '*');
|
}, '*');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[Electron WebView] Error posting message:', e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +141,6 @@ export function ElectronLoginForm({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Electron WebView] Error in checkAuth:', error);
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -178,27 +175,23 @@ export function ElectronLoginForm({
|
|||||||
clearInterval(intervalId);
|
clearInterval(intervalId);
|
||||||
}, 300000);
|
}, 300000);
|
||||||
|
|
||||||
checkAuth();
|
setTimeout(() => checkAuth(), 500);
|
||||||
})();
|
})();
|
||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (iframe.contentWindow) {
|
if (iframe.contentWindow) {
|
||||||
iframe.contentWindow.postMessage(
|
try {
|
||||||
{ type: "INJECT_SCRIPT", script: injectedScript },
|
iframe.contentWindow.eval(injectedScript);
|
||||||
"*",
|
} catch (evalError) {
|
||||||
);
|
iframe.contentWindow.postMessage(
|
||||||
|
{ type: "INJECT_SCRIPT", script: injectedScript },
|
||||||
iframe.contentWindow.eval(injectedScript);
|
"*",
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {}
|
||||||
console.warn(
|
} catch (err) {}
|
||||||
"[ElectronLoginForm] Cannot inject script due to cross-origin restrictions",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error("[ElectronLoginForm] Error in handleLoad:", err);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleError = () => {
|
const handleError = () => {
|
||||||
|
|||||||
@@ -1695,10 +1695,34 @@ export async function loginUser(
|
|||||||
try {
|
try {
|
||||||
const response = await authApi.post("/users/login", { username, password });
|
const response = await authApi.post("/users/login", { username, password });
|
||||||
|
|
||||||
if (isElectron() && response.data.token) {
|
const hasToken = response.data.token;
|
||||||
|
|
||||||
|
if (isElectron() && hasToken) {
|
||||||
localStorage.setItem("jwt", response.data.token);
|
localStorage.setItem("jwt", response.data.token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isInIframe =
|
||||||
|
typeof window !== "undefined" && window.self !== window.top;
|
||||||
|
|
||||||
|
if (isInIframe && hasToken) {
|
||||||
|
localStorage.setItem("jwt", response.data.token);
|
||||||
|
|
||||||
|
try {
|
||||||
|
window.parent.postMessage(
|
||||||
|
{
|
||||||
|
type: "AUTH_SUCCESS",
|
||||||
|
token: response.data.token,
|
||||||
|
source: "login_api",
|
||||||
|
platform: "desktop",
|
||||||
|
timestamp: Date.now(),
|
||||||
|
},
|
||||||
|
"*",
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[main-axios] Error posting message to parent:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
token: response.data.token || "cookie-based",
|
token: response.data.token || "cookie-based",
|
||||||
success: response.data.success,
|
success: response.data.success,
|
||||||
@@ -2027,6 +2051,35 @@ export async function verifyTOTPLogin(
|
|||||||
temp_token,
|
temp_token,
|
||||||
totp_code,
|
totp_code,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const hasToken = response.data.token;
|
||||||
|
|
||||||
|
if (isElectron() && hasToken) {
|
||||||
|
localStorage.setItem("jwt", response.data.token);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isInIframe =
|
||||||
|
typeof window !== "undefined" && window.self !== window.top;
|
||||||
|
|
||||||
|
if (isInIframe && hasToken) {
|
||||||
|
localStorage.setItem("jwt", response.data.token);
|
||||||
|
|
||||||
|
try {
|
||||||
|
window.parent.postMessage(
|
||||||
|
{
|
||||||
|
type: "AUTH_SUCCESS",
|
||||||
|
token: response.data.token,
|
||||||
|
source: "totp_verify",
|
||||||
|
platform: "desktop",
|
||||||
|
timestamp: Date.now(),
|
||||||
|
},
|
||||||
|
"*",
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[main-axios] Error posting message to parent:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleApiError(error as AxiosError, "verify TOTP login");
|
handleApiError(error as AxiosError, "verify TOTP login");
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ function postJWTToWebView() {
|
|||||||
const jwt = getCookie("jwt") || localStorage.getItem("jwt");
|
const jwt = getCookie("jwt") || localStorage.getItem("jwt");
|
||||||
|
|
||||||
if (!jwt) {
|
if (!jwt) {
|
||||||
console.warn("JWT not found when trying to post to WebView");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,11 +52,7 @@ function postJWTToWebView() {
|
|||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
} catch (error) {}
|
||||||
console.log("JWT posted to React Native WebView");
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to post JWT to WebView:", error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AuthProps extends React.ComponentProps<"div"> {
|
interface AuthProps extends React.ComponentProps<"div"> {
|
||||||
@@ -563,7 +558,8 @@ export function Auth({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`w-full max-w-md flex flex-col bg-dark-bg ${className || ""}`}
|
className={`w-full max-w-md flex flex-col bg-dark-bg overflow-y-auto my-2 ${className || ""}`}
|
||||||
|
style={{ maxHeight: "calc(100vh - 1rem)" }}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{isReactNativeWebView() && (
|
{isReactNativeWebView() && (
|
||||||
|
|||||||
Reference in New Issue
Block a user