Add license key management features and backup functionality
- Updated base template to include a link for license key management. - Created a new template for license key management with forms for generating and allocating keys, as well as backup options. - Implemented backup.py for loading and saving licenses to MongoDB, including export and import functionalities. - Added verify.py for license key verification, user ID generation, and key registration.
This commit is contained in:
@@ -0,0 +1,75 @@
|
|||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
from pymongo import MongoClient
|
||||||
|
from pymongo.errors import PyMongoError
|
||||||
|
|
||||||
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
INVOICE_UPLOAD_DIR = os.path.join(BASE_DIR, "static", "uploads", "invoices")
|
||||||
|
MONGO_URI = os.environ.get("MONGO_URI", "mongodb://localhost:27017")
|
||||||
|
MONGO_DB_NAME = os.environ.get("MONGO_DB_NAME", "Invario_Website")
|
||||||
|
LICENSE_COLLECTION = "licenses"
|
||||||
|
|
||||||
|
|
||||||
|
def _get_collection():
|
||||||
|
client = MongoClient(MONGO_URI, serverSelectionTimeoutMS=1200)
|
||||||
|
return client, client[MONGO_DB_NAME][LICENSE_COLLECTION]
|
||||||
|
|
||||||
|
def load_licenses() -> dict:
|
||||||
|
"""Load all licenses from MongoDB and return them as a list."""
|
||||||
|
client = None
|
||||||
|
try:
|
||||||
|
client, col = _get_collection()
|
||||||
|
return list(col.find({}, {"_id": 0}).sort("created_at", 1))
|
||||||
|
except PyMongoError:
|
||||||
|
return []
|
||||||
|
finally:
|
||||||
|
if client:
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
|
||||||
|
def save_licenses(data: dict) -> bool:
|
||||||
|
"""Replace MongoDB licenses with provided list data."""
|
||||||
|
if not isinstance(data, list):
|
||||||
|
return False
|
||||||
|
|
||||||
|
client = None
|
||||||
|
try:
|
||||||
|
client, col = _get_collection()
|
||||||
|
col.delete_many({})
|
||||||
|
|
||||||
|
prepared = []
|
||||||
|
for item in data:
|
||||||
|
if not isinstance(item, dict):
|
||||||
|
continue
|
||||||
|
license_key = str(item.get("license_key", "")).strip()
|
||||||
|
user_id = str(item.get("user_id", "")).strip()
|
||||||
|
if not license_key:
|
||||||
|
continue
|
||||||
|
prepared.append(
|
||||||
|
{
|
||||||
|
"user_id": user_id,
|
||||||
|
"license_key": license_key,
|
||||||
|
"hwid_uuid": str(item.get("hwid_uuid", "")).strip(),
|
||||||
|
"created_at": item.get("created_at") or datetime.utcnow().isoformat(timespec="seconds") + "Z",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if prepared:
|
||||||
|
col.insert_many(prepared)
|
||||||
|
|
||||||
|
return True
|
||||||
|
except PyMongoError:
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
if client:
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
|
||||||
|
def export_backup() -> dict:
|
||||||
|
"""Export current licenses content from MongoDB."""
|
||||||
|
return load_licenses()
|
||||||
|
|
||||||
|
|
||||||
|
def import_backup(data: dict) -> bool:
|
||||||
|
"""Import and restore licenses in MongoDB from backup data."""
|
||||||
|
return save_licenses(data)
|
||||||
+151
-1
@@ -1,9 +1,11 @@
|
|||||||
from flask_jwt_extended import JWTManager, create_access_token, jwt_required
|
from flask_jwt_extended import JWTManager, create_access_token, jwt_required
|
||||||
from flask import Flask, render_template, request, jsonify, flash, redirect, url_for, get_flashed_messages, session
|
from flask import Flask, render_template, request, jsonify, flash, redirect, url_for, get_flashed_messages, session, send_file
|
||||||
import os
|
import os
|
||||||
|
import json
|
||||||
import calendar
|
import calendar
|
||||||
from datetime import timedelta, datetime, date
|
from datetime import timedelta, datetime, date
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
from io import BytesIO
|
||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
import bleach
|
import bleach
|
||||||
@@ -11,6 +13,8 @@ from markupsafe import escape
|
|||||||
from pymongo import MongoClient
|
from pymongo import MongoClient
|
||||||
from pymongo.errors import PyMongoError
|
from pymongo.errors import PyMongoError
|
||||||
from bson.objectid import ObjectId
|
from bson.objectid import ObjectId
|
||||||
|
import verify
|
||||||
|
import backup
|
||||||
|
|
||||||
import user as user_store
|
import user as user_store
|
||||||
|
|
||||||
@@ -1211,6 +1215,152 @@ def impressum():
|
|||||||
def nutzungsbedingungen():
|
def nutzungsbedingungen():
|
||||||
return render_template("nutzungsbedingungen.html")
|
return render_template("nutzungsbedingungen.html")
|
||||||
|
|
||||||
|
@app.route("/admin/lizenz_key")
|
||||||
|
@app.route("/admin/license-management")
|
||||||
|
@admin_required
|
||||||
|
def admin_license_keys():
|
||||||
|
if 'username' not in session:
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
keys = verify.load_file()
|
||||||
|
users = _list_users_for_admin()
|
||||||
|
return render_template("lizenz-managment.html", keys=keys, users=users)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/admin/generate_new", methods=["POST"])
|
||||||
|
@admin_required
|
||||||
|
def generate_new():
|
||||||
|
if 'username' not in session:
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
new_license = verify.new_key()
|
||||||
|
if not new_license:
|
||||||
|
flash("License generation failed (database unavailable).", "error")
|
||||||
|
return redirect(url_for('admin_license_keys'))
|
||||||
|
|
||||||
|
flash(f"New key generated: {new_license}", "success")
|
||||||
|
return redirect(url_for('admin_license_keys'))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/admin/allocate_key", methods=["POST"])
|
||||||
|
@admin_required
|
||||||
|
def allocate_key():
|
||||||
|
if 'username' not in session:
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
username = _sanitize_text(request.form.get("username") or "", 80)
|
||||||
|
license_key = _sanitize_text(request.form.get("license_key") or "", 160)
|
||||||
|
|
||||||
|
if not username or not license_key:
|
||||||
|
flash("Bitte Benutzer und Lizenz-Key auswaehlen.", "error")
|
||||||
|
return redirect(url_for('admin_license_keys'))
|
||||||
|
|
||||||
|
if not _find_user(username):
|
||||||
|
flash("Ausgewaehlter Benutzer wurde nicht gefunden.", "error")
|
||||||
|
return redirect(url_for('admin_license_keys'))
|
||||||
|
|
||||||
|
client = None
|
||||||
|
try:
|
||||||
|
client, col = _get_collection("licenses")
|
||||||
|
result = col.update_one(
|
||||||
|
{"license_key": license_key},
|
||||||
|
{
|
||||||
|
"$set": {
|
||||||
|
"username": username,
|
||||||
|
"updated_at": _utc_now_iso(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if result.matched_count == 0:
|
||||||
|
flash("Lizenz-Key nicht gefunden.", "error")
|
||||||
|
return redirect(url_for('admin_license_keys'))
|
||||||
|
except PyMongoError:
|
||||||
|
flash("Key-Zuweisung fehlgeschlagen.", "error")
|
||||||
|
return redirect(url_for('admin_license_keys'))
|
||||||
|
finally:
|
||||||
|
if client:
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
flash("Lizenz-Key wurde erfolgreich zugewiesen.", "success")
|
||||||
|
return redirect(url_for('admin_license_keys'))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/admin/remove_key/<user_id>", methods=["POST"])
|
||||||
|
@admin_required
|
||||||
|
def remove_key(user_id):
|
||||||
|
if 'username' not in session:
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
if verify.remove_key(user_id):
|
||||||
|
flash(f"Key for user {user_id} removed", "success")
|
||||||
|
else:
|
||||||
|
flash(f"No key found for user {user_id}", "error")
|
||||||
|
|
||||||
|
return redirect(url_for('admin_license_keys'))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/admin/download_backup', methods=['GET'])
|
||||||
|
@admin_required
|
||||||
|
def download_backup():
|
||||||
|
if 'username' not in session:
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
licenses_data = backup.export_backup()
|
||||||
|
json_str = json.dumps(licenses_data, indent=2)
|
||||||
|
return send_file(
|
||||||
|
BytesIO(json_str.encode('utf-8')),
|
||||||
|
mimetype='application/json',
|
||||||
|
as_attachment=True,
|
||||||
|
download_name='licenses_backup.json'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/admin/upload_backup', methods=['POST'])
|
||||||
|
@admin_required
|
||||||
|
def upload_backup():
|
||||||
|
if 'username' not in session:
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
if 'file' not in request.files:
|
||||||
|
flash('No file provided', 'error')
|
||||||
|
return redirect(url_for('admin_license_keys'))
|
||||||
|
|
||||||
|
file = request.files['file']
|
||||||
|
|
||||||
|
if file.filename == '':
|
||||||
|
flash('No file selected', 'error')
|
||||||
|
return redirect(url_for('admin_license_keys'))
|
||||||
|
|
||||||
|
try:
|
||||||
|
content = file.read().decode('utf-8')
|
||||||
|
data = json.loads(content)
|
||||||
|
|
||||||
|
if backup.import_backup(data):
|
||||||
|
flash('Licenses backup restored successfully', 'success')
|
||||||
|
else:
|
||||||
|
flash('Failed to restore backup - invalid format', 'error')
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
flash('Invalid JSON file', 'error')
|
||||||
|
except Exception as e:
|
||||||
|
flash(f'Error uploading backup: {str(e)}', 'error')
|
||||||
|
|
||||||
|
return redirect(url_for('admin_license_keys'))
|
||||||
|
|
||||||
|
@app.route("/validate__information", methods=['POST'])
|
||||||
|
def validate__information():
|
||||||
|
data = request.get_json(silent=True)
|
||||||
|
if not data:
|
||||||
|
return jsonify({"error": "No JSON data provided"}), 400
|
||||||
|
license_key = data.get("license")
|
||||||
|
hwid_uuid = data.get("hwid")
|
||||||
|
if not license_key or not hwid_uuid:
|
||||||
|
return jsonify({"error": "Missing 'license' or 'hwid' in JSON data"}), 400
|
||||||
|
if verify.check(license_key, hwid_uuid):
|
||||||
|
return jsonify({"status": "ok"}), 200
|
||||||
|
else:
|
||||||
|
return jsonify({"status": "invalid"}), 402
|
||||||
|
|
||||||
|
|
||||||
@app.route('/logout')
|
@app.route('/logout')
|
||||||
def logout():
|
def logout():
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -380,6 +380,7 @@
|
|||||||
<a href="{{ url_for('admin_dashboard') }}">Dashboard</a>
|
<a href="{{ url_for('admin_dashboard') }}">Dashboard</a>
|
||||||
<a href="{{ url_for('admin_users') }}">Userverwaltung</a>
|
<a href="{{ url_for('admin_users') }}">Userverwaltung</a>
|
||||||
<a href="{{ url_for('admin_licenses') }}">Lizenzverwaltung</a>
|
<a href="{{ url_for('admin_licenses') }}">Lizenzverwaltung</a>
|
||||||
|
<a href="{{ url_for('admin_license_keys') }}">Lizenz-Key Verwaltung</a>
|
||||||
<a href="{{ url_for('admin_invoices') }}">Rechnungsverwaltung</a>
|
<a href="{{ url_for('admin_invoices') }}">Rechnungsverwaltung</a>
|
||||||
<a href="{{ url_for('admin_chats') }}">Admin-Chat</a>
|
<a href="{{ url_for('admin_chats') }}">Admin-Chat</a>
|
||||||
<a href="{{ url_for('admin_tickets') }}">Support-Center</a>
|
<a href="{{ url_for('admin_tickets') }}">Support-Center</a>
|
||||||
|
|||||||
@@ -0,0 +1,268 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Admin | Lizenz-Key Verwaltung{% endblock %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
{{ super() }}
|
||||||
|
<style>
|
||||||
|
.key-header {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-header p {
|
||||||
|
margin-top: 0.4rem;
|
||||||
|
max-width: 70ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 360px 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-card {
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #d4e0e9;
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 1rem;
|
||||||
|
box-shadow: 0 10px 28px rgba(20, 40, 55, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-card h3 {
|
||||||
|
font-size: 1.02rem;
|
||||||
|
color: #143a55;
|
||||||
|
margin-bottom: 0.65rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-form {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.55rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-form label {
|
||||||
|
font-size: 0.86rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #3f5f74;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-form select,
|
||||||
|
.key-form input,
|
||||||
|
.key-form button {
|
||||||
|
width: 100%;
|
||||||
|
font: inherit;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 0.52rem 0.62rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-form select,
|
||||||
|
.key-form input {
|
||||||
|
border: 1px solid #c7d7e2;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #17394f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-main,
|
||||||
|
.btn-alt,
|
||||||
|
.btn-danger {
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-main {
|
||||||
|
color: #ffffff;
|
||||||
|
background: linear-gradient(120deg, #0b5b89 0%, #0b4262 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-alt {
|
||||||
|
color: #0f3d59;
|
||||||
|
border: 1px solid #bfd2df;
|
||||||
|
background: #f8fbfd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
color: #7e2d2d;
|
||||||
|
border: 1px solid #e8b8b8;
|
||||||
|
background: #fff3f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-actions {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-actions a {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-table-wrap {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-table th,
|
||||||
|
.key-table td {
|
||||||
|
border-bottom: 1px solid #e0e8ef;
|
||||||
|
text-align: left;
|
||||||
|
padding: 0.68rem 0.5rem;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-table th {
|
||||||
|
font-size: 0.82rem;
|
||||||
|
color: #4b697d;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-table td {
|
||||||
|
color: #17384f;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code {
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
background: #f2f6fa;
|
||||||
|
border: 1px solid #d9e4ed;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0.2rem 0.35rem;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
display: inline-block;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 0.2rem 0.5rem;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.assigned {
|
||||||
|
border: 1px solid #9fd2bf;
|
||||||
|
color: #14684a;
|
||||||
|
background: #edfaf4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.unassigned {
|
||||||
|
border: 1px solid #ebd08c;
|
||||||
|
color: #8a6113;
|
||||||
|
background: #fff8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-form {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.key-layout {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<section class="key-header">
|
||||||
|
<h1>Lizenz-Key Verwaltung</h1>
|
||||||
|
<p>Generieren, zuweisen und sichern Sie Lizenz-Keys im gleichen Stil wie der restliche Admin-Bereich.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="key-layout">
|
||||||
|
<aside class="key-card">
|
||||||
|
<h3>Key erstellen</h3>
|
||||||
|
<form method="POST" action="{{ url_for('generate_new') }}" class="key-form">
|
||||||
|
<button type="submit" class="btn-main">Neuen Key generieren</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<h3 style="margin-top: 1rem;">Key zuweisen</h3>
|
||||||
|
<form method="POST" action="{{ url_for('allocate_key') }}" class="key-form">
|
||||||
|
<label for="alloc_user">Benutzer</label>
|
||||||
|
<select id="alloc_user" name="username" required>
|
||||||
|
<option value="">Benutzer auswaehlen</option>
|
||||||
|
{% for user in users %}
|
||||||
|
<option value="{{ user.username }}">{{ user.username }} ({{ user.display_name }})</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<label for="alloc_key">Lizenz-Key</label>
|
||||||
|
<select id="alloc_key" name="license_key" required>
|
||||||
|
<option value="">Key auswaehlen</option>
|
||||||
|
{% for item in keys %}
|
||||||
|
<option value="{{ item.license_key }}">{{ item.user_id or '----' }} | {{ item.license_key[:22] }}{% if item.license_key|length > 22 %}...{% endif %}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<button type="submit" class="btn-main">Key zuweisen</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<h3 style="margin-top: 1rem;">Backup</h3>
|
||||||
|
<div class="key-actions">
|
||||||
|
<a href="{{ url_for('download_backup') }}" class="btn-alt">Backup herunterladen</a>
|
||||||
|
<form method="POST" action="{{ url_for('upload_backup') }}" enctype="multipart/form-data" class="key-form">
|
||||||
|
<input type="file" name="file" accept=".json" required>
|
||||||
|
<button type="submit" class="btn-danger" onclick="return confirm('Backup wirklich einspielen? Aktuelle Keys werden ersetzt.');">Backup einspielen</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<article class="key-card">
|
||||||
|
<h3>Alle Keys</h3>
|
||||||
|
<div class="key-table-wrap">
|
||||||
|
<table class="key-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>User ID</th>
|
||||||
|
<th>Lizenz-Key</th>
|
||||||
|
<th>Benutzer</th>
|
||||||
|
<th>HWID</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Aktion</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for item in keys %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ item.user_id or '-' }}</td>
|
||||||
|
<td><span class="code">{{ item.license_key }}</span></td>
|
||||||
|
<td>{{ item.username or '-' }}</td>
|
||||||
|
<td>
|
||||||
|
{% if item.hwid_uuid %}
|
||||||
|
<span class="code">{{ item.hwid_uuid }}</span>
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if item.username %}
|
||||||
|
<span class="badge assigned">Zugewiesen</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge unassigned">Nicht zugewiesen</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if item.user_id %}
|
||||||
|
<form method="POST" action="{{ url_for('remove_key', user_id=item.user_id) }}" class="remove-form" onsubmit="return confirm('Key fuer User {{ item.user_id }} wirklich loeschen?');">
|
||||||
|
<button type="submit" class="btn-danger">Entfernen</button>
|
||||||
|
</form>
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="6">Keine Lizenz-Keys gefunden.</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
import os
|
||||||
|
import secrets
|
||||||
|
from pymongo import MongoClient
|
||||||
|
from pymongo.errors import PyMongoError
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
INVOICE_UPLOAD_DIR = os.path.join(BASE_DIR, "static", "uploads", "invoices")
|
||||||
|
MONGO_URI = os.environ.get("MONGO_URI", "mongodb://localhost:27017")
|
||||||
|
MONGO_DB_NAME = os.environ.get("MONGO_DB_NAME", "Invario_Website")
|
||||||
|
LICENSE_COLLECTION = "licenses"
|
||||||
|
|
||||||
|
|
||||||
|
def _get_collection():
|
||||||
|
client = MongoClient(MONGO_URI, serverSelectionTimeoutMS=1200)
|
||||||
|
return client, client[MONGO_DB_NAME][LICENSE_COLLECTION]
|
||||||
|
|
||||||
|
|
||||||
|
def _next_user_id(col) -> str:
|
||||||
|
existing_ids = []
|
||||||
|
for item in col.find({}, {"user_id": 1, "_id": 0}):
|
||||||
|
user_id = str(item.get("user_id", "")).strip()
|
||||||
|
if user_id.isdigit():
|
||||||
|
existing_ids.append(int(user_id))
|
||||||
|
next_id = (max(existing_ids) + 1) if existing_ids else 1
|
||||||
|
return f"{next_id:04d}"
|
||||||
|
|
||||||
|
|
||||||
|
def load_file() -> list:
|
||||||
|
client = None
|
||||||
|
try:
|
||||||
|
client, col = _get_collection()
|
||||||
|
docs = list(col.find({}, {"_id": 0}).sort("created_at", 1))
|
||||||
|
return docs
|
||||||
|
except PyMongoError:
|
||||||
|
return []
|
||||||
|
finally:
|
||||||
|
if client:
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
def check(license_key, hwid_uuid) -> bool:
|
||||||
|
client = None
|
||||||
|
try:
|
||||||
|
client, col = _get_collection()
|
||||||
|
doc = col.find_one({"license_key": str(license_key)})
|
||||||
|
if not doc:
|
||||||
|
return False
|
||||||
|
|
||||||
|
current_hwid = str(doc.get("hwid_uuid", ""))
|
||||||
|
if current_hwid == str(hwid_uuid):
|
||||||
|
return True
|
||||||
|
|
||||||
|
if current_hwid == "":
|
||||||
|
col.update_one(
|
||||||
|
{"_id": doc.get("_id")},
|
||||||
|
{
|
||||||
|
"$set": {
|
||||||
|
"hwid_uuid": str(hwid_uuid),
|
||||||
|
"updated_at": datetime.utcnow().isoformat(timespec="seconds") + "Z",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
except PyMongoError:
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
if client:
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
def register_new_Key(data_stream):
|
||||||
|
if not isinstance(data_stream, list):
|
||||||
|
return False
|
||||||
|
|
||||||
|
client = None
|
||||||
|
try:
|
||||||
|
client, col = _get_collection()
|
||||||
|
col.delete_many({})
|
||||||
|
prepared = []
|
||||||
|
for item in data_stream:
|
||||||
|
if not isinstance(item, dict):
|
||||||
|
continue
|
||||||
|
prepared.append(
|
||||||
|
{
|
||||||
|
"user_id": str(item.get("user_id", "")).strip(),
|
||||||
|
"license_key": str(item.get("license_key", "")).strip(),
|
||||||
|
"hwid_uuid": str(item.get("hwid_uuid", "")).strip(),
|
||||||
|
"created_at": item.get("created_at") or datetime.utcnow().isoformat(timespec="seconds") + "Z",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if prepared:
|
||||||
|
col.insert_many(prepared)
|
||||||
|
return True
|
||||||
|
except PyMongoError:
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
if client:
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
def new_key() -> str:
|
||||||
|
generated_key = key_generator()
|
||||||
|
client = None
|
||||||
|
try:
|
||||||
|
client, col = _get_collection()
|
||||||
|
col.insert_one(
|
||||||
|
{
|
||||||
|
"user_id": _next_user_id(col),
|
||||||
|
"license_key": generated_key,
|
||||||
|
"hwid_uuid": "",
|
||||||
|
"created_at": datetime.utcnow().isoformat(timespec="seconds") + "Z",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
except PyMongoError:
|
||||||
|
return ""
|
||||||
|
finally:
|
||||||
|
if client:
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
return generated_key
|
||||||
|
|
||||||
|
|
||||||
|
def remove_key(user_id: str) -> bool:
|
||||||
|
client = None
|
||||||
|
try:
|
||||||
|
client, col = _get_collection()
|
||||||
|
result = col.delete_one({"user_id": str(user_id)})
|
||||||
|
return result.deleted_count > 0
|
||||||
|
except PyMongoError:
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
if client:
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
def key_generator():
|
||||||
|
return secrets.token_urlsafe(32)
|
||||||
Reference in New Issue
Block a user