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": {
"allow": [
"Bash(curl:*)",
"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\")"
"Bash(python3:*)"
],
"deny": [],
"defaultMode": "acceptEdits",
"additionalDirectories": [
"/mnt/a/GiTea/Styleguide"
]
],
"ask": []
}
}

0
CLAUDE.md Normale Datei
Datei anzeigen

Datei anzeigen

@ -5,9 +5,9 @@
## Project Overview
- **Path**: `A:\GiTea\AccountForger`
- **Files**: 1317 files
- **Size**: 240.4 MB
- **Last Modified**: 2025-11-09 20:55
- **Files**: 1393 files
- **Size**: 257.7 MB
- **Last Modified**: 2025-11-16 22:31
## Technology Stack
@ -65,6 +65,7 @@ config/
controllers/
│ ├── account_controller.py
│ ├── main_controller.py
│ ├── profile_export_controller.py
│ ├── session_controller.py
│ ├── settings_controller.py
│ └── platform_controllers/
@ -360,8 +361,8 @@ utils/
│ ├── modal_test.py
│ ├── password_generator.py
│ ├── performance_monitor.py
│ ├── proxy_rotator.py
│ └── result_decorators.py
│ ├── process_guard.py
│ └── profile_export_service.py
views/
├── about_dialog.py
├── main_window.py
@ -376,7 +377,9 @@ views/
│ └── __init__.py
├── dialogs/
│ ├── account_creation_result_dialog.py
│ ├── export_success_dialog.py
│ ├── license_activation_dialog.py
│ ├── profile_export_dialog.py
│ └── __init__.py
├── tabs/
│ ├── 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-18 22:23:22
- 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}")
# 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:
logger.info("Kein Speicherort ausgewählt - Export abgebrochen")
@ -79,11 +79,11 @@ class ProfileExportController:
logger.info(f"Speicherort: {save_directory}")
# 4. Export durchführen
files_dict, password = self.export_service.export_account(
# 4. Export durchführen (ohne Passwortschutz)
files_dict = self.export_service.export_account(
account_data,
formats,
password_protect
password_protect=False
)
if not files_dict:
@ -117,8 +117,7 @@ class ProfileExportController:
show_export_success(
parent_widget,
saved_files,
save_directory,
password
save_directory
)
logger.info(f"Export erfolgreich: {len(saved_files)} Datei(en) gespeichert")
@ -136,12 +135,6 @@ class ProfileExportController:
"Bitte installieren Sie die erforderlichen Bibliotheken:\n"
"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(
parent_widget,
@ -154,62 +147,30 @@ class ProfileExportController:
self,
parent_widget,
account_data: Dict[str, Any],
formats: list,
password_protect: bool
formats: list
) -> Optional[str]:
"""
Öffnet einen Datei-Dialog zur Auswahl des Speicherorts.
Öffnet einen Verzeichnis-Dialog zur Auswahl des Speicherorts.
Args:
parent_widget: Parent-Widget
account_data: Account-Daten
formats: Liste der Export-Formate
password_protect: Ob Passwortschutz aktiviert ist
Returns:
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
default_directory = str(Path.home() / "Downloads")
if password_protect:
# Für ZIP: File-Dialog
file_path, _ = QFileDialog.getSaveFileName(
parent_widget,
"Profil exportieren",
os.path.join(default_directory, default_filename),
"ZIP-Archiv (*.zip)"
)
# Verzeichnis-Dialog für mehrere Dateien
directory = QFileDialog.getExistingDirectory(
parent_widget,
"Speicherort für Export auswählen",
default_directory
)
if not file_path:
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
return directory if directory else None
def export_multiple_accounts(self, parent_widget, account_ids: list) -> bool:
"""

Datei anzeigen

