diff --git a/PLAN-4.1.0.md b/PLAN-4.1.0.md index af75e35..0ecaf41 100644 --- a/PLAN-4.1.0.md +++ b/PLAN-4.1.0.md @@ -426,6 +426,23 @@ Or simpler: detect on startup, update schema automatically (current pattern). ## Progress - [x] Multi-User Support (commit 7b33501) -- [ ] Channel Key QR (Web UI) +- [x] Channel Key QR (Web UI) - added QR generator on About page - [x] CLI Channel Commands -- [ ] Advanced Tools +- [x] Saved Channel Keys (Web UI) - users can save/manage channel keys +- [ ] Advanced Tools (in progress) + +--- + +## Action Item: Architectural Review + +Review other modules for consistency with the Library → CLI → API → WebUI pattern: + +| Module | Library | CLI | API | WebUI | Notes | +|--------|---------|-----|-----|-------|-------| +| encode | ✓ | ✓ | ✓ | ✓ | Review for consistency | +| decode | ✓ | ✓ | ✓ | ✓ | Review for consistency | +| channel | ✓ | ✓ | - | ✓ | Needs API layer? | +| tools | ✓ | WIP | ✓ | WIP | Building now | +| generate | ✓ | ? | - | ✓ | CLI for credential gen? | + +Priority order: Developer/CLI → API integrator → WebUI end-user diff --git a/frontends/web/app.py b/frontends/web/app.py index 9e450b9..7b18261 100644 --- a/frontends/web/app.py +++ b/frontends/web/app.py @@ -1288,6 +1288,88 @@ def about(): return render_template("about.html", has_argon2=has_argon2(), has_qrcode_read=HAS_QRCODE_READ) +# ============================================================================ +# TOOLS ROUTES (v4.1.0) +# ============================================================================ + + +@app.route("/tools") +@login_required +def tools(): + """Advanced tools page.""" + return render_template("tools.html", has_dct=has_dct_support()) + + +@app.route("/api/tools/capacity", methods=["POST"]) +@login_required +def api_tools_capacity(): + """Calculate image capacity for steganography.""" + from stegasoo.dct_steganography import estimate_capacity_comparison + + carrier = request.files.get("image") + if not carrier: + return jsonify({"success": False, "error": "No image provided"}), 400 + + try: + image_data = carrier.read() + result = estimate_capacity_comparison(image_data) + result["success"] = True + result["filename"] = carrier.filename + result["megapixels"] = round((result["width"] * result["height"]) / 1_000_000, 2) + return jsonify(result) + except Exception as e: + return jsonify({"success": False, "error": str(e)}), 400 + + +@app.route("/api/tools/strip-metadata", methods=["POST"]) +@login_required +def api_tools_strip_metadata(): + """Strip EXIF/metadata from image.""" + import io + + from stegasoo.utils import strip_image_metadata + + image_file = request.files.get("image") + if not image_file: + return jsonify({"success": False, "error": "No image provided"}), 400 + + try: + image_data = image_file.read() + clean_data = strip_image_metadata(image_data, output_format="PNG") + + buffer = io.BytesIO(clean_data) + filename = image_file.filename.rsplit(".", 1)[0] + "_clean.png" + + return send_file( + buffer, + mimetype="image/png", + as_attachment=True, + download_name=filename + ) + except Exception as e: + return jsonify({"success": False, "error": str(e)}), 400 + + +@app.route("/api/tools/peek", methods=["POST"]) +@login_required +def api_tools_peek(): + """Check if image contains Stegasoo header.""" + from stegasoo.steganography import peek_image + + image_file = request.files.get("image") + if not image_file: + return jsonify({"success": False, "error": "No image provided"}), 400 + + try: + image_data = image_file.read() + result = peek_image(image_data) + result["success"] = True + result["filename"] = image_file.filename + return jsonify(result) + except Exception as e: + return jsonify({"success": False, "error": str(e)}), 400 + + # Add these two test routes anywhere in app.py after the app = Flask(...) line: diff --git a/frontends/web/templates/base.html b/frontends/web/templates/base.html index 5054bf0..cc4b4cb 100644 --- a/frontends/web/templates/base.html +++ b/frontends/web/templates/base.html @@ -38,6 +38,9 @@ + {% if auth_enabled %} {% if is_authenticated %}