v1.7.0 #318
@@ -103,7 +103,6 @@ if [ -f "package.json" ]; then
|
|||||||
VERSION=$(grep '"version"' package.json | sed 's/.*"version": *"\([^"]*\)".*/\1/')
|
VERSION=$(grep '"version"' package.json | sed 's/.*"version": *"\([^"]*\)".*/\1/')
|
||||||
if [ -n "$VERSION" ]; then
|
if [ -n "$VERSION" ]; then
|
||||||
export VERSION
|
export VERSION
|
||||||
echo "Detected version: $VERSION"
|
|
||||||
else
|
else
|
||||||
echo "Warning: Could not extract version from package.json"
|
echo "Warning: Could not extract version from package.json"
|
||||||
fi
|
fi
|
||||||
|
|||||||
+16
-1
@@ -3,6 +3,11 @@ const path = require("path");
|
|||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const os = require("os");
|
const os = require("os");
|
||||||
|
|
||||||
|
app.commandLine.appendSwitch('--ignore-certificate-errors');
|
||||||
|
app.commandLine.appendSwitch('--ignore-ssl-errors');
|
||||||
|
app.commandLine.appendSwitch('--ignore-certificate-errors-spki-list');
|
||||||
|
app.commandLine.appendSwitch('--enable-features=NetworkService');
|
||||||
|
|
||||||
|
|
|||||||
let mainWindow = null;
|
let mainWindow = null;
|
||||||
|
|
||||||
const isDev = process.env.NODE_ENV === "development" || !app.isPackaged;
|
const isDev = process.env.NODE_ENV === "development" || !app.isPackaged;
|
||||||
@@ -35,7 +40,7 @@ function createWindow() {
|
|||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: false,
|
nodeIntegration: false,
|
||||||
contextIsolation: true,
|
contextIsolation: true,
|
||||||
webSecurity: !isDev,
|
webSecurity: true,
|
||||||
preload: path.join(__dirname, "preload.js"),
|
preload: path.join(__dirname, "preload.js"),
|
||||||
},
|
},
|
||||||
show: false,
|
show: false,
|
||||||
@@ -57,6 +62,16 @@ function createWindow() {
|
|||||||
mainWindow.show();
|
mainWindow.show();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
mainWindow.webContents.session.webRequest.onBeforeSendHeaders((details, callback) => {
|
||||||
|
callback({ requestHeaders: details.requestHeaders });
|
||||||
|
});
|
||||||
|
|
||||||
|
mainWindow.webContents.session.cookies.onChanged((event, cookie, cause, removed) => {
|
||||||
|
if (!removed) {
|
||||||
|
console.log('Cookie set:', cookie.name, 'for domain:', cookie.domain);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
mainWindow.webContents.on(
|
mainWindow.webContents.on(
|
||||||
"did-fail-load",
|
"did-fail-load",
|
||||||
(event, errorCode, errorDescription, validatedURL) => {
|
(event, errorCode, errorDescription, validatedURL) => {
|
||||||
|
|||||||
Generated
+1630
-25
File diff suppressed because it is too large
Load Diff
+1
-5
@@ -9,15 +9,10 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "npx prettier . --write",
|
"clean": "npx prettier . --write",
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"dev:https": "cross-env VITE_HTTPS=true vite",
|
|
||||||
"build": "vite build && tsc -p tsconfig.node.json",
|
"build": "vite build && tsc -p tsconfig.node.json",
|
||||||
"build:backend": "tsc -p tsconfig.node.json",
|
"build:backend": "tsc -p tsconfig.node.json",
|
||||||
"dev:backend": "tsc -p tsconfig.node.json && node ./dist/backend/backend/starter.js",
|
"dev:backend": "tsc -p tsconfig.node.json && node ./dist/backend/backend/starter.js",
|
||||||
"start": "npm run build:backend && node ./dist/backend/backend/starter.js",
|
|
||||||
"start:ssl": "npm run start",
|
|
||||||
"lint": "eslint .",
|
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"electron": "electron .",
|
|
||||||
"electron:dev": "concurrently \"npm run dev\" \"wait-on http://localhost:5173 && electron .\"",
|
"electron:dev": "concurrently \"npm run dev\" \"wait-on http://localhost:5173 && electron .\"",
|
||||||
"build:win-portable": "npm run build && electron-builder --win --dir",
|
"build:win-portable": "npm run build && electron-builder --win --dir",
|
||||||
"build:win-installer": "npm run build && electron-builder --win --publish=never",
|
"build:win-installer": "npm run build && electron-builder --win --publish=never",
|
||||||
@@ -103,6 +98,7 @@
|
|||||||
"speakeasy": "^2.0.0",
|
"speakeasy": "^2.0.0",
|
||||||
"ssh2": "^1.16.0",
|
"ssh2": "^1.16.0",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
|
"wait-on": "^9.0.1",
|
||||||
"ws": "^8.18.3",
|
"ws": "^8.18.3",
|
||||||
"zod": "^4.0.5"
|
"zod": "^4.0.5"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -920,17 +920,25 @@ router.post("/login", async (req, res) => {
|
|||||||
dataUnlocked: true,
|
dataUnlocked: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const response: any = {
|
||||||
|
success: true,
|
||||||
|
is_admin: !!userRecord.is_admin,
|
||||||
|
username: userRecord.username,
|
||||||
|
};
|
||||||
|
|
||||||
|
const isElectron = req.headers['x-electron-app'] === 'true' || req.headers['X-Electron-App'] === 'true';
|
||||||
|
|
||||||
|
if (isElectron) {
|
||||||
|
response.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
.cookie(
|
.cookie(
|
||||||
"jwt",
|
"jwt",
|
||||||
token,
|
token,
|
||||||
authManager.getSecureCookieOptions(req, 24 * 60 * 60 * 1000),
|
authManager.getSecureCookieOptions(req, 24 * 60 * 60 * 1000),
|
||||||
)
|
)
|
||||||
.json({
|
.json(response);
|
||||||
success: true,
|
|
||||||
is_admin: !!userRecord.is_admin,
|
|
||||||
username: userRecord.username,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
authLogger.error("Failed to log in user", err);
|
authLogger.error("Failed to log in user", err);
|
||||||
return res.status(500).json({ error: "Login failed" });
|
return res.status(500).json({ error: "Login failed" });
|
||||||
@@ -1499,6 +1507,7 @@ router.post("/totp/verify-login", async (req, res) => {
|
|||||||
success: true,
|
success: true,
|
||||||
is_admin: !!userRecord.is_admin,
|
is_admin: !!userRecord.is_admin,
|
||||||
username: userRecord.username,
|
username: userRecord.username,
|
||||||
|
token: req.headers['x-electron-app'] === 'true' ? token : undefined,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
authLogger.error("TOTP verification failed", err);
|
authLogger.error("TOTP verification failed", err);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert.tsx";
|
import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert.tsx";
|
||||||
import { Button } from "@/components/ui/button.tsx";
|
import { Button } from "@/components/ui/button.tsx";
|
||||||
import { ExternalLink, Download, X, AlertTriangle } from "lucide-react";
|
import { ExternalLink, Download, AlertTriangle } from "lucide-react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
interface VersionAlertProps {
|
interface VersionAlertProps {
|
||||||
@@ -21,16 +21,12 @@ interface VersionAlertProps {
|
|||||||
cache_age?: number;
|
cache_age?: number;
|
||||||
error?: string;
|
error?: string;
|
||||||
};
|
};
|
||||||
onDismiss?: () => void;
|
|
||||||
onDownload?: () => void;
|
onDownload?: () => void;
|
||||||
showDismiss?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function VersionAlert({
|
export function VersionAlert({
|
||||||
updateInfo,
|
updateInfo,
|
||||||
onDismiss,
|
|
||||||
onDownload,
|
onDownload,
|
||||||
showDismiss = true,
|
|
||||||
}: VersionAlertProps) {
|
}: VersionAlertProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -106,18 +102,6 @@ export function VersionAlert({
|
|||||||
{t("versionCheck.downloadUpdate")}
|
{t("versionCheck.downloadUpdate")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{showDismiss && onDismiss && (
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
onClick={onDismiss}
|
|
||||||
className="flex items-center gap-1"
|
|
||||||
>
|
|
||||||
<X className="h-3 w-3" />
|
|
||||||
{t("versionCheck.dismiss")}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ import { checkElectronUpdate, isElectron } from "@/ui/main-axios.ts";
|
|||||||
interface VersionCheckModalProps {
|
interface VersionCheckModalProps {
|
||||||
onDismiss: () => void;
|
onDismiss: () => void;
|
||||||
onContinue: () => void;
|
onContinue: () => void;
|
||||||
|
isAuthenticated?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function VersionCheckModal({ onDismiss, onContinue }: VersionCheckModalProps) {
|
export function VersionCheckModal({ onDismiss, onContinue, isAuthenticated = false }: VersionCheckModalProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [versionInfo, setVersionInfo] = useState<any>(null);
|
const [versionInfo, setVersionInfo] = useState<any>(null);
|
||||||
const [versionChecking, setVersionChecking] = useState(false);
|
const [versionChecking, setVersionChecking] = useState(false);
|
||||||
@@ -29,6 +30,11 @@ export function VersionCheckModal({ onDismiss, onContinue }: VersionCheckModalPr
|
|||||||
try {
|
try {
|
||||||
const updateInfo = await checkElectronUpdate();
|
const updateInfo = await checkElectronUpdate();
|
||||||
setVersionInfo(updateInfo);
|
setVersionInfo(updateInfo);
|
||||||
|
|
||||||
|
if (updateInfo?.status === "up_to_date") {
|
||||||
|
onContinue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to check for updates:", error);
|
console.error("Failed to check for updates:", error);
|
||||||
setVersionInfo({ success: false, error: "Check failed" });
|
setVersionInfo({ success: false, error: "Check failed" });
|
||||||
@@ -57,8 +63,25 @@ export function VersionCheckModal({ onDismiss, onContinue }: VersionCheckModalPr
|
|||||||
|
|
||||||
if (versionChecking && !versionInfo) {
|
if (versionChecking && !versionInfo) {
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
<div className="fixed inset-0 flex items-center justify-center z-50">
|
||||||
<div className="bg-dark-bg border border-dark-border rounded-lg p-6 max-w-md w-full mx-4">
|
{!isAuthenticated && (
|
||||||
|
<div
|
||||||
|
className="absolute inset-0"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `linear-gradient(
|
||||||
|
135deg,
|
||||||
|
transparent 0%,
|
||||||
|
transparent 49%,
|
||||||
|
rgba(255, 255, 255, 0.03) 49%,
|
||||||
|
rgba(255, 255, 255, 0.03) 51%,
|
||||||
|
transparent 51%,
|
||||||
|
transparent 100%
|
||||||
|
)`,
|
||||||
|
backgroundSize: "80px 80px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className="bg-dark-bg border-2 border-dark-border rounded-lg p-6 max-w-md w-full mx-4 relative z-10">
|
||||||
<div className="flex items-center justify-center mb-4">
|
<div className="flex items-center justify-center mb-4">
|
||||||
<div className="w-8 h-8 border-2 border-primary border-t-transparent rounded-full animate-spin" />
|
<div className="w-8 h-8 border-2 border-primary border-t-transparent rounded-full animate-spin" />
|
||||||
</div>
|
</div>
|
||||||
@@ -70,53 +93,47 @@ export function VersionCheckModal({ onDismiss, onContinue }: VersionCheckModalPr
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!versionInfo || versionInfo.status === "up_to_date" || versionDismissed) {
|
|
||||||
|
if (!versionInfo || versionDismissed) {
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
<div className="fixed inset-0 flex items-center justify-center z-50">
|
||||||
<div className="bg-dark-bg border border-dark-border rounded-lg p-6 max-w-md w-full mx-4">
|
{!isAuthenticated && (
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div
|
||||||
|
className="absolute inset-0"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `linear-gradient(
|
||||||
|
135deg,
|
||||||
|
transparent 0%,
|
||||||
|
transparent 49%,
|
||||||
|
rgba(255, 255, 255, 0.03) 49%,
|
||||||
|
rgba(255, 255, 255, 0.03) 51%,
|
||||||
|
transparent 51%,
|
||||||
|
transparent 100%
|
||||||
|
)`,
|
||||||
|
backgroundSize: "80px 80px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className="bg-dark-bg border-2 border-dark-border rounded-lg p-6 max-w-md w-full mx-4 relative z-10">
|
||||||
|
<div className="mb-4">
|
||||||
<h2 className="text-lg font-semibold">
|
<h2 className="text-lg font-semibold">
|
||||||
{t("versionCheck.checkUpdates")}
|
{t("versionCheck.checkUpdates")}
|
||||||
</h2>
|
</h2>
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
onClick={onDismiss}
|
|
||||||
className="h-6 w-6 p-0"
|
|
||||||
>
|
|
||||||
<X className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{versionInfo && !versionDismissed && (
|
{versionInfo && !versionDismissed && (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<VersionAlert
|
<VersionAlert
|
||||||
updateInfo={versionInfo}
|
updateInfo={versionInfo}
|
||||||
onDismiss={handleVersionDismiss}
|
|
||||||
onDownload={handleDownloadUpdate}
|
onDownload={handleDownloadUpdate}
|
||||||
showDismiss={true}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={checkForUpdates}
|
|
||||||
disabled={versionChecking}
|
|
||||||
className="flex-1"
|
|
||||||
>
|
|
||||||
{versionChecking ? (
|
|
||||||
<div className="w-3 h-3 border border-current border-t-transparent rounded-full animate-spin" />
|
|
||||||
) : (
|
|
||||||
<RefreshCw className="w-3 h-3" />
|
|
||||||
)}
|
|
||||||
{t("versionCheck.refresh")}
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={handleContinue}
|
onClick={handleContinue}
|
||||||
className="flex-1"
|
className="flex-1 h-10"
|
||||||
>
|
>
|
||||||
{t("common.continue")}
|
{t("common.continue")}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -127,49 +144,42 @@ export function VersionCheckModal({ onDismiss, onContinue }: VersionCheckModalPr
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
<div className="fixed inset-0 flex items-center justify-center z-50">
|
||||||
<div className="bg-dark-bg border border-dark-border rounded-lg p-6 max-w-md w-full mx-4">
|
{!isAuthenticated && (
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div
|
||||||
|
className="absolute inset-0"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `linear-gradient(
|
||||||
|
135deg,
|
||||||
|
transparent 0%,
|
||||||
|
transparent 49%,
|
||||||
|
rgba(255, 255, 255, 0.03) 49%,
|
||||||
|
rgba(255, 255, 255, 0.03) 51%,
|
||||||
|
transparent 51%,
|
||||||
|
transparent 100%
|
||||||
|
)`,
|
||||||
|
backgroundSize: "80px 80px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className="bg-dark-bg border-2 border-dark-border rounded-lg p-6 max-w-md w-full mx-4 relative z-10">
|
||||||
|
<div className="mb-4">
|
||||||
<h2 className="text-lg font-semibold">
|
<h2 className="text-lg font-semibold">
|
||||||
{t("versionCheck.updateRequired")}
|
{t("versionCheck.updateRequired")}
|
||||||
</h2>
|
</h2>
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
onClick={onDismiss}
|
|
||||||
className="h-6 w-6 p-0"
|
|
||||||
>
|
|
||||||
<X className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<VersionAlert
|
<VersionAlert
|
||||||
updateInfo={versionInfo}
|
updateInfo={versionInfo}
|
||||||
onDismiss={handleVersionDismiss}
|
|
||||||
onDownload={handleDownloadUpdate}
|
onDownload={handleDownloadUpdate}
|
||||||
showDismiss={true}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={checkForUpdates}
|
|
||||||
disabled={versionChecking}
|
|
||||||
className="flex-1"
|
|
||||||
>
|
|
||||||
{versionChecking ? (
|
|
||||||
<div className="w-3 h-3 border border-current border-t-transparent rounded-full animate-spin" />
|
|
||||||
) : (
|
|
||||||
<RefreshCw className="w-3 h-3" />
|
|
||||||
)}
|
|
||||||
{t("versionCheck.refresh")}
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={handleContinue}
|
onClick={handleContinue}
|
||||||
className="flex-1"
|
className="flex-1 h-10"
|
||||||
>
|
>
|
||||||
{t("common.continue")}
|
{t("common.continue")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -229,13 +229,16 @@
|
|||||||
"checkUpdates": "Check for Updates",
|
"checkUpdates": "Check for Updates",
|
||||||
"checkingUpdates": "Checking for updates...",
|
"checkingUpdates": "Checking for updates...",
|
||||||
"refresh": "Refresh",
|
"refresh": "Refresh",
|
||||||
"updateRequired": "Update Required"
|
"updateRequired": "Update Required",
|
||||||
|
"updateDismissed": "Update notification dismissed",
|
||||||
|
"noUpdatesFound": "No updates found"
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
"minimize": "Minimize",
|
"minimize": "Minimize",
|
||||||
"online": "Online",
|
"online": "Online",
|
||||||
"offline": "Offline",
|
"offline": "Offline",
|
||||||
|
"continue": "Continue",
|
||||||
"maintenance": "Maintenance",
|
"maintenance": "Maintenance",
|
||||||
"degraded": "Degraded",
|
"degraded": "Degraded",
|
||||||
"discord": "Discord",
|
"discord": "Discord",
|
||||||
|
|||||||
@@ -227,13 +227,16 @@
|
|||||||
"checkUpdates": "检查更新",
|
"checkUpdates": "检查更新",
|
||||||
"checkingUpdates": "正在检查更新...",
|
"checkingUpdates": "正在检查更新...",
|
||||||
"refresh": "刷新",
|
"refresh": "刷新",
|
||||||
"updateRequired": "需要更新"
|
"updateRequired": "需要更新",
|
||||||
|
"updateDismissed": "更新通知已忽略",
|
||||||
|
"noUpdatesFound": "未找到更新"
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"close": "关闭",
|
"close": "关闭",
|
||||||
"minimize": "最小化",
|
"minimize": "最小化",
|
||||||
"online": "在线",
|
"online": "在线",
|
||||||
"offline": "离线",
|
"offline": "离线",
|
||||||
|
"continue": "继续",
|
||||||
"maintenance": "维护中",
|
"maintenance": "维护中",
|
||||||
"degraded": "降级",
|
"degraded": "降级",
|
||||||
"discord": "Discord",
|
"discord": "Discord",
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ export function AdminSettings({
|
|||||||
try {
|
try {
|
||||||
const apiUrl = isElectron()
|
const apiUrl = isElectron()
|
||||||
? `${(window as any).configuredServerUrl}/database/export`
|
? `${(window as any).configuredServerUrl}/database/export`
|
||||||
: "http://localhost:30001/database/export";
|
: "/database/export";
|
||||||
|
|
||||||
const response = await fetch(apiUrl, {
|
const response = await fetch(apiUrl, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -333,7 +333,7 @@ export function AdminSettings({
|
|||||||
try {
|
try {
|
||||||
const apiUrl = isElectron()
|
const apiUrl = isElectron()
|
||||||
? `${(window as any).configuredServerUrl}/database/import`
|
? `${(window as any).configuredServerUrl}/database/import`
|
||||||
: "http://localhost:30001/database/import";
|
: "/database/import";
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("file", importFile);
|
formData.append("file", importFile);
|
||||||
|
|||||||
@@ -100,10 +100,11 @@ function AppContent() {
|
|||||||
<VersionCheckModal
|
<VersionCheckModal
|
||||||
onDismiss={() => setShowVersionCheck(false)}
|
onDismiss={() => setShowVersionCheck(false)}
|
||||||
onContinue={() => setShowVersionCheck(false)}
|
onContinue={() => setShowVersionCheck(false)}
|
||||||
|
isAuthenticated={isAuthenticated}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isAuthenticated && !authLoading && (
|
{!isAuthenticated && !authLoading && !showVersionCheck && (
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0"
|
className="absolute inset-0"
|
||||||
@@ -123,7 +124,7 @@ function AppContent() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isAuthenticated && !authLoading && (
|
{!isAuthenticated && !authLoading && !showVersionCheck && (
|
||||||
<div className="fixed inset-0 flex items-center justify-center z-[10000]">
|
<div className="fixed inset-0 flex items-center justify-center z-[10000]">
|
||||||
<Homepage
|
<Homepage
|
||||||
onSelectView={handleSelectView}
|
onSelectView={handleSelectView}
|
||||||
|
|||||||
@@ -696,7 +696,7 @@ export function HomepageAuth({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!internalLoggedIn && !authLoading && !totpRequired && (
|
{!loggedIn && !authLoading && !totpRequired && (
|
||||||
<>
|
<>
|
||||||
<div className="flex gap-2 mb-6">
|
<div className="flex gap-2 mb-6">
|
||||||
<button
|
<button
|
||||||
@@ -963,7 +963,7 @@ export function HomepageAuth({
|
|||||||
className="h-11 text-base"
|
className="h-11 text-base"
|
||||||
value={localUsername}
|
value={localUsername}
|
||||||
onChange={(e) => setLocalUsername(e.target.value)}
|
onChange={(e) => setLocalUsername(e.target.value)}
|
||||||
disabled={loading || internalLoggedIn}
|
disabled={loading || loggedIn}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
@@ -974,7 +974,7 @@ export function HomepageAuth({
|
|||||||
className="h-11 text-base"
|
className="h-11 text-base"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
disabled={loading || internalLoggedIn}
|
disabled={loading || loggedIn}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{tab === "signup" && (
|
{tab === "signup" && (
|
||||||
@@ -988,7 +988,7 @@ export function HomepageAuth({
|
|||||||
className="h-11 text-base"
|
className="h-11 text-base"
|
||||||
value={signupConfirmPassword}
|
value={signupConfirmPassword}
|
||||||
onChange={(e) => setSignupConfirmPassword(e.target.value)}
|
onChange={(e) => setSignupConfirmPassword(e.target.value)}
|
||||||
disabled={loading || internalLoggedIn}
|
disabled={loading || loggedIn}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -1008,7 +1008,7 @@ export function HomepageAuth({
|
|||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="w-full h-11 text-base font-semibold"
|
className="w-full h-11 text-base font-semibold"
|
||||||
disabled={loading || internalLoggedIn}
|
disabled={loading || loggedIn}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTab("reset");
|
setTab("reset");
|
||||||
resetPasswordState();
|
resetPasswordState();
|
||||||
|
|||||||
+30
-16
@@ -142,7 +142,7 @@ function createApiInstance(
|
|||||||
baseURL,
|
baseURL,
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
timeout: 30000,
|
timeout: 30000,
|
||||||
withCredentials: true, // Required for HttpOnly cookies to be sent cross-origin
|
withCredentials: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
instance.interceptors.request.use((config) => {
|
instance.interceptors.request.use((config) => {
|
||||||
@@ -169,12 +169,13 @@ function createApiInstance(
|
|||||||
logger.requestStart(method, fullUrl, context);
|
logger.requestStart(method, fullUrl, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: JWT token is now automatically sent via secure HttpOnly cookies
|
|
||||||
// No need to manually set Authorization header for cookie-based auth
|
|
||||||
|
|
||||||
if (isElectron()) {
|
if (isElectron()) {
|
||||||
config.headers["X-Electron-App"] = "true";
|
config.headers["X-Electron-App"] = "true";
|
||||||
config.headers["User-Agent"] = "Termix-Electron/1.6.0";
|
|
||||||
|
const token = localStorage.getItem("jwt");
|
||||||
|
if (token) {
|
||||||
|
config.headers["Authorization"] = `Bearer ${token}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
@@ -300,6 +301,10 @@ function createApiInstance(
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
function isDev(): boolean {
|
function isDev(): boolean {
|
||||||
|
if (isElectron()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
process.env.NODE_ENV === "development" &&
|
process.env.NODE_ENV === "development" &&
|
||||||
(window.location.port === "3000" ||
|
(window.location.port === "3000" ||
|
||||||
@@ -347,6 +352,7 @@ export async function saveServerConfig(config: ServerConfig): Promise<boolean> {
|
|||||||
);
|
);
|
||||||
if (result?.success) {
|
if (result?.success) {
|
||||||
configuredServerUrl = config.serverUrl;
|
configuredServerUrl = config.serverUrl;
|
||||||
|
(window as any).configuredServerUrl = configuredServerUrl;
|
||||||
updateApiInstances();
|
updateApiInstances();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -405,18 +411,8 @@ export async function checkElectronUpdate(): Promise<{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isElectron()) {
|
|
||||||
getServerConfig().then((config) => {
|
|
||||||
if (config?.serverUrl) {
|
|
||||||
configuredServerUrl = config.serverUrl;
|
|
||||||
updateApiInstances();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getApiUrl(path: string, defaultPort: number): string {
|
function getApiUrl(path: string, defaultPort: number): string {
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
// Auto-detect HTTPS in development
|
|
||||||
const protocol = window.location.protocol === "https:" ? "https" : "http";
|
const protocol = window.location.protocol === "https:" ? "https" : "http";
|
||||||
const sslPort = protocol === "https" ? 8443 : defaultPort;
|
const sslPort = protocol === "https" ? 8443 : defaultPort;
|
||||||
return `${protocol}://${apiHost}:${sslPort}${path}`;
|
return `${protocol}://${apiHost}:${sslPort}${path}`;
|
||||||
@@ -466,7 +462,20 @@ export let statsApi: AxiosInstance;
|
|||||||
// Authentication API (port 30001)
|
// Authentication API (port 30001)
|
||||||
export let authApi: AxiosInstance;
|
export let authApi: AxiosInstance;
|
||||||
|
|
||||||
|
if (isElectron()) {
|
||||||
|
getServerConfig().then((config) => {
|
||||||
|
if (config?.serverUrl) {
|
||||||
|
configuredServerUrl = config.serverUrl;
|
||||||
|
(window as any).configuredServerUrl = configuredServerUrl;
|
||||||
|
}
|
||||||
initializeApiInstances();
|
initializeApiInstances();
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error("Failed to load server config, initializing with default:", error);
|
||||||
|
initializeApiInstances();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
initializeApiInstances();
|
||||||
|
}
|
||||||
|
|
||||||
function updateApiInstances() {
|
function updateApiInstances() {
|
||||||
systemLogger.info("Updating API instances with new server configuration", {
|
systemLogger.info("Updating API instances with new server configuration", {
|
||||||
@@ -1518,8 +1527,13 @@ export async function loginUser(
|
|||||||
): Promise<AuthResponse> {
|
): Promise<AuthResponse> {
|
||||||
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) {
|
||||||
|
localStorage.setItem("jwt", response.data.token);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
token: "cookie-based",
|
token: response.data.token || "cookie-based",
|
||||||
success: response.data.success,
|
success: response.data.success,
|
||||||
is_admin: response.data.is_admin,
|
is_admin: response.data.is_admin,
|
||||||
username: response.data.username,
|
username: response.data.username,
|
||||||
|
|||||||
Reference in New Issue
Block a user
Globally ignoring all certificate and SSL errors is a significant security risk. This makes the application vulnerable to Man-in-the-Middle (MITM) attacks, as it will trust any certificate, including malicious ones. While this might be intended for connecting to self-hosted instances with self-signed certificates, it should not be enabled by default for all connections. Consider making this a user-configurable setting that is disabled by default, and perhaps apply it more granularly only to user-defined server connections rather than globally.