Kein PW bei Export

Dieser Commit ist enthalten in:
Claude Project Manager
2025-11-16 22:57:59 +01:00
Ursprung 88dc8eea5e
Commit 03ca1c607f
7 geänderte Dateien mit 45 neuen und 328 gelöschten Zeilen

Datei anzeigen

@ -1,38 +1,13 @@
{ {
"permissions": { "permissions": {
"allow": [ "allow": [
"Bash(curl:*)", "Bash(python3:*)"
"Bash(nslookup:*)",
"WebFetch(domain:multilogin.com)",
"WebFetch(domain:dicloak.com)",
"WebFetch(domain:support.google.com)",
"Bash(python3 -m pip list:*)",
"Bash(python3:*)",
"Bash(grep:*)",
"Bash(cat:*)",
"Bash(claude config)",
"Bash(claude config list:*)",
"Bash(claude mcp)",
"Bash(claude mcp:*)",
"Bash(sqlite3:*)",
"Bash(python -m pip install:*)",
"Bash(python -m pytest teststest_generator_tab_factory.py -v)",
"Bash(python tests:*)",
"Bash(python tests/test_generator_tab_factory.py)",
"Bash(git push:*)",
"Bash(git remote set-url:*)",
"WebSearch",
"Bash(find:*)",
"Bash(mount:*)",
"Read(//mnt/a/**)",
"Read(//mnt/c/Users/Administrator/AppData/Local/Programs/Python/Python310/**)",
"Read(//mnt/c/Users/Administrator/**)",
"Bash(/mnt/c/Users/Administrator/AppData/Local/Programs/Python/Python310/python.exe -c \"\nimport sys\nsys.dont_write_bytecode = True # Verhindere .pyc Erstellung\nimport utils.email_handler as eh\nhandler = eh.EmailHandler()\npw = handler.config[''imap_pass'']\nprint(f''Windows Python lädt Passwort: {pw[:4]}...{pw[-4:]}'')\nif ''GZsg'' in pw:\n print(''✅ NEUES Passwort wird geladen!'')\nelse:\n print(''❌ ALTES Passwort wird noch geladen!'')\n\")"
], ],
"deny": [], "deny": [],
"defaultMode": "acceptEdits", "defaultMode": "acceptEdits",
"additionalDirectories": [ "additionalDirectories": [
"/mnt/a/GiTea/Styleguide" "/mnt/a/GiTea/Styleguide"
] ],
"ask": []
} }
} }

0
CLAUDE.md Normale Datei
Datei anzeigen

Datei anzeigen

@ -5,9 +5,9 @@
## Project Overview ## Project Overview
- **Path**: `A:\GiTea\AccountForger` - **Path**: `A:\GiTea\AccountForger`
- **Files**: 1317 files - **Files**: 1393 files
- **Size**: 240.4 MB - **Size**: 257.7 MB
- **Last Modified**: 2025-11-09 20:55 - **Last Modified**: 2025-11-16 22:31
## Technology Stack ## Technology Stack
@ -65,6 +65,7 @@ config/
controllers/ controllers/
│ ├── account_controller.py │ ├── account_controller.py
│ ├── main_controller.py │ ├── main_controller.py
│ ├── profile_export_controller.py
│ ├── session_controller.py │ ├── session_controller.py
│ ├── settings_controller.py │ ├── settings_controller.py
│ └── platform_controllers/ │ └── platform_controllers/
@ -360,8 +361,8 @@ utils/
│ ├── modal_test.py │ ├── modal_test.py
│ ├── password_generator.py │ ├── password_generator.py
│ ├── performance_monitor.py │ ├── performance_monitor.py
│ ├── proxy_rotator.py │ ├── process_guard.py
│ └── result_decorators.py │ └── profile_export_service.py
views/ views/
├── about_dialog.py ├── about_dialog.py
├── main_window.py ├── main_window.py
@ -376,7 +377,9 @@ views/
│ └── __init__.py │ └── __init__.py
├── dialogs/ ├── dialogs/
│ ├── account_creation_result_dialog.py │ ├── account_creation_result_dialog.py
│ ├── export_success_dialog.py
│ ├── license_activation_dialog.py │ ├── license_activation_dialog.py
│ ├── profile_export_dialog.py
│ └── __init__.py │ └── __init__.py
├── tabs/ ├── tabs/
│ ├── accounts_tab.py │ ├── accounts_tab.py
@ -431,3 +434,5 @@ This project is managed with Claude Project Manager. To work with this project:
- README updated on 2025-10-08 22:34:43 - README updated on 2025-10-08 22:34:43
- README updated on 2025-10-18 22:23:22 - README updated on 2025-10-18 22:23:22
- README updated on 2025-11-09 21:00:06 - README updated on 2025-11-09 21:00:06
- README updated on 2025-11-16 22:31:39
- README updated on 2025-11-16 22:32:35

