Fix electron version checking
This commit is contained in:
@@ -920,17 +920,25 @@ router.post("/login", async (req, res) => {
|
||||
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
|
||||
.cookie(
|
||||
"jwt",
|
||||
token,
|
||||
authManager.getSecureCookieOptions(req, 24 * 60 * 60 * 1000),
|
||||
)
|
||||
.json({
|
||||
success: true,
|
||||
is_admin: !!userRecord.is_admin,
|
||||
username: userRecord.username,
|
||||
});
|
||||
.json(response);
|
||||
} catch (err) {
|
||||
authLogger.error("Failed to log in user", err);
|
||||
return res.status(500).json({ error: "Login failed" });
|
||||
@@ -1499,6 +1507,7 @@ router.post("/totp/verify-login", async (req, res) => {
|
||||
success: true,
|
||||
is_admin: !!userRecord.is_admin,
|
||||
username: userRecord.username,
|
||||
token: req.headers['x-electron-app'] === 'true' ? token : undefined,
|
||||
});
|
||||
} catch (err) {
|
||||
authLogger.error("TOTP verification failed", err);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert.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";
|
||||
|
||||
interface VersionAlertProps {
|
||||
@@ -21,16 +21,12 @@ interface VersionAlertProps {
|
||||
cache_age?: number;
|
||||
error?: string;
|
||||
};
|
||||
onDismiss?: () => void;
|
||||
onDownload?: () => void;
|
||||
showDismiss?: boolean;
|
||||
}
|
||||
|
||||
export function VersionAlert({
|
||||
updateInfo,
|
||||
onDismiss,
|
||||
onDownload,
|
||||
showDismiss = true,
|
||||
}: VersionAlertProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -106,18 +102,6 @@ export function VersionAlert({
|
||||
{t("versionCheck.downloadUpdate")}
|
||||
</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>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
@@ -8,9 +8,10 @@ import { checkElectronUpdate, isElectron } from "@/ui/main-axios.ts";
|
||||
interface VersionCheckModalProps {
|
||||
onDismiss: () => void;
|
||||
onContinue: () => void;
|
||||
isAuthenticated?: boolean;
|
||||
}
|
||||
|
||||
export function VersionCheckModal({ onDismiss, onContinue }: VersionCheckModalProps) {
|
||||
export function VersionCheckModal({ onDismiss, onContinue, isAuthenticated = false }: VersionCheckModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const [versionInfo, setVersionInfo] = useState<any>(null);
|
||||
const [versionChecking, setVersionChecking] = useState(false);
|
||||
@@ -29,6 +30,11 @@ export function VersionCheckModal({ onDismiss, onContinue }: VersionCheckModalPr
|
||||
try {
|
||||
const updateInfo = await checkElectronUpdate();
|
||||
setVersionInfo(updateInfo);
|
||||
|
||||
if (updateInfo?.status === "up_to_date") {
|
||||
onContinue();
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to check for updates:", error);
|
||||
setVersionInfo({ success: false, error: "Check failed" });
|
||||
@@ -57,8 +63,25 @@ export function VersionCheckModal({ onDismiss, onContinue }: VersionCheckModalPr
|
||||
|
||||
if (versionChecking && !versionInfo) {
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/50 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">
|
||||
<div className="fixed inset-0 flex items-center justify-center z-50">
|
||||
{!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="w-8 h-8 border-2 border-primary border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
@@ -70,53 +93,47 @@ export function VersionCheckModal({ onDismiss, onContinue }: VersionCheckModalPr
|
||||
);
|
||||
}
|
||||
|
||||
if (!versionInfo || versionInfo.status === "up_to_date" || versionDismissed) {
|
||||
|
||||
if (!versionInfo || versionDismissed) {
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/50 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">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="fixed inset-0 flex items-center justify-center z-50">
|
||||
{!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="mb-4">
|
||||
<h2 className="text-lg font-semibold">
|
||||
{t("versionCheck.checkUpdates")}
|
||||
</h2>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={onDismiss}
|
||||
className="h-6 w-6 p-0"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{versionInfo && !versionDismissed && (
|
||||
<div className="mb-4">
|
||||
<VersionAlert
|
||||
updateInfo={versionInfo}
|
||||
onDismiss={handleVersionDismiss}
|
||||
onDownload={handleDownloadUpdate}
|
||||
showDismiss={true}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<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
|
||||
onClick={handleContinue}
|
||||
className="flex-1"
|
||||
className="flex-1 h-10"
|
||||
>
|
||||
{t("common.continue")}
|
||||
</Button>
|
||||
@@ -127,49 +144,42 @@ export function VersionCheckModal({ onDismiss, onContinue }: VersionCheckModalPr
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/50 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">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="fixed inset-0 flex items-center justify-center z-50">
|
||||
{!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="mb-4">
|
||||
<h2 className="text-lg font-semibold">
|
||||
{t("versionCheck.updateRequired")}
|
||||
</h2>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={onDismiss}
|
||||
className="h-6 w-6 p-0"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<VersionAlert
|
||||
updateInfo={versionInfo}
|
||||
onDismiss={handleVersionDismiss}
|
||||
onDownload={handleDownloadUpdate}
|
||||
showDismiss={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<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
|
||||
onClick={handleContinue}
|
||||
className="flex-1"
|
||||
className="flex-1 h-10"
|
||||
>
|
||||
{t("common.continue")}
|
||||
</Button>
|
||||
|
||||
@@ -229,13 +229,16 @@
|
||||
"checkUpdates": "Check for Updates",
|
||||
"checkingUpdates": "Checking for updates...",
|
||||
"refresh": "Refresh",
|
||||
"updateRequired": "Update Required"
|
||||
"updateRequired": "Update Required",
|
||||
"updateDismissed": "Update notification dismissed",
|
||||
"noUpdatesFound": "No updates found"
|
||||
},
|
||||
"common": {
|
||||
"close": "Close",
|
||||
"minimize": "Minimize",
|
||||
"online": "Online",
|
||||
"offline": "Offline",
|
||||
"continue": "Continue",
|
||||
"maintenance": "Maintenance",
|
||||
"degraded": "Degraded",
|
||||
"discord": "Discord",
|
||||
|
||||
@@ -227,13 +227,16 @@
|
||||
"checkUpdates": "检查更新",
|
||||
"checkingUpdates": "正在检查更新...",
|
||||
"refresh": "刷新",
|
||||
"updateRequired": "需要更新"
|
||||
"updateRequired": "需要更新",
|
||||
"updateDismissed": "更新通知已忽略",
|
||||
"noUpdatesFound": "未找到更新"
|
||||
},
|
||||
"common": {
|
||||
"close": "关闭",
|
||||
"minimize": "最小化",
|
||||
"online": "在线",
|
||||
"offline": "离线",
|
||||
"continue": "继续",
|
||||
"maintenance": "维护中",
|
||||
"degraded": "降级",
|
||||
"discord": "Discord",
|
||||
|
||||
@@ -273,7 +273,7 @@ export function AdminSettings({
|
||||
try {
|
||||
const apiUrl = isElectron()
|
||||
? `${(window as any).configuredServerUrl}/database/export`
|
||||
: "http://localhost:30001/database/export";
|
||||
: "/database/export";
|
||||
|
||||
const response = await fetch(apiUrl, {
|
||||
method: "POST",
|
||||
@@ -333,7 +333,7 @@ export function AdminSettings({
|
||||
try {
|
||||
const apiUrl = isElectron()
|
||||
? `${(window as any).configuredServerUrl}/database/import`
|
||||
: "http://localhost:30001/database/import";
|
||||
: "/database/import";
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", importFile);
|
||||
|
||||
@@ -100,10 +100,11 @@ function AppContent() {
|
||||
<VersionCheckModal
|
||||
onDismiss={() => setShowVersionCheck(false)}
|
||||
onContinue={() => setShowVersionCheck(false)}
|
||||
isAuthenticated={isAuthenticated}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!isAuthenticated && !authLoading && (
|
||||
{!isAuthenticated && !authLoading && !showVersionCheck && (
|
||||
<div>
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
@@ -123,7 +124,7 @@ function AppContent() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isAuthenticated && !authLoading && (
|
||||
{!isAuthenticated && !authLoading && !showVersionCheck && (
|
||||
<div className="fixed inset-0 flex items-center justify-center z-[10000]">
|
||||
<Homepage
|
||||
onSelectView={handleSelectView}
|
||||
|
||||
@@ -696,7 +696,7 @@ export function HomepageAuth({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!internalLoggedIn && !authLoading && !totpRequired && (
|
||||
{!loggedIn && !authLoading && !totpRequired && (
|
||||
<>
|
||||
<div className="flex gap-2 mb-6">
|
||||
<button
|
||||
@@ -963,7 +963,7 @@ export function HomepageAuth({
|
||||
className="h-11 text-base"
|
||||
value={localUsername}
|
||||
onChange={(e) => setLocalUsername(e.target.value)}
|
||||
disabled={loading || internalLoggedIn}
|
||||
disabled={loading || loggedIn}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
@@ -974,7 +974,7 @@ export function HomepageAuth({
|
||||
className="h-11 text-base"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
disabled={loading || internalLoggedIn}
|
||||
disabled={loading || loggedIn}
|
||||
/>
|
||||
</div>
|
||||
{tab === "signup" && (
|
||||
@@ -988,7 +988,7 @@ export function HomepageAuth({
|
||||
className="h-11 text-base"
|
||||
value={signupConfirmPassword}
|
||||
onChange={(e) => setSignupConfirmPassword(e.target.value)}
|
||||
disabled={loading || internalLoggedIn}
|
||||
disabled={loading || loggedIn}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -1008,7 +1008,7 @@ export function HomepageAuth({
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="w-full h-11 text-base font-semibold"
|
||||
disabled={loading || internalLoggedIn}
|
||||
disabled={loading || loggedIn}
|
||||
onClick={() => {
|
||||
setTab("reset");
|
||||
resetPasswordState();
|
||||
|
||||
@@ -142,7 +142,7 @@ function createApiInstance(
|
||||
baseURL,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
timeout: 30000,
|
||||
withCredentials: true, // Required for HttpOnly cookies to be sent cross-origin
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
instance.interceptors.request.use((config) => {
|
||||
@@ -168,13 +168,14 @@ function createApiInstance(
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
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()) {
|
||||
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;
|
||||
@@ -300,6 +301,10 @@ function createApiInstance(
|
||||
// ============================================================================
|
||||
|
||||
function isDev(): boolean {
|
||||
if (isElectron()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
process.env.NODE_ENV === "development" &&
|
||||
(window.location.port === "3000" ||
|
||||
@@ -347,6 +352,7 @@ export async function saveServerConfig(config: ServerConfig): Promise<boolean> {
|
||||
);
|
||||
if (result?.success) {
|
||||
configuredServerUrl = config.serverUrl;
|
||||
(window as any).configuredServerUrl = configuredServerUrl;
|
||||
updateApiInstances();
|
||||
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 {
|
||||
if (isDev()) {
|
||||
// Auto-detect HTTPS in development
|
||||
const protocol = window.location.protocol === "https:" ? "https" : "http";
|
||||
const sslPort = protocol === "https" ? 8443 : defaultPort;
|
||||
return `${protocol}://${apiHost}:${sslPort}${path}`;
|
||||
@@ -466,7 +462,20 @@ export let statsApi: AxiosInstance;
|
||||
// Authentication API (port 30001)
|
||||
export let authApi: AxiosInstance;
|
||||
|
||||
initializeApiInstances();
|
||||
if (isElectron()) {
|
||||
getServerConfig().then((config) => {
|
||||
if (config?.serverUrl) {
|
||||
configuredServerUrl = config.serverUrl;
|
||||
(window as any).configuredServerUrl = configuredServerUrl;
|
||||
}
|
||||
initializeApiInstances();
|
||||
}).catch((error) => {
|
||||
console.error("Failed to load server config, initializing with default:", error);
|
||||
initializeApiInstances();
|
||||
});
|
||||
} else {
|
||||
initializeApiInstances();
|
||||
}
|
||||
|
||||
function updateApiInstances() {
|
||||
systemLogger.info("Updating API instances with new server configuration", {
|
||||
@@ -1518,8 +1527,13 @@ export async function loginUser(
|
||||
): Promise<AuthResponse> {
|
||||
try {
|
||||
const response = await authApi.post("/users/login", { username, password });
|
||||
|
||||
if (isElectron() && response.data.token) {
|
||||
localStorage.setItem("jwt", response.data.token);
|
||||
}
|
||||
|
||||
return {
|
||||
token: "cookie-based",
|
||||
token: response.data.token || "cookie-based",
|
||||
success: response.data.success,
|
||||
is_admin: response.data.is_admin,
|
||||
username: response.data.username,
|
||||
|
||||
Reference in New Issue
Block a user