Add 6 new tools: Hasher, Base64, JWT Decoder, Password Gen, Timestamp, Text Diff

- backend/tools/hasher.py: POST /api/hash/sha256 and /api/hash/bcrypt (bcrypt added to requirements)
- backend/tools/base64tool.py: POST /api/base64/encode and /api/base64/decode
- backend/tools/jwtdecoder.py: POST /api/jwt/decode (signature verification disabled)
- backend/tools/passwordgen.py: POST /api/password/generate with charset and length options
- backend/tools/timestamp.py: POST /api/timestamp/convert (unix<->date, ISO 8601 + German format)
- backend/tools/textdiff.py: POST /api/text/diff returning structured added/removed/unchanged lines
- All blueprints registered in app.py and tools/__init__.py
- React components with copy button, dark/light mode support via CSS variables
- ToolOverview rebuilt as card grid; App.jsx routes added for all tools

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Nirodan
2026-04-24 14:28:18 +02:00
parent 80ec5eca7b
commit 7f9c5c874a
17 changed files with 788 additions and 6 deletions
+6
View File
@@ -1 +1,7 @@
from .md5 import md5_blueprint
from .hasher import hasher_blueprint
from .base64tool import base64_blueprint
from .jwtdecoder import jwt_decoder_blueprint
from .passwordgen import passwordgen_blueprint
from .timestamp import timestamp_blueprint
from .textdiff import textdiff_blueprint
+38
View File
@@ -0,0 +1,38 @@
from flask import Blueprint, request, jsonify
import base64
from util.logger import logger
from auth.token import verify_token
base64_blueprint = Blueprint('base64_tool', __name__)
@base64_blueprint.route('/api/base64/encode', methods=['POST'])
def encode():
user = verify_token()
if not user:
return jsonify({"message": "Nicht autorisiert"}), 401
try:
data = request.get_json()
text = data.get("text", "")
result = base64.b64encode(text.encode()).decode()
logger.info(f"Base64 kodiert von {user['username']}")
return jsonify({"result": result})
except Exception as e:
logger.error(f"Fehler Base64 encode: {e}")
return jsonify({"message": "Fehler beim Kodieren"}), 500
@base64_blueprint.route('/api/base64/decode', methods=['POST'])
def decode():
user = verify_token()
if not user:
return jsonify({"message": "Nicht autorisiert"}), 401
try:
data = request.get_json()
text = data.get("text", "").strip()
result = base64.b64decode(text.encode()).decode('utf-8')
logger.info(f"Base64 dekodiert von {user['username']}")
return jsonify({"result": result})
except Exception as e:
logger.error(f"Fehler Base64 decode: {e}")
return jsonify({"message": "Ungültiger Base64-Input"}), 400
+39
View File
@@ -0,0 +1,39 @@
from flask import Blueprint, request, jsonify
import hashlib
import bcrypt
from util.logger import logger
from auth.token import verify_token
hasher_blueprint = Blueprint('hasher_tool', __name__)
@hasher_blueprint.route('/api/hash/sha256', methods=['POST'])
def hash_sha256():
user = verify_token()
if not user:
return jsonify({"message": "Nicht autorisiert"}), 401
try:
data = request.get_json()
text = data.get("text", "")
result = hashlib.sha256(text.encode()).hexdigest()
logger.info(f"SHA256 erstellt von {user['username']}")
return jsonify({"sha256": result, "by": user['username']})
except Exception as e:
logger.error(f"Fehler SHA256: {e}")
return jsonify({"message": "Fehler beim Hashen"}), 500
@hasher_blueprint.route('/api/hash/bcrypt', methods=['POST'])
def hash_bcrypt():
user = verify_token()
if not user:
return jsonify({"message": "Nicht autorisiert"}), 401
try:
data = request.get_json()
text = data.get("text", "")
hashed = bcrypt.hashpw(text.encode(), bcrypt.gensalt()).decode()
logger.info(f"bcrypt erstellt von {user['username']}")
return jsonify({"bcrypt": hashed, "by": user['username']})
except Exception as e:
logger.error(f"Fehler bcrypt: {e}")
return jsonify({"message": "Fehler beim Hashen"}), 500
+29
View File
@@ -0,0 +1,29 @@
from flask import Blueprint, request, jsonify
import jwt
from datetime import datetime, timezone
from util.logger import logger
from auth.token import verify_token
jwt_decoder_blueprint = Blueprint('jwt_decoder_tool', __name__)
@jwt_decoder_blueprint.route('/api/jwt/decode', methods=['POST'])
def decode_jwt():
user = verify_token()
if not user:
return jsonify({"message": "Nicht autorisiert"}), 401
try:
data = request.get_json()
token = data.get("token", "").strip()
header = jwt.get_unverified_header(token)
payload = jwt.decode(token, options={"verify_signature": False})
expired = False
if "exp" in payload:
expired = payload["exp"] < datetime.now(timezone.utc).timestamp()
logger.info(f"JWT dekodiert von {user['username']}")
return jsonify({"header": header, "payload": payload, "expired": expired})
except Exception as e:
logger.error(f"Fehler JWT decode: {e}")
return jsonify({"message": "Ungültiger JWT Token"}), 400
+41
View File
@@ -0,0 +1,41 @@
from flask import Blueprint, request, jsonify
import secrets
import string
from util.logger import logger
from auth.token import verify_token
passwordgen_blueprint = Blueprint('passwordgen_tool', __name__)
@passwordgen_blueprint.route('/api/password/generate', methods=['POST'])
def generate_password():
user = verify_token()
if not user:
return jsonify({"message": "Nicht autorisiert"}), 401
try:
data = request.get_json()
length = min(max(int(data.get("length", 16)), 8), 64)
use_uppercase = data.get("uppercase", True)
use_lowercase = data.get("lowercase", True)
use_numbers = data.get("numbers", True)
use_symbols = data.get("symbols", False)
charset = ""
if use_uppercase:
charset += string.ascii_uppercase
if use_lowercase:
charset += string.ascii_lowercase
if use_numbers:
charset += string.digits
if use_symbols:
charset += string.punctuation
if not charset:
return jsonify({"message": "Mindestens ein Zeichensatz muss ausgewählt sein"}), 400
password = ''.join(secrets.choice(charset) for _ in range(length))
logger.info(f"Passwort generiert von {user['username']}")
return jsonify({"password": password})
except Exception as e:
logger.error(f"Fehler Passwortgenerator: {e}")
return jsonify({"message": "Fehler beim Generieren"}), 500
+37
View File
@@ -0,0 +1,37 @@
from flask import Blueprint, request, jsonify
import difflib
from util.logger import logger
from auth.token import verify_token
textdiff_blueprint = Blueprint('textdiff_tool', __name__)
@textdiff_blueprint.route('/api/text/diff', methods=['POST'])
def text_diff():
user = verify_token()
if not user:
return jsonify({"message": "Nicht autorisiert"}), 401
try:
data = request.get_json()
text1 = data.get("text1", "")
text2 = data.get("text2", "")
lines1 = text1.splitlines(keepends=True)
lines2 = text2.splitlines(keepends=True)
result = []
for line in difflib.unified_diff(lines1, lines2, lineterm=''):
if line.startswith('+++') or line.startswith('---') or line.startswith('@@'):
continue
if line.startswith('+'):
result.append({"type": "added", "text": line[1:]})
elif line.startswith('-'):
result.append({"type": "removed", "text": line[1:]})
else:
result.append({"type": "unchanged", "text": line[1:] if line.startswith(' ') else line})
logger.info(f"Text-Diff erstellt von {user['username']}")
return jsonify({"diff": result})
except Exception as e:
logger.error(f"Fehler Text-Diff: {e}")
return jsonify({"message": "Fehler beim Vergleich"}), 500
+46
View File
@@ -0,0 +1,46 @@
from flask import Blueprint, request, jsonify
from datetime import datetime, timezone
from util.logger import logger
from auth.token import verify_token
timestamp_blueprint = Blueprint('timestamp_tool', __name__)
_DATE_FORMATS = [
"%Y-%m-%dT%H:%M:%S",
"%Y-%m-%d %H:%M:%S",
"%Y-%m-%d",
"%d.%m.%Y %H:%M:%S",
"%d.%m.%Y",
]
@timestamp_blueprint.route('/api/timestamp/convert', methods=['POST'])
def convert_timestamp():
user = verify_token()
if not user:
return jsonify({"message": "Nicht autorisiert"}), 401
try:
data = request.get_json()
value = data.get("value", "").strip()
direction = data.get("direction", "unix_to_date")
if direction == "unix_to_date":
ts = float(value)
dt_utc = datetime.fromtimestamp(ts, tz=timezone.utc)
return jsonify({"utc": dt_utc.isoformat(), "unix": int(ts)})
dt = None
for fmt in _DATE_FORMATS:
try:
dt = datetime.strptime(value, fmt).replace(tzinfo=timezone.utc)
break
except ValueError:
continue
if dt is None:
return jsonify({"message": "Ungültiges Datumsformat"}), 400
logger.info(f"Timestamp konvertiert von {user['username']}")
return jsonify({"unix": int(dt.timestamp()), "utc": dt.isoformat()})
except Exception as e:
logger.error(f"Fehler Timestamp: {e}")
return jsonify({"message": "Ungültiger Wert"}), 400