Datei anzeigen

@ -71,7 +71,7 @@ class ProfileExportController:
logger.info(f"Export-Optionen: Formate={formats}, Passwort={password_protect}") logger.info(f"Export-Optionen: Formate={formats}, Passwort={password_protect}")
# 3. Speicherort wählen # 3. Speicherort wählen
save_directory = self._select_save_location(parent_widget, account_data, formats, password_protect) save_directory = self._select_save_location(parent_widget, account_data, formats)
if not save_directory: if not save_directory:
logger.info("Kein Speicherort ausgewählt - Export abgebrochen") logger.info("Kein Speicherort ausgewählt - Export abgebrochen")
@ -79,11 +79,11 @@ class ProfileExportController:
logger.info(f"Speicherort: {save_directory}") logger.info(f"Speicherort: {save_directory}")
# 4. Export durchführen # 4. Export durchführen (ohne Passwortschutz)
files_dict, password = self.export_service.export_account( files_dict = self.export_service.export_account(
account_data, account_data,
formats, formats,
password_protect password_protect=False
) )
if not files_dict: if not files_dict:
@ -117,8 +117,7 @@ class ProfileExportController:
show_export_success( show_export_success(
parent_widget, parent_widget,
saved_files, saved_files,
save_directory, save_directory
password
) )
logger.info(f"Export erfolgreich: {len(saved_files)} Datei(en) gespeichert") logger.info(f"Export erfolgreich: {len(saved_files)} Datei(en) gespeichert")
@ -136,12 +135,6 @@ class ProfileExportController:
"Bitte installieren Sie die erforderlichen Bibliotheken:\n" "Bitte installieren Sie die erforderlichen Bibliotheken:\n"
"pip install reportlab svglib" "pip install reportlab svglib"
) )
elif "pyzipper" in error_message.lower():
error_message = (
"Passwortgeschützter Export ist nicht verfügbar.\n\n"
"Bitte installieren Sie die erforderliche Bibliothek:\n"
"pip install pyzipper"
)
show_error( show_error(
parent_widget, parent_widget,
@ -154,62 +147,30 @@ class ProfileExportController:
self, self,
parent_widget, parent_widget,
account_data: Dict[str, Any], account_data: Dict[str, Any],
formats: list, formats: list
password_protect: bool
) -> Optional[str]: ) -> Optional[str]:
""" """
Öffnet einen Datei-Dialog zur Auswahl des Speicherorts. Öffnet einen Verzeichnis-Dialog zur Auswahl des Speicherorts.
Args: Args:
parent_widget: Parent-Widget parent_widget: Parent-Widget
account_data: Account-Daten account_data: Account-Daten
formats: Liste der Export-Formate formats: Liste der Export-Formate
password_protect: Ob Passwortschutz aktiviert ist
Returns: Returns:
Ausgewähltes Verzeichnis oder None bei Abbruch Ausgewähltes Verzeichnis oder None bei Abbruch
""" """
# Standard-Dateiname generieren
if password_protect:
# Bei Passwortschutz wird eine ZIP erstellt
default_filename = self.export_service.generate_filename(
account_data,
"zip"
)
else:
# Ohne Passwortschutz: Ersten Format als Beispiel nehmen
first_format = formats[0] if formats else "csv"
default_filename = self.export_service.generate_filename(
account_data,
first_format
)
# Standard-Speicherort: Benutzer-Downloads-Ordner # Standard-Speicherort: Benutzer-Downloads-Ordner
default_directory = str(Path.home() / "Downloads") default_directory = str(Path.home() / "Downloads")
if password_protect: # Verzeichnis-Dialog für mehrere Dateien
# Für ZIP: File-Dialog directory = QFileDialog.getExistingDirectory(
file_path, _ = QFileDialog.getSaveFileName( parent_widget,
parent_widget, "Speicherort für Export auswählen",
"Profil exportieren", default_directory
os.path.join(default_directory, default_filename), )
"ZIP-Archiv (*.zip)"
)
if not file_path: return directory if directory else None
return None
# Verzeichnis aus Dateipfad extrahieren
return os.path.dirname(file_path)
else:
# Für mehrere Dateien: Verzeichnis-Dialog
directory = QFileDialog.getExistingDirectory(
parent_widget,
"Speicherort für Export auswählen",
default_directory
)
return directory if directory else None
def export_multiple_accounts(self, parent_widget, account_ids: list) -> bool: def export_multiple_accounts(self, parent_widget, account_ids: list) -> bool:
""" """

