"Update API configuration and SSL certificates for enhanced security"

This commit is contained in:
Till Tomczak 2025-05-26 09:54:29 +02:00
parent 45fcc1a948
commit 201f75cfd3
9 changed files with 8308 additions and 84 deletions

View File

@ -33,10 +33,10 @@ FLASK_DEBUG = True
SESSION_LIFETIME = timedelta(days=7) SESSION_LIFETIME = timedelta(days=7)
# SSL-Konfiguration # SSL-Konfiguration
SSL_ENABLED = False SSL_ENABLED = True
SSL_CERT_PATH = "instance/ssl/myp.crt" SSL_CERT_PATH = "instance/ssl/myp.crt"
SSL_KEY_PATH = "instance/ssl/myp.key" SSL_KEY_PATH = "instance/ssl/myp.key"
SSL_HOSTNAME = "raspberrypi" SSL_HOSTNAME = "localhost"
# Scheduler-Konfiguration # Scheduler-Konfiguration
SCHEDULER_INTERVAL = 60 # Sekunden SCHEDULER_INTERVAL = 60 # Sekunden
@ -92,49 +92,21 @@ def get_ssl_context():
if not os.path.exists(SSL_CERT_PATH) or not os.path.exists(SSL_KEY_PATH): if not os.path.exists(SSL_CERT_PATH) or not os.path.exists(SSL_KEY_PATH):
ensure_ssl_directory() ensure_ssl_directory()
# Prüfen, ob wir uns im Entwicklungsmodus befinden # Im Entwicklungsmodus versuchen wir, einfache Zertifikate zu erstellen
if FLASK_DEBUG: if FLASK_DEBUG:
print("SSL-Zertifikate nicht gefunden. Erstelle selbstsignierte Zertifikate...") print("SSL-Zertifikate nicht gefunden. Erstelle einfache selbstsignierte Zertifikate...")
try:
# Auf Windows prüfen # Einfache Zertifikate mit Python erstellen
import platform create_simple_ssl_cert()
if platform.system() == "Windows":
# Unter Windows verwenden wir OpenSSL direkt über subprocess
import subprocess
try:
# Erstelle ein selbstsigniertes Zertifikat mit OpenSSL
ssl_cmd = [
"openssl", "req", "-new", "-x509", "-newkey", "rsa:2048",
"-nodes", "-keyout", SSL_KEY_PATH, "-out", SSL_CERT_PATH,
"-days", "365", "-subj", f"/CN={SSL_HOSTNAME}"
]
result = subprocess.run(ssl_cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f"Fehler beim Erstellen der SSL-Zertifikate: {result.stderr}")
# Fallback: Generiere einfache Schlüssel mit Python
create_simple_ssl_cert()
except Exception as e:
print(f"Fehler beim Ausführen von OpenSSL: {e}")
# Fallback: Generiere einfache Schlüssel mit Python
create_simple_ssl_cert()
else:
# Unter Linux das vorhandene Skript verwenden
script_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
"install", "create_ssl_cert.sh")
# Ausführungsrechte setzen # Prüfen, ob die Zertifikate erfolgreich erstellt wurden
if os.path.exists(script_path): if not os.path.exists(SSL_CERT_PATH) or not os.path.exists(SSL_KEY_PATH):
os.system(f"chmod +x {script_path}") print("Konnte keine SSL-Zertifikate erstellen.")
# Zertifikate erstellen mit spezifischem Hostnamen
os.system(f"{script_path} -c {SSL_CERT_PATH} -k {SSL_KEY_PATH} -h {SSL_HOSTNAME}")
else:
print(f"WARNUNG: SSL-Zertifikat-Generator nicht gefunden: {script_path}")
return None return None
except Exception as e:
print(f"Fehler beim Erstellen der SSL-Zertifikate: {e}")
return None
else: else:
print("WARNUNG: SSL-Zertifikate nicht gefunden und Nicht-Debug-Modus. SSL wird deaktiviert.") print("WARNUNG: SSL-Zertifikate nicht gefunden und Nicht-Debug-Modus. SSL wird deaktiviert.")
return None return None

