From 21f67d39c786d81d814696fc331b663ea8e88065 Mon Sep 17 00:00:00 2001 From: Omair Saleh Date: Mon, 2 Mar 2026 23:04:22 +0800 Subject: [PATCH] feat: add simple password auth gate - Flask session-based login with styled dark-theme login page - All routes gated behind password (configurable via SITE_PASSWORD env) - /login and /api/health are public - Wrong password shows red error, correct redirects to original page - 30-day session persistence - /logout to clear session - Password: jv2026 (set in docker-compose.yml) --- app.py | 90 ++++++++++++++++++++++++++++++++++++++++++++-- docker-compose.yml | 2 ++ 2 files changed, 90 insertions(+), 2 deletions(-) diff --git a/app.py b/app.py index 9d66504..6c59256 100644 --- a/app.py +++ b/app.py @@ -2,10 +2,12 @@ Live proposal site with real Gemini-powered demos. PostgreSQL-backed data dashboard.""" -import os, json, time, traceback, logging +import os, json, time, traceback, logging, secrets from pathlib import Path +from functools import wraps from flask import (Flask, render_template, jsonify, request, - send_from_directory, redirect) + send_from_directory, redirect, session, url_for, + make_response) from scraper import scrape_product, scrape_competitor from ai_engine import (generate_asset_pack, competitor_xray, pdp_surgeon, @@ -18,6 +20,9 @@ app = Flask(__name__, static_folder="static", template_folder="templates") +app.secret_key = os.environ.get("SECRET_KEY", secrets.token_hex(32)) +SITE_PASSWORD = os.environ.get("SITE_PASSWORD", "jv2026") + GEN_DIR = Path(__file__).parent / "generated" GEN_DIR.mkdir(exist_ok=True) @@ -28,6 +33,87 @@ with app.app_context(): except Exception as e: logging.error(f"DB init failed: {e}") + +# ── Auth ───────────────────────────────────────────────────── + +PUBLIC_PATHS = {"/login", "/api/health"} + +@app.before_request +def require_auth(): + """Gate every request behind a simple password session.""" + path = request.path + # Allow login page, health check, and static login assets + if path in PUBLIC_PATHS or path.startswith("/static/"): + return + if not session.get("authed"): + if request.is_json: + return jsonify({"error": "Authentication required"}), 401 + return redirect(url_for("login", next=request.path)) + + +LOGIN_HTML = """ + + +Login — JustVitamins × QuikCue + + +
+

JustVitamins × QuikCue

+

Confidential proposal — enter password to continue

+ {{ERR}} +
+
+ +
+ +
+ +
+""" + + +@app.route("/login", methods=["GET", "POST"]) +def login(): + err = "" + if request.method == "POST": + pw = request.form.get("password", "") + if pw == SITE_PASSWORD: + session["authed"] = True + session.permanent = True + app.permanent_session_lifetime = __import__("datetime").timedelta(days=30) + dest = request.args.get("next", "/") + return redirect(dest) + err = '
Wrong password
' + html = LOGIN_HTML.replace("{{ERR}}", err) + return make_response(html) + + +@app.route("/logout") +def logout(): + session.clear() + return redirect("/login") + # ── Page routes ────────────────────────────────────────────── @app.route("/") diff --git a/docker-compose.yml b/docker-compose.yml index 2590645..941714a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,6 +7,8 @@ services: environment: - GEMINI_API_KEY=AIzaSyCHnesXLjPw-UgeZaQotut66bgjFdvy12E - DATABASE_URL=postgresql://jv:jvpass@tasks.db:5432/justvitamin + - SECRET_KEY=c4f8a2e91b0d7f3e5a6c9d2b8e1f4a7d + - SITE_PASSWORD=jv2026 volumes: - jv-generated:/app/generated networks: