mirror of
https://github.com/9technologygroup/patchmon.net.git
synced 2025-10-22 23:32:03 +00:00
Setup Redis passwords to be used in Vm installation or via Docker
Setup so that CORS_ORIGIN error appears on the frontend to help new installations
This commit is contained in:
34
.dockerignore
Normal file
34
.dockerignore
Normal file
@@ -0,0 +1,34 @@
|
||||
# Environment files
|
||||
**/.env
|
||||
**/.env.*
|
||||
**/env.example
|
||||
|
||||
# Node modules
|
||||
**/node_modules
|
||||
|
||||
# Logs
|
||||
**/logs
|
||||
**/*.log
|
||||
|
||||
# Git
|
||||
**/.git
|
||||
**/.gitignore
|
||||
|
||||
# IDE files
|
||||
**/.vscode
|
||||
**/.idea
|
||||
**/*.swp
|
||||
**/*.swo
|
||||
|
||||
# OS files
|
||||
**/.DS_Store
|
||||
**/Thumbs.db
|
||||
|
||||
# Build artifacts
|
||||
**/dist
|
||||
**/build
|
||||
**/coverage
|
||||
|
||||
# Temporary files
|
||||
**/tmp
|
||||
**/temp
|
@@ -6,6 +6,7 @@ PM_DB_CONN_WAIT_INTERVAL=2
|
||||
# Redis Configuration
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
REDIS_USER=your-redis-username-here
|
||||
REDIS_PASSWORD=your-redis-password-here
|
||||
REDIS_DB=0
|
||||
|
||||
|
@@ -179,7 +179,7 @@ model settings {
|
||||
updated_at DateTime
|
||||
update_interval Int @default(60)
|
||||
auto_update Boolean @default(false)
|
||||
github_repo_url String @default("git@github.com:9technologygroup/patchmon.net.git")
|
||||
github_repo_url String @default("https://github.com/PatchMon/PatchMon.git")
|
||||
ssh_key_path String?
|
||||
repository_type String @default("public")
|
||||
last_update_check DateTime?
|
||||
|
@@ -6,7 +6,7 @@ const { PrismaClient } = require("@prisma/client");
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Default GitHub repository URL
|
||||
const DEFAULT_GITHUB_REPO = "https://github.com/patchMon/patchmon";
|
||||
const DEFAULT_GITHUB_REPO = "https://github.com/PatchMon/PatchMon.git";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
|
@@ -339,9 +339,7 @@ const parseOrigins = (val) =>
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
const allowedOrigins = parseOrigins(
|
||||
process.env.CORS_ORIGINS ||
|
||||
process.env.CORS_ORIGIN ||
|
||||
"http://localhost:3000",
|
||||
process.env.CORS_ORIGINS || process.env.CORS_ORIGIN || "http://fabio:3000",
|
||||
);
|
||||
app.use(
|
||||
cors({
|
||||
@@ -564,6 +562,15 @@ app.use((err, _req, res, _next) => {
|
||||
if (process.env.ENABLE_LOGGING === "true") {
|
||||
logger.error(err.stack);
|
||||
}
|
||||
|
||||
// Special handling for CORS errors - always include the message
|
||||
if (err.message?.includes("Not allowed by CORS")) {
|
||||
return res.status(500).json({
|
||||
error: "Something went wrong!",
|
||||
message: err.message, // Always include CORS error message
|
||||
});
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
error: "Something went wrong!",
|
||||
message: process.env.NODE_ENV === "development" ? err.message : undefined,
|
||||
|
@@ -21,7 +21,7 @@ class GitHubUpdateCheck {
|
||||
try {
|
||||
// Get settings
|
||||
const settings = await prisma.settings.findFirst();
|
||||
const DEFAULT_GITHUB_REPO = "https://github.com/patchMon/patchmon";
|
||||
const DEFAULT_GITHUB_REPO = "https://github.com/PatchMon/PatchMon.git";
|
||||
const repoUrl = settings?.githubRepoUrl || DEFAULT_GITHUB_REPO;
|
||||
let owner, repo;
|
||||
|
||||
|
@@ -5,6 +5,7 @@ const redisConnection = {
|
||||
host: process.env.REDIS_HOST || "localhost",
|
||||
port: parseInt(process.env.REDIS_PORT, 10) || 6379,
|
||||
password: process.env.REDIS_PASSWORD || undefined,
|
||||
username: process.env.REDIS_USER || undefined,
|
||||
db: parseInt(process.env.REDIS_DB, 10) || 0,
|
||||
retryDelayOnFailover: 100,
|
||||
maxRetriesPerRequest: null, // BullMQ requires this to be null
|
||||
|
@@ -1 +1,3 @@
|
||||
**/env.example
|
||||
**/.env
|
||||
**/.env.*
|
||||
|
@@ -1,3 +1,19 @@
|
||||
# Change 3 Passwords in this file:
|
||||
# Generate passwords with 'openssl rand -hex 64'
|
||||
#
|
||||
# 1. The database password in the environment variable POSTGRES_PASSWORD
|
||||
# 2. The redis password in the command redis-server --requirepass your-redis-password-here
|
||||
# 3. The jwt secret in the environment variable JWT_SECRET
|
||||
#
|
||||
#
|
||||
# Change 2 URL areas in this file:
|
||||
# 1. Setup your CORS_ORIGIN to what url you will use for accessing PatchMon frontend url
|
||||
# 2. Setup your SERVER_PROTOCOL, SERVER_HOST and SERVER_PORT to what you will use for linux agents to access PatchMon
|
||||
#
|
||||
# This is generally the same as your CORS_ORIGIN url , in some cases it might be different - SERVER_* variables are used in the scripts for Server connection.
|
||||
# You can also change this in the front-end but in the case of docker-compose - it is overwritten by the variables set here.
|
||||
|
||||
|
||||
name: patchmon
|
||||
|
||||
services:
|
||||
@@ -7,7 +23,7 @@ services:
|
||||
environment:
|
||||
POSTGRES_DB: patchmon_db
|
||||
POSTGRES_USER: patchmon_user
|
||||
POSTGRES_PASSWORD: # CREATE A STRONG PASSWORD AND PUT IT HERE
|
||||
POSTGRES_PASSWORD: # CREATE A STRONG DB PASSWORD AND PUT IT HERE
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
@@ -19,11 +35,11 @@ services:
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
restart: unless-stopped
|
||||
command: redis-server --requirepass your-redis-password-here
|
||||
command: redis-server --requirepass your-redis-password-here # CHANGE THIS TO YOUR REDIS PASSWORD
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "--no-auth-warning", "-a", "your-redis-password-here", "ping"]
|
||||
test: ["CMD", "redis-cli", "--no-auth-warning", "-a", "your-redis-password-here", "ping"] # CHANGE THIS TO YOUR REDIS PASSWORD
|
||||
interval: 3s
|
||||
timeout: 5s
|
||||
retries: 7
|
||||
@@ -35,7 +51,7 @@ services:
|
||||
environment:
|
||||
LOG_LEVEL: info
|
||||
DATABASE_URL: postgresql://patchmon_user:REPLACE_YOUR_POSTGRES_PASSWORD_HERE@database:5432/patchmon_db
|
||||
JWT_SECRET: # CREATE A STRONG SECRET AND PUT IT HERE - Generate with 'openssl rand -hex 64'
|
||||
JWT_SECRET: # CREATE A STRONG SECRET AND PUT IT HERE
|
||||
SERVER_PROTOCOL: http
|
||||
SERVER_HOST: localhost
|
||||
SERVER_PORT: 3000
|
||||
|
@@ -41,7 +41,7 @@ server {
|
||||
# Preserve original client IP through proxy chain
|
||||
proxy_set_header X-Original-Forwarded-For $http_x_forwarded_for;
|
||||
|
||||
# CORS headers for API calls
|
||||
# CORS headers for API calls - even though backend is doing it
|
||||
add_header Access-Control-Allow-Origin * always;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
|
||||
add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization" always;
|
||||
@@ -77,10 +77,10 @@ server {
|
||||
proxy_request_buffering off;
|
||||
proxy_max_temp_file_size 0;
|
||||
|
||||
# CORS headers for SSE
|
||||
add_header Access-Control-Allow-Origin * always;
|
||||
add_header Access-Control-Allow-Methods "GET, OPTIONS" always;
|
||||
add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization" always;
|
||||
# CORS headers for SSE - commented out to let backend handle CORS
|
||||
# add_header Access-Control-Allow-Origin * always;
|
||||
# add_header Access-Control-Allow-Methods "GET, OPTIONS" always;
|
||||
# add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization" always;
|
||||
|
||||
# Handle preflight requests
|
||||
if ($request_method = 'OPTIONS') {
|
||||
|
@@ -1,35 +0,0 @@
|
||||
# Redis Configuration for PatchMon Production
|
||||
# Security settings
|
||||
# requirepass ${REDIS_PASSWORD} # Disabled - using command-line password instead
|
||||
rename-command FLUSHDB ""
|
||||
rename-command FLUSHALL ""
|
||||
rename-command DEBUG ""
|
||||
rename-command CONFIG "CONFIG_DISABLED"
|
||||
|
||||
# Memory management
|
||||
maxmemory 256mb
|
||||
maxmemory-policy allkeys-lru
|
||||
|
||||
# Persistence settings
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
|
||||
# Logging
|
||||
loglevel notice
|
||||
logfile ""
|
||||
|
||||
# Network security
|
||||
bind 127.0.0.1
|
||||
protected-mode yes
|
||||
|
||||
# Performance tuning
|
||||
tcp-keepalive 300
|
||||
timeout 0
|
||||
|
||||
# Disable dangerous commands
|
||||
rename-command SHUTDOWN "SHUTDOWN_DISABLED"
|
||||
rename-command KEYS ""
|
||||
rename-command MONITOR ""
|
||||
rename-command SLAVEOF ""
|
||||
rename-command REPLICAOF ""
|
@@ -2,6 +2,7 @@ import { AlertCircle, CheckCircle, Shield, UserPlus } from "lucide-react";
|
||||
import { useId, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useAuth } from "../contexts/AuthContext";
|
||||
import { isCorsError } from "../utils/api";
|
||||
|
||||
const FirstTimeAdminSetup = () => {
|
||||
const { login, setAuthState } = useAuth();
|
||||
@@ -121,11 +122,39 @@ const FirstTimeAdminSetup = () => {
|
||||
}, 2000);
|
||||
}
|
||||
} else {
|
||||
setError(data.error || "Failed to create admin user");
|
||||
// Handle HTTP error responses (like 500 CORS errors)
|
||||
console.log("HTTP error response:", response.status, data);
|
||||
|
||||
// Check if this is a CORS error based on the response data
|
||||
if (
|
||||
data.message?.includes("Not allowed by CORS") ||
|
||||
data.message?.includes("CORS") ||
|
||||
data.error?.includes("CORS")
|
||||
) {
|
||||
setError(
|
||||
"CORS_ORIGIN mismatch - please set your URL in your environment variable",
|
||||
);
|
||||
} else {
|
||||
setError(data.error || "Failed to create admin user");
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Setup error:", error);
|
||||
setError("Network error. Please try again.");
|
||||
// Check for CORS/network errors first
|
||||
if (isCorsError(error)) {
|
||||
setError(
|
||||
"CORS_ORIGIN mismatch - please set your URL in your environment variable",
|
||||
);
|
||||
} else if (
|
||||
error.name === "TypeError" &&
|
||||
error.message?.includes("Failed to fetch")
|
||||
) {
|
||||
setError(
|
||||
"CORS_ORIGIN mismatch - please set your URL in your environment variable",
|
||||
);
|
||||
} else {
|
||||
setError("Network error. Please try again.");
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ import {
|
||||
} from "react";
|
||||
import { flushSync } from "react-dom";
|
||||
import { AUTH_PHASES, isAuthPhase } from "../constants/authPhases";
|
||||
import { isCorsError } from "../utils/api";
|
||||
|
||||
const AuthContext = createContext();
|
||||
|
||||
@@ -120,9 +121,50 @@ export const AuthProvider = ({ children }) => {
|
||||
|
||||
return { success: true };
|
||||
} else {
|
||||
// Handle HTTP error responses (like 500 CORS errors)
|
||||
console.log("HTTP error response:", response.status, data);
|
||||
|
||||
// Check if this is a CORS error based on the response data
|
||||
if (
|
||||
data.message?.includes("Not allowed by CORS") ||
|
||||
data.message?.includes("CORS") ||
|
||||
data.error?.includes("CORS")
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
"CORS_ORIGIN mismatch - please set your URL in your environment variable",
|
||||
};
|
||||
}
|
||||
|
||||
return { success: false, error: data.error || "Login failed" };
|
||||
}
|
||||
} catch {
|
||||
} catch (error) {
|
||||
console.log("Login error:", error);
|
||||
console.log("Error response:", error.response);
|
||||
console.log("Error message:", error.message);
|
||||
|
||||
// Check for CORS/network errors first
|
||||
if (isCorsError(error)) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
"CORS_ORIGIN mismatch - please set your URL in your environment variable",
|
||||
};
|
||||
}
|
||||
|
||||
// Check for other network errors
|
||||
if (
|
||||
error.name === "TypeError" &&
|
||||
error.message?.includes("Failed to fetch")
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
"CORS_ORIGIN mismatch - please set your URL in your environment variable",
|
||||
};
|
||||
}
|
||||
|
||||
return { success: false, error: "Network error occurred" };
|
||||
}
|
||||
};
|
||||
@@ -167,9 +209,46 @@ export const AuthProvider = ({ children }) => {
|
||||
localStorage.setItem("user", JSON.stringify(data.user));
|
||||
return { success: true, user: data.user };
|
||||
} else {
|
||||
// Handle HTTP error responses (like 500 CORS errors)
|
||||
console.log("HTTP error response:", response.status, data);
|
||||
|
||||
// Check if this is a CORS error based on the response data
|
||||
if (
|
||||
data.message?.includes("Not allowed by CORS") ||
|
||||
data.message?.includes("CORS") ||
|
||||
data.error?.includes("CORS")
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
"CORS_ORIGIN mismatch - please set your URL in your environment variable",
|
||||
};
|
||||
}
|
||||
|
||||
return { success: false, error: data.error || "Update failed" };
|
||||
}
|
||||
} catch {
|
||||
} catch (error) {
|
||||
// Check for CORS/network errors first
|
||||
if (isCorsError(error)) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
"CORS_ORIGIN mismatch - please set your URL in your environment variable",
|
||||
};
|
||||
}
|
||||
|
||||
// Check for other network errors
|
||||
if (
|
||||
error.name === "TypeError" &&
|
||||
error.message?.includes("Failed to fetch")
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
"CORS_ORIGIN mismatch - please set your URL in your environment variable",
|
||||
};
|
||||
}
|
||||
|
||||
return { success: false, error: "Network error occurred" };
|
||||
}
|
||||
};
|
||||
@@ -190,12 +269,49 @@ export const AuthProvider = ({ children }) => {
|
||||
if (response.ok) {
|
||||
return { success: true };
|
||||
} else {
|
||||
// Handle HTTP error responses (like 500 CORS errors)
|
||||
console.log("HTTP error response:", response.status, data);
|
||||
|
||||
// Check if this is a CORS error based on the response data
|
||||
if (
|
||||
data.message?.includes("Not allowed by CORS") ||
|
||||
data.message?.includes("CORS") ||
|
||||
data.error?.includes("CORS")
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
"CORS_ORIGIN mismatch - please set your URL in your environment variable",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: data.error || "Password change failed",
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
} catch (error) {
|
||||
// Check for CORS/network errors first
|
||||
if (isCorsError(error)) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
"CORS_ORIGIN mismatch - please set your URL in your environment variable",
|
||||
};
|
||||
}
|
||||
|
||||
// Check for other network errors
|
||||
if (
|
||||
error.name === "TypeError" &&
|
||||
error.message?.includes("Failed to fetch")
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
"CORS_ORIGIN mismatch - please set your URL in your environment variable",
|
||||
};
|
||||
}
|
||||
|
||||
return { success: false, error: "Network error occurred" };
|
||||
}
|
||||
};
|
||||
|
@@ -13,7 +13,7 @@ import { useEffect, useId, useState } from "react";
|
||||
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useAuth } from "../contexts/AuthContext";
|
||||
import { authAPI } from "../utils/api";
|
||||
import { authAPI, isCorsError } from "../utils/api";
|
||||
|
||||
const Login = () => {
|
||||
const usernameId = useId();
|
||||
@@ -82,7 +82,21 @@ const Login = () => {
|
||||
setError(result.error || "Login failed");
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err.response?.data?.error || "Login failed");
|
||||
// Check for CORS/network errors first
|
||||
if (isCorsError(err)) {
|
||||
setError(
|
||||
"CORS_ORIGIN mismatch - please set your URL in your environment variable",
|
||||
);
|
||||
} else if (
|
||||
err.name === "TypeError" &&
|
||||
err.message?.includes("Failed to fetch")
|
||||
) {
|
||||
setError(
|
||||
"CORS_ORIGIN mismatch - please set your URL in your environment variable",
|
||||
);
|
||||
} else {
|
||||
setError(err.response?.data?.error || "Login failed");
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -112,12 +126,25 @@ const Login = () => {
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Signup error:", err);
|
||||
const errorMessage =
|
||||
err.response?.data?.error ||
|
||||
(err.response?.data?.errors && err.response.data.errors.length > 0
|
||||
? err.response.data.errors.map((e) => e.msg).join(", ")
|
||||
: err.message || "Signup failed");
|
||||
setError(errorMessage);
|
||||
if (isCorsError(err)) {
|
||||
setError(
|
||||
"CORS_ORIGIN mismatch - please set your URL in your environment variable",
|
||||
);
|
||||
} else if (
|
||||
err.name === "TypeError" &&
|
||||
err.message?.includes("Failed to fetch")
|
||||
) {
|
||||
setError(
|
||||
"CORS_ORIGIN mismatch - please set your URL in your environment variable",
|
||||
);
|
||||
} else {
|
||||
const errorMessage =
|
||||
err.response?.data?.error ||
|
||||
(err.response?.data?.errors && err.response.data.errors.length > 0
|
||||
? err.response.data.errors.map((e) => e.msg).join(", ")
|
||||
: err.message || "Signup failed");
|
||||
setError(errorMessage);
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -146,9 +173,22 @@ const Login = () => {
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("TFA verification error:", err);
|
||||
const errorMessage =
|
||||
err.response?.data?.error || err.message || "TFA verification failed";
|
||||
setError(errorMessage);
|
||||
if (isCorsError(err)) {
|
||||
setError(
|
||||
"CORS_ORIGIN mismatch - please set your URL in your environment variable",
|
||||
);
|
||||
} else if (
|
||||
err.name === "TypeError" &&
|
||||
err.message?.includes("Failed to fetch")
|
||||
) {
|
||||
setError(
|
||||
"CORS_ORIGIN mismatch - please set your URL in your environment variable",
|
||||
);
|
||||
} else {
|
||||
const errorMessage =
|
||||
err.response?.data?.error || err.message || "TFA verification failed";
|
||||
setError(errorMessage);
|
||||
}
|
||||
// Clear the token input for security
|
||||
setTfaData({ token: "" });
|
||||
} finally {
|
||||
|
@@ -26,7 +26,7 @@ import { useEffect, useId, useState } from "react";
|
||||
|
||||
import { useAuth } from "../contexts/AuthContext";
|
||||
import { useTheme } from "../contexts/ThemeContext";
|
||||
import { tfaAPI } from "../utils/api";
|
||||
import { isCorsError, tfaAPI } from "../utils/api";
|
||||
|
||||
const Profile = () => {
|
||||
const usernameId = useId();
|
||||
@@ -88,8 +88,15 @@ const Profile = () => {
|
||||
text: result.error || "Failed to update profile",
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
setMessage({ type: "error", text: "Network error occurred" });
|
||||
} catch (error) {
|
||||
if (isCorsError(error)) {
|
||||
setMessage({
|
||||
type: "error",
|
||||
text: "CORS_ORIGIN mismatch - please set your URL in your environment variable",
|
||||
});
|
||||
} else {
|
||||
setMessage({ type: "error", text: "Network error occurred" });
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -133,8 +140,15 @@ const Profile = () => {
|
||||
text: result.error || "Failed to change password",
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
setMessage({ type: "error", text: "Network error occurred" });
|
||||
} catch (error) {
|
||||
if (isCorsError(error)) {
|
||||
setMessage({
|
||||
type: "error",
|
||||
text: "CORS_ORIGIN mismatch - please set your URL in your environment variable",
|
||||
});
|
||||
} else {
|
||||
setMessage({ type: "error", text: "Network error occurred" });
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
@@ -144,7 +144,7 @@ const Settings = () => {
|
||||
defaultUserRole: settings.default_user_role || "user",
|
||||
githubRepoUrl:
|
||||
settings.github_repo_url ||
|
||||
"git@github.com:9technologygroup/patchmon.net.git",
|
||||
"https://github.com/PatchMon/PatchMon.git",
|
||||
repositoryType: settings.repository_type || "public",
|
||||
sshKeyPath: settings.ssh_key_path || "",
|
||||
useCustomSshKey: !!settings.ssh_key_path,
|
||||
|
@@ -221,7 +221,82 @@ export const packagesAPI = {
|
||||
};
|
||||
|
||||
// Utility functions
|
||||
export const isCorsError = (error) => {
|
||||
// Check for browser-level CORS errors (when request is blocked before reaching server)
|
||||
if (error.message?.includes("Failed to fetch") && !error.response) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for TypeError with Failed to fetch (common CORS error pattern)
|
||||
if (
|
||||
error.name === "TypeError" &&
|
||||
error.message?.includes("Failed to fetch")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for backend CORS errors that get converted to 500 by proxy
|
||||
if (error.response?.status === 500) {
|
||||
// Check if the error message contains CORS-related text
|
||||
if (
|
||||
error.message?.includes("Not allowed by CORS") ||
|
||||
error.message?.includes("CORS") ||
|
||||
error.message?.includes("cors")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if the response data contains CORS error information
|
||||
if (
|
||||
error.response?.data?.error?.includes("CORS") ||
|
||||
error.response?.data?.error?.includes("cors") ||
|
||||
error.response?.data?.message?.includes("CORS") ||
|
||||
error.response?.data?.message?.includes("cors") ||
|
||||
error.response?.data?.message?.includes("Not allowed by CORS")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for specific CORS error patterns from backend logs
|
||||
if (
|
||||
error.message?.includes("origin") &&
|
||||
error.message?.includes("callback")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if this is likely a CORS error based on context
|
||||
// If we're accessing from localhost but CORS_ORIGIN is set to fabio, this is likely CORS
|
||||
const currentOrigin = window.location.origin;
|
||||
if (
|
||||
currentOrigin === "http://localhost:3000" &&
|
||||
error.config?.url?.includes("/api/")
|
||||
) {
|
||||
// This is likely a CORS error when accessing from localhost
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for CORS-related errors
|
||||
return (
|
||||
error.message?.includes("CORS") ||
|
||||
error.message?.includes("cors") ||
|
||||
error.message?.includes("Access to fetch") ||
|
||||
error.message?.includes("blocked by CORS policy") ||
|
||||
error.message?.includes("Cross-Origin Request Blocked") ||
|
||||
error.message?.includes("NetworkError when attempting to fetch resource") ||
|
||||
error.message?.includes("ERR_BLOCKED_BY_CLIENT") ||
|
||||
error.message?.includes("ERR_NETWORK") ||
|
||||
error.message?.includes("ERR_CONNECTION_REFUSED")
|
||||
);
|
||||
};
|
||||
|
||||
export const formatError = (error) => {
|
||||
// Check for CORS-related errors
|
||||
if (isCorsError(error)) {
|
||||
return "CORS_ORIGIN mismatch - please set your URL in your environment variable";
|
||||
}
|
||||
|
||||
if (error.response?.data?.message) {
|
||||
return error.response.data.message;
|
||||
}
|
||||
|
234
setup-redis.sh
Executable file
234
setup-redis.sh
Executable file
@@ -0,0 +1,234 @@
|
||||
#!/bin/bash
|
||||
|
||||
# redis-setup.sh - Redis Database and User Setup for PatchMon
|
||||
# This script creates a dedicated Redis database and user for a PatchMon instance
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Default Redis connection details
|
||||
REDIS_HOST=${REDIS_HOST:-"localhost"}
|
||||
REDIS_PORT=${REDIS_PORT:-6379}
|
||||
REDIS_ADMIN_PASSWORD=${REDIS_ADMIN_PASSWORD:-"redispass1"}
|
||||
|
||||
echo -e "${BLUE}🔧 PatchMon Redis Setup${NC}"
|
||||
echo "=================================="
|
||||
|
||||
# Function to generate random strings
|
||||
generate_random_string() {
|
||||
local length=${1:-16}
|
||||
openssl rand -base64 $length | tr -d "=+/" | cut -c1-$length
|
||||
}
|
||||
|
||||
# Function to check if Redis is accessible
|
||||
check_redis_connection() {
|
||||
echo -e "${YELLOW}📡 Checking Redis connection...${NC}"
|
||||
|
||||
if [ -n "$REDIS_ADMIN_PASSWORD" ]; then
|
||||
# With password
|
||||
if redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" -a "$REDIS_ADMIN_PASSWORD" --no-auth-warning ping > /dev/null 2>&1; then
|
||||
echo -e "${GREEN}✅ Redis connection successful${NC}"
|
||||
return 0
|
||||
else
|
||||
echo -e "${RED}❌ Cannot connect to Redis with password${NC}"
|
||||
echo "Please ensure Redis is running and the admin password is correct"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
# Without password
|
||||
if redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" ping > /dev/null 2>&1; then
|
||||
echo -e "${GREEN}✅ Redis connection successful${NC}"
|
||||
return 0
|
||||
else
|
||||
echo -e "${RED}❌ Cannot connect to Redis${NC}"
|
||||
echo "Please ensure Redis is running"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to find next available database number
|
||||
find_next_db() {
|
||||
echo -e "${YELLOW}🔍 Finding next available database...${NC}" >&2
|
||||
|
||||
# Start from database 0 and keep checking until we find an empty one
|
||||
local db_num=0
|
||||
local max_attempts=100 # Safety limit to prevent infinite loop
|
||||
|
||||
while [ $db_num -lt $max_attempts ]; do
|
||||
# Test if database is empty
|
||||
local key_count
|
||||
local redis_output
|
||||
|
||||
if [ -n "$REDIS_ADMIN_PASSWORD" ]; then
|
||||
# With password
|
||||
redis_output=$(redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" -a "$REDIS_ADMIN_PASSWORD" --no-auth-warning -n "$db_num" DBSIZE 2>&1)
|
||||
else
|
||||
# Without password
|
||||
redis_output=$(redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" -n "$db_num" DBSIZE 2>&1)
|
||||
fi
|
||||
|
||||
# Check for authentication errors
|
||||
if echo "$redis_output" | grep -q "NOAUTH"; then
|
||||
echo -e "${RED}❌ Authentication required but REDIS_ADMIN_PASSWORD not set${NC}" >&2
|
||||
echo -e "${YELLOW}💡 Please set REDIS_ADMIN_PASSWORD environment variable:${NC}" >&2
|
||||
echo -e "${YELLOW} export REDIS_ADMIN_PASSWORD='your_password'${NC}" >&2
|
||||
echo -e "${YELLOW} Or run: REDIS_ADMIN_PASSWORD='your_password' ./setup-redis.sh${NC}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for other errors
|
||||
if echo "$redis_output" | grep -q "ERR"; then
|
||||
if echo "$redis_output" | grep -q "invalid DB index"; then
|
||||
echo -e "${RED}❌ Reached maximum database limit at database $db_num${NC}" >&2
|
||||
echo -e "${YELLOW}💡 Redis is configured with $db_num databases maximum.${NC}" >&2
|
||||
echo -e "${YELLOW}💡 Increase 'databases' setting in redis.conf or clean up unused databases.${NC}" >&2
|
||||
exit 1
|
||||
else
|
||||
echo -e "${RED}❌ Error checking database $db_num: $redis_output${NC}" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
key_count="$redis_output"
|
||||
|
||||
# If database is empty, use it
|
||||
if [ "$key_count" = "0" ]; then
|
||||
echo -e "${GREEN}✅ Found available database: $db_num (empty)${NC}" >&2
|
||||
echo "$db_num"
|
||||
return
|
||||
fi
|
||||
|
||||
echo -e "${BLUE} Database $db_num has $key_count keys, checking next...${NC}" >&2
|
||||
db_num=$((db_num + 1))
|
||||
done
|
||||
|
||||
echo -e "${RED}❌ No available databases found (checked 0-$max_attempts)${NC}" >&2
|
||||
echo -e "${YELLOW}💡 All checked databases are in use. Consider cleaning up unused databases.${NC}" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Function to create Redis user
|
||||
create_redis_user() {
|
||||
local username="$1"
|
||||
local password="$2"
|
||||
local db_num="$3"
|
||||
|
||||
echo -e "${YELLOW}👤 Creating Redis user: $username for database $db_num${NC}"
|
||||
|
||||
# Create user with password and permissions
|
||||
# Note: >password syntax is for Redis ACL, we need to properly escape it
|
||||
if [ -n "$REDIS_ADMIN_PASSWORD" ]; then
|
||||
# With password
|
||||
redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" -a "$REDIS_ADMIN_PASSWORD" --no-auth-warning ACL SETUSER "$username" on ">${password}" ~* +@all > /dev/null
|
||||
else
|
||||
# Without password
|
||||
redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" ACL SETUSER "$username" on ">${password}" ~* +@all > /dev/null
|
||||
fi
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo -e "${GREEN}✅ Redis user '$username' created successfully for database $db_num${NC}"
|
||||
return 0
|
||||
else
|
||||
echo -e "${RED}❌ Failed to create Redis user${NC}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to test user connection
|
||||
test_user_connection() {
|
||||
local username="$1"
|
||||
local password="$2"
|
||||
local db_num="$3"
|
||||
|
||||
echo -e "${YELLOW}🧪 Testing user connection...${NC}"
|
||||
|
||||
if redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" --user "$username" --pass "$password" --no-auth-warning -n "$db_num" ping > /dev/null 2>&1; then
|
||||
echo -e "${GREEN}✅ User connection test successful${NC}"
|
||||
return 0
|
||||
else
|
||||
echo -e "${RED}❌ User connection test failed${NC}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to mark database as in-use
|
||||
mark_database_in_use() {
|
||||
local db_num="$1"
|
||||
|
||||
echo -e "${YELLOW}📝 Marking database as in-use...${NC}"
|
||||
|
||||
if [ -n "$REDIS_ADMIN_PASSWORD" ]; then
|
||||
redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" -a "$REDIS_ADMIN_PASSWORD" --no-auth-warning -n "$db_num" SET "patchmon:initialized" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > /dev/null
|
||||
else
|
||||
redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" -n "$db_num" SET "patchmon:initialized" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > /dev/null
|
||||
fi
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo -e "${GREEN}✅ Database marked as in-use${NC}"
|
||||
return 0
|
||||
else
|
||||
echo -e "${RED}❌ Failed to mark database${NC}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Main execution
|
||||
main() {
|
||||
# Check Redis connection
|
||||
if ! check_redis_connection; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Generate random credentials
|
||||
USERNAME="patchmon_$(generate_random_string 8)"
|
||||
PASSWORD=$(generate_random_string 32)
|
||||
DB_NUM=$(find_next_db)
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}📋 Generated Configuration:${NC}"
|
||||
echo "Username: $USERNAME"
|
||||
echo "Password: $PASSWORD"
|
||||
echo "Database: $DB_NUM"
|
||||
echo ""
|
||||
|
||||
# Create Redis user
|
||||
if ! create_redis_user "$USERNAME" "$PASSWORD" "$DB_NUM"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test user connection
|
||||
if ! test_user_connection "$USERNAME" "$PASSWORD" "$DB_NUM"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Mark database as in-use to prevent reuse on next run
|
||||
if ! mark_database_in_use "$DB_NUM"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Output .env configuration
|
||||
echo ""
|
||||
echo -e "${GREEN}🎉 Redis setup completed successfully!${NC}"
|
||||
echo ""
|
||||
echo -e "${BLUE}📄 Add these lines to your .env file:${NC}"
|
||||
echo "=================================="
|
||||
echo "REDIS_HOST=$REDIS_HOST"
|
||||
echo "REDIS_PORT=$REDIS_PORT"
|
||||
echo "REDIS_USER=$USERNAME"
|
||||
echo "REDIS_PASSWORD=$PASSWORD"
|
||||
echo "REDIS_DB=$DB_NUM"
|
||||
echo "=================================="
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}💡 Copy the configuration above to your .env file${NC}"
|
||||
}
|
||||
|
||||
# Run main function
|
||||
main "$@"
|
Reference in New Issue
Block a user