From 03793917363a5f0617fa6ffadac6f5f6441b8eb3 Mon Sep 17 00:00:00 2001 From: UserIsMH Date: Sat, 7 Jun 2025 22:58:11 +0200 Subject: [PATCH] Lizenzkey Generator --- JOURNAL.md | 69 +++++++++++++++- v2/cookies.txt | 2 +- v2_adminpanel/app.py | 99 ++++++++++++++++++++++- v2_adminpanel/cookies.txt | 5 ++ v2_adminpanel/templates/audit_log.html | 3 + v2_adminpanel/templates/index.html | 108 ++++++++++++++++++++++++- 6 files changed, 281 insertions(+), 5 deletions(-) create mode 100644 v2_adminpanel/cookies.txt diff --git a/JOURNAL.md b/JOURNAL.md index 8ec6e78..3200725 100644 --- a/JOURNAL.md +++ b/JOURNAL.md @@ -896,4 +896,71 @@ Die Session-Daten werden erst gefüllt, wenn der License Server API implementier - Code ist vollständig implementiert und getestet - CAPTCHA wird nach 2 Fehlversuchen angezeigt - Ohne konfigurierte Keys wird CAPTCHA-Prüfung übersprungen -- Für Produktion müssen nur die Keys in .env eingetragen werden \ No newline at end of file +- Für Produktion müssen nur die Keys in .env eingetragen werden + +### 2025-06-07 - License Key Generator implementiert +- Automatische Generierung von Lizenzschlüsseln mit definiertem Format + +**Implementiertes Format:** +`AF-YYYYMMFT-XXXX-YYYY-ZZZZ` +- **AF** = Account Factory (feste Produktkennung) +- **YYYY** = Jahr (z.B. 2025) +- **MM** = Monat (z.B. 06) +- **FT** = Lizenztyp (F=Fullversion, T=Testversion) +- **XXXX-YYYY-ZZZZ** = Zufällige alphanumerische Zeichen (ohne verwirrende wie 0/O, 1/I/l) + +**Beispiele:** +- Vollversion: `AF-202506F-A7K9-M3P2-X8R4` +- Testversion: `AF-202512T-B2N5-K8L3-Q9W7` + +**Implementierte Features:** + +1. **Backend-Funktionen (app.py):** + - `generate_license_key()` - Generiert Keys mit kryptografisch sicherem Zufallsgenerator + - `validate_license_key()` - Validiert das Key-Format mit Regex + - Verwendet `secrets` statt `random` für Sicherheit + - Erlaubte Zeichen: ABCDEFGHJKLMNPQRSTUVWXYZ23456789 (ohne verwirrende) + +2. **API-Endpoint:** + - POST `/api/generate-license-key` - JSON API für Key-Generierung + - Prüft auf Duplikate in der Datenbank (max. 10 Versuche) + - Audit-Log-Eintrag bei jeder Generierung + - Login-Required geschützt + +3. **Frontend-Verbesserungen (index.html):** + - Generate-Button neben License Key Input + - Placeholder und Pattern-Attribut für Format-Hinweis + - Auto-Uppercase bei manueller Eingabe + - Visuelles Feedback bei erfolgreicher Generierung + - Format-Hinweis unter dem Eingabefeld + +4. **JavaScript-Features:** + - AJAX-basierte Key-Generierung ohne Seiten-Reload + - Automatische Prüfung bei Lizenztyp-Änderung + - Ladeindikator während der Generierung + - Fehlerbehandlung mit Benutzer-Feedback + - Standard-Datum-Einstellungen (heute + 1 Jahr) + +5. **Validierung:** + - Server-seitige Format-Validierung beim Speichern + - Flash-Message bei ungültigem Format + - Automatische Großschreibung des Keys + - Pattern-Validierung im HTML-Formular + +6. **Weitere Fixes:** + - Form Action von "/" auf "/create" korrigiert + - Flash-Messages mit Bootstrap Toasts implementiert + - GENERATE_KEY Aktion zum Audit-Log hinzugefügt (Farbe: #20c997) + +**Technische Details:** +- Keine vorhersagbaren Muster durch `secrets.choice()` +- Datum im Key zeigt Erstellungszeitpunkt +- Lizenztyp direkt im Key erkennbar +- Kollisionsprüfung gegen Datenbank + +**Status:** +- ✅ Backend-Generierung vollständig implementiert +- ✅ Frontend mit Generate-Button und JavaScript +- ✅ Validierung und Fehlerbehandlung +- ✅ Audit-Log-Integration +- ✅ Form-Action-Bug behoben \ No newline at end of file diff --git a/v2/cookies.txt b/v2/cookies.txt index afe7c67..4f0d095 100644 --- a/v2/cookies.txt +++ b/v2/cookies.txt @@ -2,4 +2,4 @@ # https://curl.se/docs/http-cookies.html # This file was generated by libcurl! Edit at your own risk. -#HttpOnly_localhost FALSE / FALSE 1749327638 admin_session hlywIUxTA0lRA4PyUOO2OrC5YNt7-2__FEVhP7H_Jac +#HttpOnly_localhost FALSE / FALSE 1749329677 admin_session DJ5-gm8DCBYcqZyLqo7pYzvq-FoFBRAYkvWPn37aAo4 diff --git a/v2_adminpanel/app.py b/v2_adminpanel/app.py index a1d356b..eee8809 100644 --- a/v2_adminpanel/app.py +++ b/v2_adminpanel/app.py @@ -18,6 +18,9 @@ import logging import random import hashlib import requests +import secrets +import string +import re load_dotenv() @@ -548,6 +551,50 @@ def verify_recaptcha(response): logging.error(f"Unerwarteter Fehler bei reCAPTCHA: {str(e)}") return False +def generate_license_key(license_type='full'): + """ + Generiert einen Lizenzschlüssel im Format: AF-YYYYMMFT-XXXX-YYYY-ZZZZ + + AF = Account Factory (Produktkennung) + YYYY = Jahr + MM = Monat + FT = F für Fullversion, T für Testversion + XXXX-YYYY-ZZZZ = Zufällige alphanumerische Zeichen + """ + # Erlaubte Zeichen (ohne verwirrende wie 0/O, 1/I/l) + chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789' + + # Datum-Teil + now = datetime.now() + date_part = now.strftime('%Y%m') + type_char = 'F' if license_type == 'full' else 'T' + + # Zufällige Teile generieren (3 Blöcke à 4 Zeichen) + parts = [] + for _ in range(3): + part = ''.join(secrets.choice(chars) for _ in range(4)) + parts.append(part) + + # Key zusammensetzen + key = f"AF-{date_part}{type_char}-{parts[0]}-{parts[1]}-{parts[2]}" + + return key + +def validate_license_key(key): + """ + Validiert das License Key Format + Erwartet: AF-YYYYMMFT-XXXX-YYYY-ZZZZ + """ + if not key: + return False + + # Pattern für das spezifische Format + # AF- (fest) + 6 Ziffern (YYYYMM) + F oder T + - + 4 Zeichen + - + 4 Zeichen + - + 4 Zeichen + pattern = r'^AF-\d{6}[FT]-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$' + + # Großbuchstaben für Vergleich + return bool(re.match(pattern, key.upper())) + @app.route("/login", methods=["GET", "POST"]) def login(): # Timing-Attack Schutz - Start Zeit merken @@ -672,6 +719,51 @@ def heartbeat(): 'username': session.get('username') }) +@app.route("/api/generate-license-key", methods=['POST']) +@login_required +def api_generate_key(): + """API Endpoint zur Generierung eines neuen Lizenzschlüssels""" + try: + # Lizenztyp aus Request holen (default: full) + data = request.get_json() or {} + license_type = data.get('type', 'full') + + # Key generieren + key = generate_license_key(license_type) + + # Prüfen ob Key bereits existiert (sehr unwahrscheinlich aber sicher ist sicher) + conn = get_connection() + cur = conn.cursor() + + # Wiederhole bis eindeutiger Key gefunden + attempts = 0 + while attempts < 10: # Max 10 Versuche + cur.execute("SELECT 1 FROM licenses WHERE license_key = %s", (key,)) + if not cur.fetchone(): + break # Key ist eindeutig + key = generate_license_key(license_type) + attempts += 1 + + cur.close() + conn.close() + + # Log für Audit + log_audit('GENERATE_KEY', 'license', + additional_info={'type': license_type, 'key': key}) + + return jsonify({ + 'success': True, + 'key': key, + 'type': license_type + }) + + except Exception as e: + logging.error(f"Fehler bei Key-Generierung: {str(e)}") + return jsonify({ + 'success': False, + 'error': 'Fehler bei der Key-Generierung' + }), 500 + @app.route("/") @login_required def dashboard(): @@ -841,11 +933,16 @@ def create_license(): if request.method == "POST": name = request.form["customer_name"] email = request.form["email"] - license_key = request.form["license_key"] + license_key = request.form["license_key"].upper() # Immer Großbuchstaben license_type = request.form["license_type"] valid_from = request.form["valid_from"] valid_until = request.form["valid_until"] + # Validiere License Key Format + if not validate_license_key(license_key): + flash('Ungültiges License Key Format! Erwartet: AF-YYYYMMFT-XXXX-YYYY-ZZZZ', 'error') + return redirect(url_for('create_license')) + conn = get_connection() cur = conn.cursor() diff --git a/v2_adminpanel/cookies.txt b/v2_adminpanel/cookies.txt new file mode 100644 index 0000000..bc84d1a --- /dev/null +++ b/v2_adminpanel/cookies.txt @@ -0,0 +1,5 @@ +# Netscape HTTP Cookie File +# https://curl.se/docs/http-cookies.html +# This file was generated by libcurl! Edit at your own risk. + +#HttpOnly_localhost FALSE / FALSE 1749329847 admin_session aojqyq4GcSt5oT7NJPeg7UHPoEZUVkn-s1Kr-EAnJWM diff --git a/v2_adminpanel/templates/audit_log.html b/v2_adminpanel/templates/audit_log.html index d76f78e..8bf8a1b 100644 --- a/v2_adminpanel/templates/audit_log.html +++ b/v2_adminpanel/templates/audit_log.html @@ -26,6 +26,7 @@ .action-LOGOUT { color: #6c757d; } .action-AUTO_LOGOUT { color: #fd7e14; } .action-EXPORT { color: #ffc107; } + .action-GENERATE_KEY { color: #20c997; } {% endblock %} @@ -63,6 +64,7 @@ +
@@ -113,6 +115,7 @@ {% elif log[3] == 'LOGOUT' %}🚪 Abmeldung {% elif log[3] == 'AUTO_LOGOUT' %}⏰ Auto-Logout {% elif log[3] == 'EXPORT' %}📥 Export + {% elif log[3] == 'GENERATE_KEY' %}🔑 Key generiert {% else %}{{ log[3] }} {% endif %} diff --git a/v2_adminpanel/templates/index.html b/v2_adminpanel/templates/index.html index ebd3c47..306612b 100644 --- a/v2_adminpanel/templates/index.html +++ b/v2_adminpanel/templates/index.html @@ -16,7 +16,7 @@
-
+
@@ -28,7 +28,16 @@
- +
+ + +
+
Format: AF-YYYYMMFT-XXXX-YYYY-ZZZZ (F=Full, T=Test)
@@ -52,4 +61,99 @@
+ + +{% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} +
+ {% for category, message in messages %} + + {% endfor %} +
+ {% endif %} +{% endwith %} + + {% endblock %}