Datei anzeigen

@ -1,18 +1,16 @@
""" """
Profil-Export-Service für Account-Daten Profil-Export-Service für Account-Daten
Exportiert Account-Profile in verschiedene Formate (CSV, TXT, PDF) Exportiert Account-Profile in verschiedene Formate (CSV, TXT, PDF).
mit optionalem Passwortschutz.
""" """
import os import os
import csv import csv
import secrets
import string import string
import logging import logging
from io import BytesIO, StringIO from io import BytesIO, StringIO
from datetime import datetime from datetime import datetime
from typing import Dict, Any, List, Optional, Tuple from typing import Dict, Any, List
from pathlib import Path from pathlib import Path
logger = logging.getLogger("profile_export_service") logger = logging.getLogger("profile_export_service")
@ -287,88 +285,6 @@ class ProfileExportService:
logger.error(f"Fehler beim PDF-Export: {e}") logger.error(f"Fehler beim PDF-Export: {e}")
raise raise
@staticmethod
def generate_password(length: int = 10) -> str:
"""
Generiert ein sicheres zufälliges Passwort.
Args:
length: Länge des Passworts (Standard: 10)
Returns:
Generiertes Passwort
"""
# Zeichensatz: Groß- und Kleinbuchstaben, Zahlen, Sonderzeichen
alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
# Sicherstellen dass mindestens ein Zeichen von jedem Typ vorhanden ist
password_chars = [
secrets.choice(string.ascii_uppercase), # Mindestens ein Großbuchstabe
secrets.choice(string.ascii_lowercase), # Mindestens ein Kleinbuchstabe
secrets.choice(string.digits), # Mindestens eine Zahl
secrets.choice("!@#$%^&*") # Mindestens ein Sonderzeichen
]
# Rest auffüllen
for _ in range(length - 4):
password_chars.append(secrets.choice(alphabet))
# Mischen für Zufälligkeit
secrets.SystemRandom().shuffle(password_chars)
password = ''.join(password_chars)
logger.info("Passwort generiert")
return password
@staticmethod
def create_protected_zip(files_dict: Dict[str, bytes], password: str) -> bytes:
"""
Erstellt eine passwortgeschützte ZIP-Datei.
Args:
files_dict: Dictionary mit {filename: content} Paaren
password: Passwort für die ZIP-Datei
Returns:
ZIP-Daten als bytes
"""
try:
import pyzipper
# ZIP-Buffer erstellen
buffer = BytesIO()
# ZIP mit AES-Verschlüsselung erstellen
with pyzipper.AESZipFile(
buffer,
'w',
compression=pyzipper.ZIP_DEFLATED,
encryption=pyzipper.WZ_AES
) as zf:
# Passwort setzen
zf.setpassword(password.encode('utf-8'))
# Dateien hinzufügen
for filename, content in files_dict.items():
zf.writestr(filename, content)
# Buffer-Wert holen
zip_content = buffer.getvalue()
buffer.close()
logger.info(f"Passwortgeschützte ZIP erstellt mit {len(files_dict)} Dateien")
return zip_content
except ImportError as e:
logger.error(f"pyzipper nicht installiert: {e}")
raise Exception(
"ZIP mit Passwortschutz erfordert 'pyzipper' Library. "
"Bitte installieren Sie: pip install pyzipper"
)
except Exception as e:
logger.error(f"Fehler beim Erstellen der geschützten ZIP: {e}")
raise
@staticmethod @staticmethod
def generate_filename( def generate_filename(
account_data: Dict[str, Any], account_data: Dict[str, Any],
@ -427,19 +343,17 @@ class ProfileExportService:
account_data: Dict[str, Any], account_data: Dict[str, Any],
formats: List[str], formats: List[str],
password_protect: bool = False password_protect: bool = False
) -> Tuple[Dict[str, bytes], Optional[str]]: ) -> Dict[str, bytes]:
""" """
Exportiert Account-Daten in angegebene Formate. Exportiert Account-Daten in angegebene Formate.
Args: Args:
account_data: Account-Daten zum Exportieren account_data: Account-Daten zum Exportieren
formats: Liste von Formaten ["csv", "txt", "pdf"] formats: Liste von Formaten ["csv", "txt", "pdf"]
password_protect: Ob Dateien passwortgeschützt werden sollen password_protect: Wird ignoriert (für Rückwärtskompatibilität)
Returns: Returns:
Tuple von (files_dict, password) Dict mit {filename: content} für alle Formate
- files_dict: {filename: content} für alle Formate
- password: Generiertes Passwort (None wenn nicht geschützt)
""" """
files_dict = {} files_dict = {}
@ -464,16 +378,4 @@ class ProfileExportService:
else: else:
logger.warning(f"Unbekanntes Format ignoriert: {fmt}") logger.warning(f"Unbekanntes Format ignoriert: {fmt}")
# Passwortschutz wenn gewünscht return files_dict
password = None
if password_protect and files_dict:
password = ProfileExportService.generate_password()
# Alle Dateien in ZIP packen
zip_filename = ProfileExportService.generate_filename(account_data, "zip")
zip_content = ProfileExportService.create_protected_zip(files_dict, password)
# Nur ZIP zurückgeben
files_dict = {zip_filename: zip_content}
return files_dict, password