@ -1,18 +1,16 @@
"""
Profil-Export-Service für Account-Daten
Exportiert Account-Profile in verschiedene Formate (CSV, TXT, PDF)
mit optionalem Passwortschutz.
Exportiert Account-Profile in verschiedene Formate (CSV, TXT, PDF).
"""
import os
import csv
import secrets
import string
import logging
from io import BytesIO, StringIO
from datetime import datetime
from typing import Dict, Any, List, Optional, Tuple
from typing import Dict, Any, List
from pathlib import Path
logger = logging.getLogger("profile_export_service")
@ -287,88 +285,6 @@ class ProfileExportService:
logger.error(f"Fehler beim PDF-Export: {e}")
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
def generate_filename(
account_data: Dict[str, Any],
@ -427,19 +343,17 @@ class ProfileExportService:
account_data: Dict[str, Any],
formats: List[str],
password_protect: bool = False
) -> Tuple[Dict[str, bytes], Optional[str]]:
) -> Dict[str, bytes]:
"""
Exportiert Account-Daten in angegebene Formate.
Args:
account_data: Account-Daten zum Exportieren
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:
Tuple von (files_dict, password)
- files_dict: {filename: content} für alle Formate
- password: Generiertes Passwort (None wenn nicht geschützt)
Dict mit {filename: content} für alle Formate
"""
files_dict = {}
@ -464,16 +378,4 @@ class ProfileExportService:
else:
logger.warning(f"Unbekanntes Format ignoriert: {fmt}")
# Passwortschutz wenn gewünscht
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
return files_dict

Datei anzeigen

@ -1,7 +1,7 @@
"""
Erfolgs-Dialog für Profil-Export
Zeigt exportierte Dateien und optional das generierte Passwort an.
Zeigt exportierte Dateien an.
"""
import os
@ -9,11 +9,11 @@ import logging
import subprocess
import platform
from pathlib import Path
from typing import List, Optional
from typing import List
from PyQt5.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
QFrame, QGraphicsDropShadowEffect, QApplication, QScrollArea, QWidget
QFrame, QGraphicsDropShadowEffect, QScrollArea, QWidget
)
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont, QColor
@ -28,8 +28,7 @@ class ExportSuccessDialog(QDialog):
self,
parent=None,
exported_files: List[str] = None,
export_directory: str = "",
password: Optional[str] = None
export_directory: str = ""
):
"""
Initialisiert den Erfolgs-Dialog.
@ -38,12 +37,10 @@ class ExportSuccessDialog(QDialog):
parent: Parent-Widget
exported_files: Liste der exportierten Dateinamen
export_directory: Verzeichnis wo Dateien gespeichert wurden
password: Optional generiertes Passwort (nur bei Passwortschutz)
"""
super().__init__(parent)
self.exported_files = exported_files or []
self.export_directory = export_directory
self.password = password
self.init_ui()
def init_ui(self):
@ -55,10 +52,8 @@ class ExportSuccessDialog(QDialog):
self.setAttribute(Qt.WA_TranslucentBackground, True)
self.setModal(True)
# Dynamische Höhe basierend auf Inhalt
# Dynamische Höhe basierend auf Anzahl der Dateien
base_height = 280
if self.password:
base_height += 80 # Extra Platz für Passwort-Anzeige
if len(self.exported_files) > 2:
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)
# 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
location_label = QLabel(f"Gespeichert in:")
location_label.setStyleSheet("color: #6B7280; font-size: 12px;")
@ -299,30 +228,6 @@ class ExportSuccessDialog(QDialog):
# Container zum Hauptlayout hinzufügen
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):
"""Öffnet den Export-Ordner im Datei-Explorer"""
if not self.export_directory or not os.path.exists(self.export_directory):
@ -373,8 +278,7 @@ class ExportSuccessDialog(QDialog):
def show_export_success(
parent,
exported_files: List[str],
export_directory: str,
password: Optional[str] = None
export_directory: str
):
"""
Zeigt den Export-Erfolgs-Dialog modal an.
@ -383,7 +287,6 @@ def show_export_success(
parent: Parent-Widget
exported_files: Liste der exportierten Dateinamen
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_()

Datei anzeigen

@ -1,7 +1,7 @@
"""
Export-Dialog für Account-Profile
Ermöglicht Auswahl von Export-Formaten und Passwortschutz.
Ermöglicht Auswahl von Export-Formaten.
"""
import logging
@ -153,36 +153,6 @@ class ProfileExportDialog(QDialog):
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
container_layout.addStretch()
@ -271,8 +241,8 @@ class ProfileExportDialog(QDialog):
)
return
# Passwortschutz-Option
password_protect = self.password_checkbox.isChecked()
# Passwortschutz ist entfernt - immer False
password_protect = False
# Signal emittieren
self.export_confirmed.emit(selected_formats, password_protect)
@ -295,7 +265,8 @@ class ProfileExportDialog(QDialog):
if self.pdf_checkbox.isChecked():
formats.append("pdf")
password_protect = self.password_checkbox.isChecked()
# Passwortschutz ist entfernt - immer False
password_protect = False
return formats, password_protect