diff --git a/.gitignore b/.gitignore index 1c3e282..7fc0503 100644 --- a/.gitignore +++ b/.gitignore @@ -64,8 +64,9 @@ htmlcov/ # Output test files. test_data/*.png -# Dev scripts (local convenience scripts) -scripts/ +# Dev scripts (local convenience scripts - except validate-release.sh) +scripts/* +!scripts/validate-release.sh # Web UI auth database and SSL certs frontends/web/instance/ diff --git a/PLAN-4.1.2.md b/PLAN-4.1.2.md index ecb3b01..dbf7110 100644 --- a/PLAN-4.1.2.md +++ b/PLAN-4.1.2.md @@ -7,7 +7,7 @@ Polish and UX improvements after the 4.1.1 stability release. ## 1. Real Progress Bar for Encode/Decode -**Status:** Planned +**Status:** Done **Problem:** Users see elapsed time but no indication of how far along the operation is. Long DCT encodes on Pi can take 2-3 minutes with no feedback. @@ -76,7 +76,7 @@ Polish and UX improvements after the 4.1.1 stability release. ## 3. Mobile-Responsive Polish -**Status:** Planned +**Status:** Done **Problem:** UI works on mobile but has rough edges - cramped buttons, hard-to-tap targets, awkward layouts on small screens. @@ -203,7 +203,7 @@ Polish and UX improvements after the 4.1.1 stability release. ## 8. Release Validation Script -**Status:** Planned +**Status:** Done **Problem:** Manual release checklist is error-prone. Need automated validation. @@ -245,6 +245,6 @@ Polish and UX improvements after the 4.1.1 stability release. ## Notes -- Keep 4.1.2 focused - 9 features (6 done) +- Keep 4.1.2 focused - 9 features (9 done) - Don't break DCT compatibility (4.1.1 RS format is stable) - Test on Pi before release diff --git a/frontends/web/static/style.css b/frontends/web/static/style.css index 3aff3d0..4f2d6f3 100644 --- a/frontends/web/static/style.css +++ b/frontends/web/static/style.css @@ -1442,3 +1442,260 @@ footer { padding: 0.35rem 0.75rem; background: rgba(0, 0, 0, 0.1); } + +/* ============================================================================ + MOBILE RESPONSIVE IMPROVEMENTS + ============================================================================ */ + +/* Mobile-specific drop zone improvements */ +@media (max-width: 768px) { + /* Larger drop zones on mobile for easier touch targets */ + .drop-zone { + padding: 2rem 1.5rem; + min-height: 140px; + } + + /* Larger touch target for upload icons */ + .drop-zone-label i { + font-size: 2.5rem !important; + } + + /* Touch feedback - active state */ + .drop-zone:active { + border-color: var(--gradient-start); + background: rgba(102, 126, 234, 0.15); + transform: scale(0.98); + } + + /* Mode buttons - stack vertically on very small screens */ + .d-flex.gap-2:has(.mode-btn) { + flex-direction: column; + } + + .mode-btn { + padding: 1rem; + min-height: 56px; /* iOS touch target minimum */ + } + + /* Full-width primary buttons */ + .btn-primary.btn-lg { + padding: 1rem 1.5rem; + font-size: 1.1rem; + min-height: 56px; + } + + /* Security factor boxes - more padding for touch */ + .security-box { + padding: 1.25rem; + } + + /* Form controls - larger for touch */ + .form-control, + .form-select { + padding: 0.75rem 1rem; + font-size: 1rem; + min-height: 48px; + } + + /* Input groups - consistent sizing */ + .input-group .form-control { + min-height: 48px; + } + + .input-group .btn { + min-width: 48px; + padding: 0.75rem; + } + + /* Password toggle button - easier to tap */ + [data-toggle-password] { + min-width: 52px; + } + + /* PIN input - larger on mobile */ + .pin-input-container .form-control { + font-size: 1.4rem; + letter-spacing: 4px; + padding: 0.875rem 1rem; + } + + /* Passphrase input - comfortable mobile size */ + .passphrase-input { + font-size: 1rem !important; + padding: 0.875rem 1rem !important; + } + + /* Card headers - compact on mobile */ + .card-header h5 { + font-size: 1.1rem; + } + + /* Alert info panel - readable text */ + .alert.small { + font-size: 0.9rem; + } + + /* Bottom info icons - larger tap targets */ + .row.text-center .col-4 { + padding: 0.5rem; + } + + .row.text-center .col-4 i { + font-size: 2rem !important; + } + + /* Capacity panel badges - easier to read */ + #capacityPanel .badge { + font-size: 0.8rem; + padding: 0.4rem 0.6rem; + } + + /* Payload type toggle - full width buttons */ + .btn-group[role="group"] { + flex-direction: row; + } + + .btn-group .btn { + padding: 0.75rem 0.5rem; + font-size: 0.95rem; + } + + /* Textarea - comfortable height */ + textarea.form-control { + min-height: 120px; + } + + /* Channel select - full width */ + #channelSelect { + font-size: 1rem; + } +} + +/* Very small screens (iPhone SE, etc.) */ +@media (max-width: 375px) { + .drop-zone { + padding: 1.5rem 1rem; + } + + .mode-btn { + padding: 0.875rem; + font-size: 0.9rem; + } + + .mode-btn .text-muted { + display: none; /* Hide secondary text on tiny screens */ + } + + .card-header h5 { + font-size: 1rem; + } + + /* Stack security factor row */ + .row:has(.security-box) > .col-md-6 { + margin-bottom: 1rem; + } +} + +/* Touch device optimizations */ +@media (hover: none) and (pointer: coarse) { + /* Remove hover effects that don't work on touch */ + .btn-primary:hover { + transform: none; + } + + .feature-card:hover { + transform: none; + } + + .card-link:hover .feature-card { + transform: none; + } + + /* Add active states instead */ + .btn-primary:active { + transform: scale(0.98); + box-shadow: 0 2px 10px rgba(102, 126, 234, 0.3); + } + + .feature-card:active { + transform: scale(0.98); + } + + /* Drop zone active feedback */ + .drop-zone:active { + border-color: var(--gradient-start); + background: rgba(102, 126, 234, 0.1); + } + + /* Mode button active state */ + .mode-btn:active { + background: rgba(255, 255, 255, 0.12); + border-color: var(--gradient-start); + } +} + +/* Camera hint for mobile - shows on file inputs */ +@media (max-width: 768px) { + .drop-zone-label span.text-muted { + display: block; + } + + /* Add camera icon hint on mobile */ + .drop-zone-label::after { + content: "Tap to take photo or choose file"; + display: block; + font-size: 0.75rem; + color: rgba(255, 255, 255, 0.4); + margin-top: 0.5rem; + } + + /* Hide the default text and show mobile version */ + .drop-zone-label > span.text-muted { + display: none; + } +} + +/* Navbar mobile adjustments */ +@media (max-width: 768px) { + .navbar { + padding: 0.5rem 1rem; + } + + .navbar-brand img { + height: 32px; + } + + /* Sticky header shouldn't eat too much space */ + .navbar.sticky-top { + position: relative; /* Don't stick on mobile - saves screen space */ + } +} + +/* Results page mobile adjustments */ +@media (max-width: 768px) { + /* Download button - full width on mobile */ + .btn-success.btn-lg, + a.btn-success.btn-lg { + width: 100%; + padding: 1rem; + font-size: 1.1rem; + } + + /* QR codes - appropriate sizing */ + .qr-scan-container { + max-width: 280px; + margin: 0 auto; + } + + /* Message display - readable on mobile */ + .alert-message { + font-size: 0.9rem; + padding: 1rem; + word-break: break-word; + } + + /* Result icon - slightly smaller on mobile */ + .result-icon { + font-size: 3rem; + } +} diff --git a/pyproject.toml b/pyproject.toml index 02329ec..a4719fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "stegasoo" -version = "4.1.1" +version = "4.1.2" description = "Secure steganography with hybrid photo + passphrase + PIN authentication" readme = "README.md" license = "MIT" diff --git a/scripts/validate-release.sh b/scripts/validate-release.sh new file mode 100755 index 0000000..d0eebde --- /dev/null +++ b/scripts/validate-release.sh @@ -0,0 +1,307 @@ +#!/bin/bash +# ============================================================================= +# Stegasoo Release Validation Script +# ============================================================================= +# Automated pre-release validation to catch issues before tagging a release. +# +# Usage: +# ./scripts/validate-release.sh # Local validation only +# ./scripts/validate-release.sh --pi # Include Pi smoke test +# PI_IP=192.168.0.4 ./scripts/validate-release.sh --pi +# +# Exit codes: +# 0 = All tests passed +# 1 = One or more tests failed +# ============================================================================= + +# Don't use set -e as we need to handle test failures gracefully + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +CYAN='\033[0;36m' +NC='\033[0m' + +# Default Pi IP (can be overridden via environment) +PI_IP="${PI_IP:-192.168.0.4}" +PI_USER="${PI_USER:-alee}" +INCLUDE_PI=false +INCLUDE_DOCKER=true + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + --pi) + INCLUDE_PI=true + shift + ;; + --no-docker) + INCLUDE_DOCKER=false + shift + ;; + --help|-h) + echo "Usage: $0 [--pi] [--no-docker]" + echo "" + echo "Options:" + echo " --pi Include Pi smoke test (requires SSH access)" + echo " --no-docker Skip Docker build/test" + echo "" + echo "Environment:" + echo " PI_IP Pi IP address (default: 192.168.0.4)" + echo " PI_USER Pi SSH user (default: alee)" + exit 0 + ;; + *) + echo "Unknown option: $1" + exit 1 + ;; + esac +done + +# Track results +TESTS_RUN=0 +TESTS_PASSED=0 +TESTS_FAILED=0 +FAILED_TESTS=() + +# Helper functions +pass() { + echo -e "${GREEN}[PASS]${NC} $1" + ((TESTS_PASSED++)) + ((TESTS_RUN++)) +} + +fail() { + echo -e "${RED}[FAIL]${NC} $1" + FAILED_TESTS+=("$1") + ((TESTS_FAILED++)) + ((TESTS_RUN++)) +} + +skip() { + echo -e "${YELLOW}[SKIP]${NC} $1" +} + +section() { + echo "" + echo -e "${CYAN}━━━ $1 ━━━${NC}" +} + +# ============================================================================= +# Header +# ============================================================================= +echo -e "${CYAN}╔═══════════════════════════════════════════════════════════════╗${NC}" +echo -e "${CYAN}║ Stegasoo Release Validation ║${NC}" +echo -e "${CYAN}╚═══════════════════════════════════════════════════════════════╝${NC}" +echo "" + +# Get version from pyproject.toml +VERSION=$(grep '^version = ' pyproject.toml | head -1 | cut -d'"' -f2) +echo -e "Version: ${YELLOW}${VERSION}${NC}" +echo -e "Branch: ${YELLOW}$(git branch --show-current)${NC}" +echo "" + +# ============================================================================= +# 1. Code Quality Checks +# ============================================================================= +section "Code Quality" + +# Ruff linting +if command -v ./venv/bin/ruff &> /dev/null; then + echo -n "Running ruff check... " + if ./venv/bin/ruff check src/ frontends/ --quiet 2>/dev/null; then + pass "Ruff linting" + else + fail "Ruff linting (run: ./venv/bin/ruff check src/ frontends/)" + fi +else + skip "Ruff not installed" +fi + +# ============================================================================= +# 2. Unit Tests (if they exist) +# ============================================================================= +section "Unit Tests" + +if ls tests/test_*.py 1> /dev/null 2>&1; then + echo -n "Running pytest... " + if ./venv/bin/pytest tests/ -q --tb=no 2>/dev/null; then + pass "Pytest unit tests" + else + fail "Pytest unit tests" + fi +else + skip "No unit tests found (tests/test_*.py)" +fi + +# ============================================================================= +# 3. Import Tests +# ============================================================================= +section "Import Tests" + +# Test core library import +echo -n "Testing stegasoo import... " +if ./venv/bin/python -c "from stegasoo import encode, decode; print('OK')" 2>/dev/null | grep -q OK; then + pass "Core library import" +else + fail "Core library import" +fi + +# Test DCT support +echo -n "Testing DCT support... " +if ./venv/bin/python -c "from stegasoo import has_dct_support; assert has_dct_support(), 'No DCT'; print('OK')" 2>/dev/null | grep -q OK; then + pass "DCT support available" +else + fail "DCT support (scipy/jpegio missing?)" +fi + +# Test CLI import +echo -n "Testing CLI import... " +if ./venv/bin/python -c "from stegasoo.cli import main; print('OK')" 2>/dev/null | grep -q OK; then + pass "CLI module import" +else + fail "CLI module import" +fi + +# ============================================================================= +# 4. Encode/Decode Sanity Test +# ============================================================================= +section "Encode/Decode Test" + +echo -n "Running encode/decode sanity check... " +SANITY_RESULT=$(./venv/bin/python << 'EOF' 2>&1 +import sys +sys.path.insert(0, 'src') +from stegasoo import encode, decode + +with open('test_data/carrier.jpg', 'rb') as f: + carrier = f.read() +with open('test_data/ref.jpg', 'rb') as f: + ref = f.read() + +# LSB test +result = encode(message="sanity test", reference_photo=ref, carrier_image=carrier, + passphrase="test", pin="123456", embed_mode="lsb") +decoded = decode(stego_image=result.stego_image, reference_photo=ref, + passphrase="test", pin="123456", embed_mode="lsb") +assert decoded.message == "sanity test", f"LSB mismatch: {decoded.message}" + +# DCT test +result = encode(message="dct sanity", reference_photo=ref, carrier_image=carrier, + passphrase="dct", pin="654321", embed_mode="dct") +decoded = decode(stego_image=result.stego_image, reference_photo=ref, + passphrase="dct", pin="654321", embed_mode="dct") +assert decoded.message == "dct sanity", f"DCT mismatch: {decoded.message}" + +print("OK") +EOF +) + +if echo "$SANITY_RESULT" | grep -q "OK"; then + pass "Encode/decode sanity (LSB + DCT)" +else + fail "Encode/decode sanity: $SANITY_RESULT" +fi + +# ============================================================================= +# 5. Docker Build & Test (optional) +# ============================================================================= +if $INCLUDE_DOCKER; then + section "Docker" + + if command -v docker &> /dev/null || command -v sudo &> /dev/null; then + DOCKER_CMD="docker" + if ! docker info &>/dev/null 2>&1; then + DOCKER_CMD="sudo docker" + fi + + echo -n "Building Docker image... " + if $DOCKER_CMD build -t stegasoo:validate -q . >/dev/null 2>&1; then + pass "Docker build" + + # Test container starts + echo -n "Testing container startup... " + CONTAINER_ID=$($DOCKER_CMD run -d -p 15000:5000 stegasoo:validate 2>/dev/null) + sleep 3 + + if curl -s -o /dev/null -w "%{http_code}" http://localhost:15000/ 2>/dev/null | grep -qE "200|302"; then + pass "Container responds to HTTP" + else + fail "Container HTTP response" + fi + + # Cleanup + $DOCKER_CMD stop "$CONTAINER_ID" >/dev/null 2>&1 || true + $DOCKER_CMD rm "$CONTAINER_ID" >/dev/null 2>&1 || true + else + fail "Docker build" + fi + + # Cleanup test image + $DOCKER_CMD rmi stegasoo:validate >/dev/null 2>&1 || true + else + skip "Docker not available" + fi +else + skip "Docker tests (use --docker to enable)" +fi + +# ============================================================================= +# 6. Pi Smoke Test (optional) +# ============================================================================= +if $INCLUDE_PI; then + section "Pi Smoke Test" + + echo -n "Testing SSH connectivity to $PI_USER@$PI_IP... " + if ssh -o ConnectTimeout=5 -o BatchMode=yes "$PI_USER@$PI_IP" "echo OK" 2>/dev/null | grep -q OK; then + pass "SSH connectivity" + + echo -n "Checking stegasoo service status... " + if ssh "$PI_USER@$PI_IP" "systemctl is-active stegasoo" 2>/dev/null | grep -q active; then + pass "Stegasoo service running" + + echo -n "Running smoke test on Pi... " + SMOKE_RESULT=$(ssh "$PI_USER@$PI_IP" "cd /home/$PI_USER/stegasoo && bash tests/smoke-test.sh --quick 2>&1" || echo "FAILED") + if echo "$SMOKE_RESULT" | grep -qE "All tests passed|PASS"; then + pass "Pi smoke test" + else + fail "Pi smoke test" + fi + else + fail "Stegasoo service not running" + fi + else + fail "SSH connectivity to Pi" + fi +else + skip "Pi smoke test (use --pi to enable)" +fi + +# ============================================================================= +# Summary +# ============================================================================= +echo "" +echo -e "${CYAN}━━━ Summary ━━━${NC}" +echo "" +echo -e "Tests run: ${TESTS_RUN}" +echo -e "Passed: ${GREEN}${TESTS_PASSED}${NC}" +echo -e "Failed: ${RED}${TESTS_FAILED}${NC}" + +if [ ${#FAILED_TESTS[@]} -gt 0 ]; then + echo "" + echo -e "${RED}Failed tests:${NC}" + for test in "${FAILED_TESTS[@]}"; do + echo -e " - $test" + done +fi + +echo "" +if [ $TESTS_FAILED -eq 0 ]; then + echo -e "${GREEN}✓ All validation checks passed!${NC}" + echo -e " Ready to tag release ${VERSION}" + exit 0 +else + echo -e "${RED}✗ Validation failed - fix issues before release${NC}" + exit 1 +fi diff --git a/src/stegasoo/__init__.py b/src/stegasoo/__init__.py index f2ab652..ec9c845 100644 --- a/src/stegasoo/__init__.py +++ b/src/stegasoo/__init__.py @@ -7,7 +7,7 @@ Changes in v4.0.0: - encode() and decode() now accept channel_key parameter """ -__version__ = "4.0.1" +__version__ = "4.1.2" # Core functionality # Channel key management (v4.0.0) diff --git a/src/stegasoo/cli.py b/src/stegasoo/cli.py index 4db7b65..ecb9185 100644 --- a/src/stegasoo/cli.py +++ b/src/stegasoo/cli.py @@ -105,6 +105,7 @@ def encode( stegasoo encode photo.png -r ref.jpg -f secret.pdf -o encoded.png """ from PIL import Image + from .encode import encode as stegasoo_encode from .encode import encode_file as stegasoo_encode_file @@ -1108,7 +1109,9 @@ def admin_recover(db_path, password): stegasoo admin recover --db /path/to/stegasoo.db """ import sqlite3 + from argon2 import PasswordHasher + from .recovery import verify_recovery_key # Try default paths if not specified diff --git a/src/stegasoo/constants.py b/src/stegasoo/constants.py index de2991c..7474f3d 100644 --- a/src/stegasoo/constants.py +++ b/src/stegasoo/constants.py @@ -25,7 +25,7 @@ from pathlib import Path # VERSION # ============================================================================ -__version__ = "4.1.0" +__version__ = "4.1.2" # ============================================================================ # FILE FORMAT