Datei anzeigen

@ -1,7 +1,7 @@
""" """
Erfolgs-Dialog für Profil-Export Erfolgs-Dialog für Profil-Export
Zeigt exportierte Dateien und optional das generierte Passwort an. Zeigt exportierte Dateien an.
""" """
import os import os
@ -9,11 +9,11 @@ import logging
import subprocess import subprocess
import platform import platform
from pathlib import Path from pathlib import Path
from typing import List, Optional from typing import List
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
QFrame, QGraphicsDropShadowEffect, QApplication, QScrollArea, QWidget QFrame, QGraphicsDropShadowEffect, QScrollArea, QWidget
) )
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont, QColor from PyQt5.QtGui import QFont, QColor
@ -28,8 +28,7 @@ class ExportSuccessDialog(QDialog):
self, self,
parent=None, parent=None,
exported_files: List[str] = None, exported_files: List[str] = None,
export_directory: str = "", export_directory: str = ""
password: Optional[str] = None
): ):
""" """
Initialisiert den Erfolgs-Dialog. Initialisiert den Erfolgs-Dialog.
@ -38,12 +37,10 @@ class ExportSuccessDialog(QDialog):
parent: Parent-Widget parent: Parent-Widget
exported_files: Liste der exportierten Dateinamen exported_files: Liste der exportierten Dateinamen
export_directory: Verzeichnis wo Dateien gespeichert wurden export_directory: Verzeichnis wo Dateien gespeichert wurden
password: Optional generiertes Passwort (nur bei Passwortschutz)
""" """
super().__init__(parent) super().__init__(parent)
self.exported_files = exported_files or [] self.exported_files = exported_files or []
self.export_directory = export_directory self.export_directory = export_directory
self.password = password
self.init_ui() self.init_ui()
def init_ui(self): def init_ui(self):
@ -55,10 +52,8 @@ class ExportSuccessDialog(QDialog):
self.setAttribute(Qt.WA_TranslucentBackground, True) self.setAttribute(Qt.WA_TranslucentBackground, True)
self.setModal(True) self.setModal(True)
# Dynamische Höhe basierend auf Inhalt # Dynamische Höhe basierend auf Anzahl der Dateien
base_height = 280 base_height = 280
if self.password:
base_height += 80 # Extra Platz für Passwort-Anzeige
if len(self.exported_files) > 2: if len(self.exported_files) > 2:
base_height += 20 * (len(self.exported_files) - 2) # Extra für mehr Dateien base_height += 20 * (len(self.exported_files) - 2) # Extra für mehr Dateien
@ -153,72 +148,6 @@ class ExportSuccessDialog(QDialog):
container_layout.addWidget(scroll_area) container_layout.addWidget(scroll_area)
# Passwort-Anzeige (nur wenn Passwort vorhanden)
if self.password:
password_frame = QFrame()
password_frame.setStyleSheet("""
QFrame {
background-color: #FEF3C7;
border: 1px solid #FCD34D;
border-radius: 6px;
padding: 12px;
}
""")
password_layout = QVBoxLayout(password_frame)
password_layout.setSpacing(8)
# Warnung
warning_label = QLabel("⚠ Bitte Passwort speichern!")
warning_label.setStyleSheet("""
color: #92400E;
font-size: 13px;
font-weight: 600;
""")
# Passwort mit Copy-Button
password_row = QHBoxLayout()
password_row.setSpacing(10)
password_label = QLabel(f"🔒 Passwort: {self.password}")
password_label.setStyleSheet("""
color: #78350F;
font-size: 13px;
font-family: 'Courier New', monospace;
font-weight: 600;
""")
password_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
copy_button = QPushButton("Kopieren")
copy_button.setMaximumWidth(80)
copy_button.setMinimumHeight(28)
copy_button.setCursor(Qt.PointingHandCursor)
copy_button.setStyleSheet("""
QPushButton {
background-color: #F59E0B;
color: white;
border: none;
border-radius: 4px;
font-size: 12px;
font-weight: 600;
padding: 4px 8px;
}
QPushButton:hover {
background-color: #D97706;
}
QPushButton:pressed {
background-color: #B45309;
}
""")
copy_button.clicked.connect(self.copy_password)
password_row.addWidget(password_label, 1)
password_row.addWidget(copy_button)
password_layout.addWidget(warning_label)
password_layout.addLayout(password_row)
container_layout.addWidget(password_frame)
# Speicherort # Speicherort
location_label = QLabel(f"Gespeichert in:") location_label = QLabel(f"Gespeichert in:")
location_label.setStyleSheet("color: #6B7280; font-size: 12px;") location_label.setStyleSheet("color: #6B7280; font-size: 12px;")
@ -299,30 +228,6 @@ class ExportSuccessDialog(QDialog):
# Container zum Hauptlayout hinzufügen # Container zum Hauptlayout hinzufügen
main_layout.addWidget(self.container) main_layout.addWidget(self.container)
def copy_password(self):
"""Kopiert das Passwort in die Zwischenablage"""
if self.password:
clipboard = QApplication.clipboard()
clipboard.setText(self.password)
logger.info("Passwort in Zwischenablage kopiert")
# Kurzes visuelles Feedback
sender = self.sender()
if sender:
original_text = sender.text()
sender.setText("✓ Kopiert!")
sender.setStyleSheet(sender.styleSheet().replace("#F59E0B", "#10B981"))
# Nach 2 Sekunden zurücksetzen
from PyQt5.QtCore import QTimer
QTimer.singleShot(2000, lambda: self._reset_copy_button(sender, original_text))
def _reset_copy_button(self, button, original_text):
"""Setzt den Copy-Button zurück"""
if button:
button.setText(original_text)
button.setStyleSheet(button.styleSheet().replace("#10B981", "#F59E0B"))
def open_folder(self): def open_folder(self):
"""Öffnet den Export-Ordner im Datei-Explorer""" """Öffnet den Export-Ordner im Datei-Explorer"""
if not self.export_directory or not os.path.exists(self.export_directory): if not self.export_directory or not os.path.exists(self.export_directory):
@ -373,8 +278,7 @@ class ExportSuccessDialog(QDialog):
def show_export_success( def show_export_success(
parent, parent,
exported_files: List[str], exported_files: List[str],
export_directory: str, export_directory: str
password: Optional[str] = None
): ):
""" """
Zeigt den Export-Erfolgs-Dialog modal an. Zeigt den Export-Erfolgs-Dialog modal an.
@ -383,7 +287,6 @@ def show_export_success(
parent: Parent-Widget parent: Parent-Widget
exported_files: Liste der exportierten Dateinamen exported_files: Liste der exportierten Dateinamen
export_directory: Verzeichnis wo Dateien gespeichert wurden export_directory: Verzeichnis wo Dateien gespeichert wurden
password: Optional generiertes Passwort
""" """
dialog = ExportSuccessDialog(parent, exported_files, export_directory, password) dialog = ExportSuccessDialog(parent, exported_files, export_directory)
dialog.exec_() dialog.exec_()

Datei anzeigen

@ -1,7 +1,7 @@
""" """
Export-Dialog für Account-Profile Export-Dialog für Account-Profile
Ermöglicht Auswahl von Export-Formaten und Passwortschutz. Ermöglicht Auswahl von Export-Formaten.
""" """
import logging import logging
@ -153,36 +153,6 @@ class ProfileExportDialog(QDialog):
container_layout.addWidget(format_group) container_layout.addWidget(format_group)
# Passwortschutz-Checkbox
self.password_checkbox = QCheckBox("Mit Passwort schützen")
self.password_checkbox.setChecked(False)
self.password_checkbox.setStyleSheet("""
QCheckBox {
font-size: 13px;
font-family: 'Poppins', sans-serif;
font-weight: 500;
color: #374151;
spacing: 8px;
}
QCheckBox::indicator {
width: 18px;
height: 18px;
border-radius: 4px;
border: 2px solid #D1D5DB;
}
QCheckBox::indicator:checked {
background-color: #10B981;
border-color: #10B981;
}
""")
# Info-Text für Passwortschutz
password_info = QLabel("(Wird automatisch generiert)")
password_info.setStyleSheet("color: #9CA3AF; font-size: 11px; font-family: 'Poppins', sans-serif; margin-left: 26px;")
container_layout.addWidget(self.password_checkbox)
container_layout.addWidget(password_info)
# Spacer # Spacer
container_layout.addStretch() container_layout.addStretch()
@ -271,8 +241,8 @@ class ProfileExportDialog(QDialog):
) )
return return
# Passwortschutz-Option # Passwortschutz ist entfernt - immer False
password_protect = self.password_checkbox.isChecked() password_protect = False
# Signal emittieren # Signal emittieren
self.export_confirmed.emit(selected_formats, password_protect) self.export_confirmed.emit(selected_formats, password_protect)
@ -295,7 +265,8 @@ class ProfileExportDialog(QDialog):
if self.pdf_checkbox.isChecked(): if self.pdf_checkbox.isChecked():
formats.append("pdf") formats.append("pdf")
password_protect = self.password_checkbox.isChecked() # Passwortschutz ist entfernt - immer False
password_protect = False
return formats, password_protect return formats, password_protect