Binary file not shown.

View File

@ -70,6 +70,27 @@ html.dark body .bg-white.dark\:bg-dark-card {
background-color: #1e293b !important; background-color: #1e293b !important;
} }
/* Spezifischere Selektoren für das Admin-Panel im Dark Mode */
body.dark .admin-container .bg-white.dark\:bg-dark-card {
background-color: #1e293b !important;
}
body.dark #users-tab .bg-white.dark\:bg-dark-card,
body.dark #printers-tab .bg-white.dark\:bg-dark-card,
body.dark #scheduler-tab .bg-white.dark\:bg-dark-card,
body.dark #system-tab .bg-white.dark\:bg-dark-card,
body.dark #logs-tab .bg-white.dark\:bg-dark-card {
background-color: #1e293b !important;
}
body.dark .admin-stats .stat-card {
background-color: #1e293b !important;
}
body.dark .bg-slate-50.dark\:bg-slate-800 {
background-color: #0f172a !important;
}
/* Admin Panel Container */ /* Admin Panel Container */
.admin-container { .admin-container {
max-width: 1280px; max-width: 1280px;
@ -841,6 +862,38 @@ html.dark body .bg-white.dark\:bg-dark-card {
} }
/* Additional utilities */ /* Additional utilities */
.transition-colors {
transition-property: color, background-color, border-color;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 300ms;
}
/* Tailwind Dark Mode Overrides */
html.dark body [class*="dark\:bg-dark-card"] {
background-color: #1e293b !important;
}
html.dark body [class*="dark\:bg-slate-800"] {
background-color: #0f172a !important;
}
html.dark body [class*="dark\:bg-slate-700"] {
background-color: #1e293b !important;
}
html.dark body div[class*="dark\:hover\:bg"] {
background-color: #1e293b !important;
}
html.dark body tr[class*="dark\:hover\:bg"] {
background-color: #1e293b !important;
}
html.dark body tr[class*="dark\:hover\:bg"]:hover {
background-color: #334155 !important;
}
/* Ensure proper transition */
.transition-colors { .transition-colors {
transition-property: color, background-color, border-color; transition-property: color, background-color, border-color;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);

View File

@ -401,38 +401,38 @@
<div class="bg-white dark:bg-dark-card rounded-xl shadow-lg p-6 transition-colors"> <div class="bg-white dark:bg-dark-card rounded-xl shadow-lg p-6 transition-colors">
<h3 class="text-xl font-bold text-slate-900 dark:text-white mb-6 transition-colors">Dienste Status</h3> <h3 class="text-xl font-bold text-slate-900 dark:text-white mb-6 transition-colors">Dienste Status</h3>
<div class="space-y-4"> <div class="space-y-4">
<div class="flex justify-between items-center p-3 bg-slate-50 dark:bg-slate-800 rounded-lg"> <div class="flex justify-between items-center p-3 bg-slate-50 dark:bg-slate-800 rounded-lg transition-colors">
<div class="flex items-center"> <div class="flex items-center">
<div class="status-indicator status-running"></div> <div class="status-indicator status-running"></div>
<span class="ml-3 text-slate-900 dark:text-white">Webserver</span> <span class="ml-3 text-slate-900 dark:text-white transition-colors">Webserver</span>
</div> </div>
<span class="text-green-600 dark:text-green-400 font-medium">Online</span> <span class="text-green-600 dark:text-green-400 font-medium transition-colors">Online</span>
</div> </div>
<div class="flex justify-between items-center p-3 bg-slate-50 dark:bg-slate-800 rounded-lg"> <div class="flex justify-between items-center p-3 bg-slate-50 dark:bg-slate-800 rounded-lg transition-colors">
<div class="flex items-center"> <div class="flex items-center">
<div class="status-indicator {{ 'status-running' if scheduler_status.running else 'status-stopped' }}"></div> <div class="status-indicator {{ 'status-running' if scheduler_status.running else 'status-stopped' }}"></div>
<span class="ml-3 text-slate-900 dark:text-white">Scheduler</span> <span class="ml-3 text-slate-900 dark:text-white transition-colors">Scheduler</span>
</div> </div>
<span class="{{ 'text-green-600 dark:text-green-400' if scheduler_status.running else 'text-red-600 dark:text-red-400' }} font-medium"> <span class="{{ 'text-green-600 dark:text-green-400' if scheduler_status.running else 'text-red-600 dark:text-red-400' }} font-medium transition-colors">
{{ 'Online' if scheduler_status.running else 'Offline' }} {{ 'Online' if scheduler_status.running else 'Offline' }}
</span> </span>
</div> </div>
<div class="flex justify-between items-center p-3 bg-slate-50 dark:bg-slate-800 rounded-lg"> <div class="flex justify-between items-center p-3 bg-slate-50 dark:bg-slate-800 rounded-lg transition-colors">
<div class="flex items-center"> <div class="flex items-center">
<div class="status-indicator status-running"></div> <div class="status-indicator status-running"></div>
<span class="ml-3 text-slate-900 dark:text-white">Datenbank</span> <span class="ml-3 text-slate-900 dark:text-white transition-colors">Datenbank</span>
</div> </div>
<span class="text-green-600 dark:text-green-400 font-medium">Online</span> <span class="text-green-600 dark:text-green-400 font-medium transition-colors">Online</span>
</div> </div>
<div class="flex justify-between items-center p-3 bg-slate-50 dark:bg-slate-800 rounded-lg"> <div class="flex justify-between items-center p-3 bg-slate-50 dark:bg-slate-800 rounded-lg transition-colors">
<div class="flex items-center"> <div class="flex items-center">
<div class="status-indicator status-running"></div> <div class="status-indicator status-running"></div>
<span class="ml-3 text-slate-900 dark:text-white">Drucker-Manager</span> <span class="ml-3 text-slate-900 dark:text-white transition-colors">Drucker-Manager</span>
</div> </div>
<span class="text-green-600 dark:text-green-400 font-medium">Online</span> <span class="text-green-600 dark:text-green-400 font-medium transition-colors">Online</span>
</div> </div>
</div> </div>
@ -454,9 +454,9 @@
<!-- Logs Tab --> <!-- Logs Tab -->
{% if active_tab == 'logs' %} {% if active_tab == 'logs' %}
<div id="logs-tab" class="tab-pane active"> <div id="logs-tab" class="tab-pane active">
<div class="bg-white dark:bg-dark-card rounded-xl shadow-lg p-6"> <div class="bg-white dark:bg-dark-card rounded-xl shadow-lg p-6 transition-colors">
<div class="flex justify-between items-center mb-6"> <div class="flex justify-between items-center mb-6">
<h3 class="text-xl font-bold text-slate-900 dark:text-white">System Logs</h3> <h3 class="text-xl font-bold text-slate-900 dark:text-white transition-colors">System Logs</h3>
<div class="flex space-x-4"> <div class="flex space-x-4">
<form action="{{ url_for('admin_page', tab='logs') }}" method="get"> <form action="{{ url_for('admin_page', tab='logs') }}" method="get">
<div class="flex space-x-2"> <div class="flex space-x-2">
@ -477,7 +477,7 @@
</form> </form>
</div> </div>
</div> </div>
<div class="bg-slate-50 dark:bg-slate-800 rounded-lg p-4 max-h-[600px] overflow-y-auto"> <div class="bg-slate-50 dark:bg-slate-800 rounded-lg p-4 max-h-[600px] overflow-y-auto transition-colors">
{% if logs %} {% if logs %}
{% for log in logs %} {% for log in logs %}
<div class="log-entry log-{{ log.level|lower }}"> <div class="log-entry log-{{ log.level|lower }}">

8081
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -71,7 +71,7 @@
"@tailwindcss/forms": "^0.5.9", "@tailwindcss/forms": "^0.5.9",
"@types/lodash": "^4.17.13", "@types/lodash": "^4.17.13",
"@types/luxon": "^3.4.2", "@types/luxon": "^3.4.2",
"@types/node": "^20.16.11", "@types/node": "^20.17.50",
"@types/react": "^18.3.11", "@types/react": "^18.3.11",
"@types/react-dom": "^18.3.1", "@types/react-dom": "^18.3.1",
"drizzle-kit": "^0.21.4", "drizzle-kit": "^0.21.4",

View File

@ -1,5 +1,46 @@
// Basis-URL für Backend-API // Basis-URL für Backend-API
export const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "https://192.168.0.105:5000"; // Versucht verschiedene Verbindungsoptionen mit Fallbacks
const getApiBaseUrl = () => {
// Explizit gesetzte Umgebungsvariable hat höchste Priorität
if (process.env.NEXT_PUBLIC_API_URL) {
return process.env.NEXT_PUBLIC_API_URL;
}
// Im Browser: Verschiedene Verbindungsoptionen versuchen
if (typeof window !== 'undefined') {
// Primäre Verbindungsoption: HTTPS auf Port 443
const hostname = window.location.hostname === 'localhost' ? 'raspberrypi' : window.location.hostname;
// Verbindungsoptionen in Prioritätsreihenfolge
return {
primary: `https://${hostname}:443`,
fallbacks: [
`http://${hostname}:443`,
`https://${hostname}:80`,
`http://${hostname}:80`,
`https://${hostname}:5000`,
`http://${hostname}:5000`,
`https://raspberrypi:443`,
`http://raspberrypi:443`,
`https://raspberrypi:80`,
`http://raspberrypi:80`,
`https://raspberrypi:5000`,
`http://raspberrypi:5000`,
`https://192.168.0.105:443`,
`http://192.168.0.105:443`,
`https://192.168.0.105:80`,
`http://192.168.0.105:80`,
`https://192.168.0.105:5000`,
`http://192.168.0.105:5000`,
]
};
}
// Standardwert für serverseitiges Rendering
return `https://raspberrypi:443`;
};
export const API_BASE_URL = getApiBaseUrl();
// Frontend-URL für Callbacks - unterstützt mehrere Domains // Frontend-URL für Callbacks - unterstützt mehrere Domains
const getFrontendUrl = () => { const getFrontendUrl = () => {
@ -40,14 +81,45 @@ export const ALLOWED_CALLBACK_HOSTS = [
// Funktion zum Testen der SSL-Verbindung // Funktion zum Testen der SSL-Verbindung
export const testSSLConnection = async (): Promise<boolean> => { export const testSSLConnection = async (): Promise<boolean> => {
try { try {
// Führe einen einfachen GET-Request zum Backend aus // Bei mehreren Verbindungsoptionen alle testen
const response = await fetch(`${API_BASE_URL}/health`, { if (typeof API_BASE_URL === 'object' && API_BASE_URL.primary && API_BASE_URL.fallbacks) {
method: 'GET', // Zuerst primäre Verbindung testen
mode: 'no-cors', // Keine CORS-Fehler erzeugen try {
}); const response = await fetch(`${API_BASE_URL.primary}/health`, {
method: 'GET',
// Wenn kein Fehler auftritt, ist die SSL-Verbindung erfolgreich mode: 'no-cors',
return true; });
// Wenn kein Fehler auftritt, ist die primäre Verbindung erfolgreich
return true;
} catch (primaryError) {
console.warn('Primäre SSL-Verbindung fehlgeschlagen, versuche Fallbacks:', primaryError);
// Fallbacks durchprobieren
for (const fallbackUrl of API_BASE_URL.fallbacks) {
try {
const response = await fetch(`${fallbackUrl}/health`, {
method: 'GET',
mode: 'no-cors',
});
// Wenn kein Fehler auftritt, ist die Fallback-Verbindung erfolgreich
return true;
} catch (fallbackError) {
// Fehlgeschlagenen Fallback ignorieren und nächsten versuchen
console.warn(`Fallback ${fallbackUrl} fehlgeschlagen:`, fallbackError);
}
}
// Alle Fallbacks fehlgeschlagen
return false;
}
} else {
// Einfacher Verbindungstest für String-URL
const response = await fetch(`${API_BASE_URL}/health`, {
method: 'GET',
mode: 'no-cors',
});
return true;
}
} catch (error) { } catch (error) {
console.warn('SSL-Verbindungstest fehlgeschlagen:', error); console.warn('SSL-Verbindungstest fehlgeschlagen:', error);
return false; return false;
@ -56,13 +128,13 @@ export const testSSLConnection = async (): Promise<boolean> => {
// Endpunkte für die verschiedenen Ressourcen // Endpunkte für die verschiedenen Ressourcen
export const API_ENDPOINTS = { export const API_ENDPOINTS = {
PRINTERS: `${API_BASE_URL}/api/printers`, PRINTERS: `/api/printers`,
JOBS: `${API_BASE_URL}/api/jobs`, JOBS: `/api/jobs`,
USERS: `${API_BASE_URL}/api/users`, USERS: `/api/users`,
// OAuth-spezifische Endpunkte // OAuth-spezifische Endpunkte
AUTH: { AUTH: {
LOGIN: `${API_BASE_URL}/api/auth/login`, LOGIN: `/api/auth/login`,
CALLBACK: `${API_BASE_URL}/api/auth/callback`, CALLBACK: `/api/auth/callback`,
} }
}; };

View File

@ -5,19 +5,68 @@ import { API_BASE_URL } from './api-config';
* Enthält Logik für Offline-Fallback und Behandlung von selbstsignierten Zertifikaten * Enthält Logik für Offline-Fallback und Behandlung von selbstsignierten Zertifikaten
*/ */
export class ExternalAPI { export class ExternalAPI {
private baseURL: string; private baseURL: string | { primary: string; fallbacks: string[] };
private activeURL: string | null = null;
constructor() { constructor() {
this.baseURL = API_BASE_URL; this.baseURL = API_BASE_URL;
// Wenn das API_BASE_URL ein Objekt ist, setzen wir die primäre URL als aktiv
if (typeof this.baseURL === 'object' && this.baseURL.primary) {
this.activeURL = this.baseURL.primary;
} else if (typeof this.baseURL === 'string') {
this.activeURL = this.baseURL;
}
} }
/** /**
* Führt einen API-Request durch mit Unterstützung für selbstsignierte Zertifikate * Führt einen API-Request durch mit Unterstützung für selbstsignierte Zertifikate
* im Entwicklungsmodus * und Fallback-URLs wenn die primäre Verbindung fehlschlägt
*/ */
async fetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> { async fetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
const url = `${this.baseURL}${endpoint}`; // Wenn wir ein Objekt mit mehreren URLs haben
if (typeof this.baseURL === 'object' && this.baseURL.primary && this.baseURL.fallbacks) {
// Versuche zuerst die aktive URL (primär oder eine der Fallbacks)
if (this.activeURL) {
try {
const response = await this.fetchFromUrl<T>(this.activeURL + endpoint, options);
// Wenn erfolgreich, behalte diese URL bei
return response;
} catch (error) {
console.warn(`Verbindung zu ${this.activeURL} fehlgeschlagen, versuche Fallbacks...`);
// Wenn primäre URL fehlschlägt, versuche alle Fallbacks
const allUrls = [this.baseURL.primary, ...this.baseURL.fallbacks];
// Versuche alle URLs nacheinander (außer der bereits fehlgeschlagenen aktiven URL)
for (const url of allUrls) {
if (url === this.activeURL) continue; // Überspringe die bereits versuchte URL
try {
const response = await this.fetchFromUrl<T>(url + endpoint, options);
// Wenn erfolgreich, setze diese URL als neue aktive URL
this.activeURL = url;
console.log(`Verbindung zu ${url} erfolgreich hergestellt.`);
return response;
} catch (fallbackError) {
// Fehlgeschlagenen Fallback ignorieren und nächsten versuchen
console.warn(`Fallback ${url} fehlgeschlagen...`);
}
}
// Wenn alle Versuche fehlschlagen, wirf den ursprünglichen Fehler
throw new Error(`Keine Verbindung zum Backend möglich. Alle Verbindungsversuche fehlgeschlagen.`);
}
}
}
// Fallback für einfache String-URL
return this.fetchFromUrl<T>((this.activeURL || this.baseURL as string) + endpoint, options);
}
/**
* Führt einen Fetch-Aufruf an einer bestimmten URL durch
*/
private async fetchFromUrl<T>(url: string, options: RequestInit = {}): Promise<T> {
try { try {
const response = await fetch(url, { const response = await fetch(url, {
...options, ...options,
@ -25,8 +74,6 @@ export class ExternalAPI {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
...options.headers, ...options.headers,
}, },
// Im Browser werden selbstsignierte Zertifikate über die Browser-Einstellungen akzeptiert
// Die fetch API im Browser hat keine Option zum Ignorieren von Zertifikaten
}); });
if (!response.ok) { if (!response.ok) {
@ -35,11 +82,11 @@ export class ExternalAPI {
return await response.json(); return await response.json();
} catch (error) { } catch (error) {
console.error('API Fehler:', error); console.error(`API Fehler (${url}):`, error);
// Prüfen auf Zertifikatsfehler // Prüfen auf Zertifikatsfehler
if (error instanceof Error && error.message.includes('certificate')) { if (error instanceof Error && error.message.includes('certificate')) {
console.warn('Zertifikatsfehler: Bitte akzeptieren Sie das selbstsignierte Zertifikat manuell, indem Sie direkt auf https://192.168.0.105:5000 zugreifen und es bestätigen.'); console.warn(`Zertifikatsfehler bei ${url}: Bitte akzeptieren Sie das selbstsignierte Zertifikat manuell.`);
} }
throw error; throw error;

View File

@ -4,9 +4,8 @@
# Überprüfen, ob das Skript als Administrator ausgeführt wird # Überprüfen, ob das Skript als Administrator ausgeführt wird
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) $isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) { if (-not $isAdmin) {
Write-Host "Dieses Skript muss mit Administrator-Rechten ausgeführt werden." -ForegroundColor Red Write-Host "Hinweis: Dieses Skript wird ohne Administrator-Rechte ausgeführt." -ForegroundColor Yellow
Write-Host "Bitte starten Sie PowerShell als Administrator und führen Sie das Skript erneut aus." -ForegroundColor Yellow Write-Host "Einige Funktionen könnten eingeschränkt sein." -ForegroundColor Yellow
Exit 1
} }
# Header anzeigen # Header anzeigen
@ -21,8 +20,8 @@ $backendCertFile = "$certDir/myp.crt"
$backendKeyFile = "$certDir/myp.key" $backendKeyFile = "$certDir/myp.key"
$frontendCertFile = "$certDir/frontend.crt" $frontendCertFile = "$certDir/frontend.crt"
$frontendKeyFile = "$certDir/frontend.key" $frontendKeyFile = "$certDir/frontend.key"
$backendHostname = "raspberrypi" $backendHostname = "localhost"
$frontendHostname = "m040tbaraspi001.de040.corpintra.net" $frontendHostname = "localhost"
$validityDays = 3650 # 10 Jahre $validityDays = 3650 # 10 Jahre
# Verzeichnis erstellen, falls es nicht existiert # Verzeichnis erstellen, falls es nicht existiert