Spaces:
Sleeping
Sleeping
Update server.py
Browse files
server.py
CHANGED
|
@@ -3,12 +3,14 @@ import random
|
|
| 3 |
import sqlite3
|
| 4 |
from datetime import datetime, timedelta
|
| 5 |
from flask import Flask, request, jsonify, redirect, session, render_template_string
|
| 6 |
-
from flask import Flask
|
| 7 |
from flask_limiter import Limiter
|
| 8 |
from flask_limiter.util import get_remote_address
|
| 9 |
|
|
|
|
| 10 |
app = Flask(__name__)
|
|
|
|
| 11 |
|
|
|
|
| 12 |
limiter = Limiter(key_func=get_remote_address)
|
| 13 |
limiter.init_app(app)
|
| 14 |
|
|
@@ -16,17 +18,11 @@ limiter.init_app(app)
|
|
| 16 |
OTP_API_KEY = os.getenv("OTP_API_KEY", "default-api-key")
|
| 17 |
ADMIN_USER = os.getenv("ADMIN_USER", "admin")
|
| 18 |
ADMIN_PASS = os.getenv("ADMIN_PASS", "password")
|
| 19 |
-
SECRET_KEY = os.getenv("FLASK_SECRET_KEY", "supersecretkey")
|
| 20 |
|
| 21 |
-
|
| 22 |
-
app.secret_key = SECRET_KEY
|
| 23 |
-
|
| 24 |
-
limiter = Limiter(app, key_func=get_remote_address)
|
| 25 |
-
|
| 26 |
-
# Writable DB path
|
| 27 |
DATABASE = "/tmp/database.db"
|
| 28 |
|
| 29 |
-
#
|
| 30 |
def init_db():
|
| 31 |
with sqlite3.connect(DATABASE) as conn:
|
| 32 |
c = conn.cursor()
|
|
@@ -43,11 +39,17 @@ def init_db():
|
|
| 43 |
|
| 44 |
init_db()
|
| 45 |
|
|
|
|
| 46 |
def generate_otp():
|
| 47 |
return str(random.randint(100000, 999999))
|
| 48 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
@app.route("/generate-otp", methods=["POST"])
|
| 50 |
-
@limiter.limit("5
|
| 51 |
def api_generate():
|
| 52 |
if request.headers.get("X-API-Key") != OTP_API_KEY:
|
| 53 |
return jsonify({"error": "Unauthorized"}), 401
|
|
@@ -64,11 +66,14 @@ def api_generate():
|
|
| 64 |
|
| 65 |
return jsonify({"otp": otp, "expires_at": expires_at.isoformat()})
|
| 66 |
|
|
|
|
| 67 |
@app.route("/verify-otp", methods=["POST"])
|
| 68 |
-
@limiter.limit("10
|
| 69 |
def verify_otp():
|
| 70 |
data = request.get_json()
|
| 71 |
otp = data.get("otp")
|
|
|
|
|
|
|
| 72 |
|
| 73 |
with sqlite3.connect(DATABASE) as conn:
|
| 74 |
c = conn.cursor()
|
|
@@ -86,13 +91,17 @@ def verify_otp():
|
|
| 86 |
if datetime.fromisoformat(expires_at) < now:
|
| 87 |
return jsonify({"valid": False, "reason": "OTP expired"}), 400
|
| 88 |
|
|
|
|
| 89 |
c.execute("UPDATE otps SET used_at = ?, status = ? WHERE id = ?",
|
| 90 |
(now.isoformat(), "used", otp_id))
|
| 91 |
conn.commit()
|
| 92 |
|
| 93 |
return jsonify({"valid": True})
|
| 94 |
|
| 95 |
-
#
|
|
|
|
|
|
|
|
|
|
| 96 |
LOGIN_TEMPLATE = """
|
| 97 |
<!DOCTYPE html><html><body>
|
| 98 |
<h2>Admin Login</h2>
|
|
@@ -113,7 +122,6 @@ def login():
|
|
| 113 |
return "Invalid login", 403
|
| 114 |
return render_template_string(LOGIN_TEMPLATE)
|
| 115 |
|
| 116 |
-
# Admin OTP generation UI
|
| 117 |
GEN_TEMPLATE = """
|
| 118 |
<!DOCTYPE html><html><body>
|
| 119 |
<h2>Generate OTP</h2>
|
|
@@ -121,7 +129,7 @@ GEN_TEMPLATE = """
|
|
| 121 |
<button type="submit">Generate OTP</button>
|
| 122 |
</form>
|
| 123 |
{% if otp %}
|
| 124 |
-
<p>OTP:
|
| 125 |
{% endif %}
|
| 126 |
<a href="/logout">Logout</a>
|
| 127 |
</body></html>
|
|
@@ -157,7 +165,14 @@ def logout():
|
|
| 157 |
# Optional debug route
|
| 158 |
@app.route("/debug")
|
| 159 |
def debug():
|
| 160 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
|
| 162 |
if __name__ == "__main__":
|
| 163 |
app.run(host="0.0.0.0", port=7860)
|
|
|
|
| 3 |
import sqlite3
|
| 4 |
from datetime import datetime, timedelta
|
| 5 |
from flask import Flask, request, jsonify, redirect, session, render_template_string
|
|
|
|
| 6 |
from flask_limiter import Limiter
|
| 7 |
from flask_limiter.util import get_remote_address
|
| 8 |
|
| 9 |
+
# Initialize Flask
|
| 10 |
app = Flask(__name__)
|
| 11 |
+
app.secret_key = os.getenv("FLASK_SECRET_KEY", "supersecretkey")
|
| 12 |
|
| 13 |
+
# Setup limiter (correct method)
|
| 14 |
limiter = Limiter(key_func=get_remote_address)
|
| 15 |
limiter.init_app(app)
|
| 16 |
|
|
|
|
| 18 |
OTP_API_KEY = os.getenv("OTP_API_KEY", "default-api-key")
|
| 19 |
ADMIN_USER = os.getenv("ADMIN_USER", "admin")
|
| 20 |
ADMIN_PASS = os.getenv("ADMIN_PASS", "password")
|
|
|
|
| 21 |
|
| 22 |
+
# Database location (must be writable in HF Spaces)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
DATABASE = "/tmp/database.db"
|
| 24 |
|
| 25 |
+
# Database setup
|
| 26 |
def init_db():
|
| 27 |
with sqlite3.connect(DATABASE) as conn:
|
| 28 |
c = conn.cursor()
|
|
|
|
| 39 |
|
| 40 |
init_db()
|
| 41 |
|
| 42 |
+
# Helper: generate 6-digit OTP
|
| 43 |
def generate_otp():
|
| 44 |
return str(random.randint(100000, 999999))
|
| 45 |
|
| 46 |
+
# -------------------------------
|
| 47 |
+
# API ROUTES
|
| 48 |
+
# -------------------------------
|
| 49 |
+
|
| 50 |
+
# Generate OTP (API)
|
| 51 |
@app.route("/generate-otp", methods=["POST"])
|
| 52 |
+
@limiter.limit("5 per minute")
|
| 53 |
def api_generate():
|
| 54 |
if request.headers.get("X-API-Key") != OTP_API_KEY:
|
| 55 |
return jsonify({"error": "Unauthorized"}), 401
|
|
|
|
| 66 |
|
| 67 |
return jsonify({"otp": otp, "expires_at": expires_at.isoformat()})
|
| 68 |
|
| 69 |
+
# Verify OTP (API)
|
| 70 |
@app.route("/verify-otp", methods=["POST"])
|
| 71 |
+
@limiter.limit("10 per minute")
|
| 72 |
def verify_otp():
|
| 73 |
data = request.get_json()
|
| 74 |
otp = data.get("otp")
|
| 75 |
+
if not otp:
|
| 76 |
+
return jsonify({"valid": False, "reason": "Missing OTP"}), 400
|
| 77 |
|
| 78 |
with sqlite3.connect(DATABASE) as conn:
|
| 79 |
c = conn.cursor()
|
|
|
|
| 91 |
if datetime.fromisoformat(expires_at) < now:
|
| 92 |
return jsonify({"valid": False, "reason": "OTP expired"}), 400
|
| 93 |
|
| 94 |
+
# Mark as used
|
| 95 |
c.execute("UPDATE otps SET used_at = ?, status = ? WHERE id = ?",
|
| 96 |
(now.isoformat(), "used", otp_id))
|
| 97 |
conn.commit()
|
| 98 |
|
| 99 |
return jsonify({"valid": True})
|
| 100 |
|
| 101 |
+
# -------------------------------
|
| 102 |
+
# ADMIN WEB UI
|
| 103 |
+
# -------------------------------
|
| 104 |
+
|
| 105 |
LOGIN_TEMPLATE = """
|
| 106 |
<!DOCTYPE html><html><body>
|
| 107 |
<h2>Admin Login</h2>
|
|
|
|
| 122 |
return "Invalid login", 403
|
| 123 |
return render_template_string(LOGIN_TEMPLATE)
|
| 124 |
|
|
|
|
| 125 |
GEN_TEMPLATE = """
|
| 126 |
<!DOCTYPE html><html><body>
|
| 127 |
<h2>Generate OTP</h2>
|
|
|
|
| 129 |
<button type="submit">Generate OTP</button>
|
| 130 |
</form>
|
| 131 |
{% if otp %}
|
| 132 |
+
<p><b>OTP:</b> {{ otp }}<br><b>Expires:</b> {{ expires }}</p>
|
| 133 |
{% endif %}
|
| 134 |
<a href="/logout">Logout</a>
|
| 135 |
</body></html>
|
|
|
|
| 165 |
# Optional debug route
|
| 166 |
@app.route("/debug")
|
| 167 |
def debug():
|
| 168 |
+
return jsonify({
|
| 169 |
+
"db_exists": os.path.exists(DATABASE),
|
| 170 |
+
"size_bytes": os.path.getsize(DATABASE) if os.path.exists(DATABASE) else 0
|
| 171 |
+
})
|
| 172 |
+
|
| 173 |
+
# -------------------------------
|
| 174 |
+
# ENTRY POINT
|
| 175 |
+
# -------------------------------
|
| 176 |
|
| 177 |
if __name__ == "__main__":
|
| 178 |
app.run(host="0.0.0.0", port=7860)
|