0vergeared commited on
Commit
239b62a
·
verified ·
1 Parent(s): 82fade8

Update server.py

Browse files
Files changed (1) hide show
  1. server.py +128 -114
server.py CHANGED
@@ -1,32 +1,81 @@
1
  import os
2
  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_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
 
17
- # Environment variables
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()
29
- c.execute('''CREATE TABLE IF NOT EXISTS otps (
30
  id INTEGER PRIMARY KEY AUTOINCREMENT,
31
  otp TEXT,
32
  generated_at TEXT,
@@ -34,145 +83,110 @@ def init_db():
34
  used_at TEXT,
35
  ip_address TEXT,
36
  status TEXT
37
- )''')
38
  conn.commit()
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
 
56
 
57
  otp = generate_otp()
58
  now = datetime.utcnow()
59
  expires_at = now + timedelta(minutes=10)
60
-
61
  with sqlite3.connect(DATABASE) as conn:
62
  c = conn.cursor()
63
  c.execute("INSERT INTO otps (otp, generated_at, expires_at, ip_address, status) VALUES (?, ?, ?, ?, ?)",
64
  (otp, now.isoformat(), expires_at.isoformat(), request.remote_addr, "generated"))
65
  conn.commit()
 
 
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()
80
  c.execute("SELECT id, expires_at, used_at, status FROM otps WHERE otp = ?", (otp,))
81
  row = c.fetchone()
82
-
83
  if not row:
84
- return jsonify({"valid": False, "reason": "Invalid OTP"}), 400
85
-
86
  otp_id, expires_at, used_at, status = row
87
- now = datetime.utcnow()
88
 
89
- if used_at:
90
- return jsonify({"valid": False, "reason": "OTP already used"}), 400
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>
108
- <form method="post">
109
- <input type="text" name="username" placeholder="Username" required><br>
110
- <input type="password" name="password" placeholder="Password" required><br>
111
- <button type="submit">Login</button>
112
- </form>
113
- </body></html>
114
- """
115
-
116
- @app.route("/login", methods=["GET", "POST"])
117
- def login():
118
- if request.method == "POST":
119
- if request.form["username"] == ADMIN_USER and request.form["password"] == ADMIN_PASS:
120
- session["admin"] = True
121
- return redirect("/generate")
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>
128
- <form method="post">
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>
136
- """
137
-
138
- @app.route("/generate", methods=["GET", "POST"])
139
- def generate_page():
140
- if not session.get("admin"):
141
- return redirect("/login")
142
-
143
- otp = None
144
- expires = None
145
- if request.method == "POST":
146
- otp = generate_otp()
147
- now = datetime.utcnow()
148
- expires_at = now + timedelta(minutes=10)
149
-
150
- with sqlite3.connect(DATABASE) as conn:
151
- c = conn.cursor()
152
- c.execute("INSERT INTO otps (otp, generated_at, expires_at, ip_address, status) VALUES (?, ?, ?, ?, ?)",
153
- (otp, now.isoformat(), expires_at.isoformat(), request.remote_addr, "generated"))
154
- conn.commit()
155
-
156
- expires = expires_at.isoformat()
157
-
158
- return render_template_string(GEN_TEMPLATE, otp=otp, expires=expires)
159
-
160
- @app.route("/logout")
161
- def logout():
162
- session.clear()
163
- return redirect("/login")
164
-
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)
 
1
  import os
2
  import random
3
  import sqlite3
4
+ import csv
5
+ import json
6
  from datetime import datetime, timedelta
7
+ from flask import Flask, request, jsonify, redirect, session, render_template_string, send_file
8
+
9
  from flask_limiter import Limiter
10
  from flask_limiter.util import get_remote_address
11
+ import pathlib
12
+
13
+ # Paths and setup
14
+ DATABASE = "/tmp/database.db"
15
+ EXPORT_CSV = "/data/otp_logs.csv"
16
+ EXPORT_JSON = "/data/otp_logs.json"
17
+ pathlib.Path("/data").mkdir(parents=True, exist_ok=True)
18
 
19
+ # App setup
20
  app = Flask(__name__)
21
+ app.secret_key = os.getenv("FLASK_SECRET_KEY", "IDONTKNOWWHATISTHIS")
22
 
23
+ # Rate limiter setup
24
  limiter = Limiter(key_func=get_remote_address)
25
  limiter.init_app(app)
26
 
27
+ # Auth
 
28
  ADMIN_USER = os.getenv("ADMIN_USER", "admin")
29
+ ADMIN_PASS = os.getenv("ADMIN_PASS", "Welcome123")
30
+ OTP_API_KEY = os.getenv("OTP_API_KEY", "IDONTKNOWWHATIMDOING")
31
 
32
+ # Templates
33
+ LOGIN_TEMPLATE = """
34
+ <!DOCTYPE html><html><head><title>Login</title></head><body>
35
+ <h2>Admin Login</h2>
36
+ <form method="post">
37
+ Username: <input name="username"><br>
38
+ Password: <input name="password" type="password"><br>
39
+ <input type="submit" value="Login">
40
+ </form>
41
+ </body></html>
42
+ """
43
+
44
+ GEN_TEMPLATE = """
45
+ <!DOCTYPE html><html><head><title>OTP Admin</title></head><body>
46
+ <h2>Generate OTP</h2>
47
+ <form method="post">
48
+ <button type="submit">Generate OTP</button>
49
+ </form>
50
+ {% if otp %}
51
+ <p><b>OTP:</b> {{ otp }}<br><b>Expires:</b> {{ expires }}</p>
52
+ {% endif %}
53
+
54
+ <hr>
55
+ <h3>Recent OTPs</h3>
56
+ <table border="1" cellpadding="5">
57
+ <tr><th>OTP</th><th>Generated At</th><th>Expires</th><th>Used At</th><th>Status</th><th>IP</th></tr>
58
+ {% for row in otps %}
59
+ <tr>
60
+ <td>{{ row[0] }}</td>
61
+ <td>{{ row[1] }}</td>
62
+ <td>{{ row[2] }}</td>
63
+ <td>{{ row[3] or "-" }}</td>
64
+ <td>{{ row[5] }}</td>
65
+ <td>{{ row[4] }}</td>
66
+ </tr>
67
+ {% endfor %}
68
+ </table>
69
+ <p><a href="/download-csv" target="_blank">📥 Download CSV</a> | <a href="/download-json" target="_blank">📥 Download JSON</a></p>
70
+ <a href="/logout">Logout</a>
71
+ </body></html>
72
+ """
73
 
74
+ # DB init
75
  def init_db():
76
  with sqlite3.connect(DATABASE) as conn:
77
  c = conn.cursor()
78
+ c.execute("""CREATE TABLE IF NOT EXISTS otps (
79
  id INTEGER PRIMARY KEY AUTOINCREMENT,
80
  otp TEXT,
81
  generated_at TEXT,
 
83
  used_at TEXT,
84
  ip_address TEXT,
85
  status TEXT
86
+ )""")
87
  conn.commit()
88
 
89
  init_db()
90
 
91
+ # Routes
92
+ @app.route("/login", methods=["GET", "POST"])
93
+ def login():
94
+ if request.method == "POST":
95
+ if request.form["username"] == ADMIN_USER and request.form["password"] == ADMIN_PASS:
96
+ session["admin"] = True
97
+ return redirect("/generate")
98
+ else:
99
+ return "Invalid credentials", 403
100
+ return LOGIN_TEMPLATE
101
 
102
+ @app.route("/logout")
103
+ def logout():
104
+ session.clear()
105
+ return redirect("/login")
106
+
107
+ @app.route("/generate", methods=["GET", "POST"])
108
+ def generate_page():
109
+ if not session.get("admin"):
110
+ return redirect("/login")
111
+
112
+ otp = None
113
+ expires = None
114
+
115
+ if request.method == "POST":
116
+ otp = generate_otp()
117
+ now = datetime.utcnow()
118
+ expires_at = now + timedelta(minutes=10)
119
+ with sqlite3.connect(DATABASE) as conn:
120
+ c = conn.cursor()
121
+ c.execute("INSERT INTO otps (otp, generated_at, expires_at, ip_address, status) VALUES (?, ?, ?, ?, ?)",
122
+ (otp, now.isoformat(), expires_at.isoformat(), request.remote_addr, "generated"))
123
+ conn.commit()
124
+ expires = expires_at.isoformat()
125
+ export_logs()
126
+
127
+ with sqlite3.connect(DATABASE) as conn:
128
+ c = conn.cursor()
129
+ c.execute("SELECT otp, generated_at, expires_at, used_at, ip_address, status FROM otps ORDER BY id DESC LIMIT 10")
130
+ otps = c.fetchall()
131
+
132
+ return render_template_string(GEN_TEMPLATE, otp=otp, expires=expires, otps=otps)
133
 
 
134
  @app.route("/generate-otp", methods=["POST"])
135
+ @limiter.limit("10 per minute")
136
+ def api_generate_otp():
137
+ api_key = request.headers.get("X-API-Key")
138
+ if api_key != OTP_API_KEY:
139
+ return jsonify({"error": "Invalid API key"}), 403
140
 
141
  otp = generate_otp()
142
  now = datetime.utcnow()
143
  expires_at = now + timedelta(minutes=10)
 
144
  with sqlite3.connect(DATABASE) as conn:
145
  c = conn.cursor()
146
  c.execute("INSERT INTO otps (otp, generated_at, expires_at, ip_address, status) VALUES (?, ?, ?, ?, ?)",
147
  (otp, now.isoformat(), expires_at.isoformat(), request.remote_addr, "generated"))
148
  conn.commit()
149
+ export_logs()
150
+ return jsonify({"otp": otp, "expires": expires_at.isoformat()})
151
 
 
 
 
152
  @app.route("/verify-otp", methods=["POST"])
153
+ @limiter.limit("30 per minute")
154
  def verify_otp():
155
  data = request.get_json()
156
  otp = data.get("otp")
 
 
157
 
158
  with sqlite3.connect(DATABASE) as conn:
159
  c = conn.cursor()
160
  c.execute("SELECT id, expires_at, used_at, status FROM otps WHERE otp = ?", (otp,))
161
  row = c.fetchone()
 
162
  if not row:
163
+ return jsonify({"valid": False, "reason": "Invalid OTP"})
 
164
  otp_id, expires_at, used_at, status = row
 
165
 
166
+ if status == "used":
167
+ return jsonify({"valid": False, "reason": "OTP already used"})
 
 
168
 
169
+ if datetime.fromisoformat(expires_at) < datetime.utcnow():
170
+ return jsonify({"valid": False, "reason": "OTP expired"})
 
 
171
 
172
+ now = datetime.utcnow().isoformat()
173
+ c.execute("UPDATE otps SET used_at = ?, status = ? WHERE id = ?", (now, "used", otp_id))
174
+ conn.commit()
175
+ export_logs()
176
  return jsonify({"valid": True})
177
 
178
+ @app.route("/download-csv")
179
+ def download_csv():
180
+ export_logs()
181
+ return send_file(EXPORT_CSV, as_attachment=True)
182
 
183
+ @app.route("/download-json")
184
+ def download_json():
185
+ export_logs()
186
+ return send_file(EXPORT_JSON, as_attachment=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  @app.route("/debug")
189
  def debug():
190
  return jsonify({
191
  "db_exists": os.path.exists(DATABASE),
192
+ "csv_exists": os.path.exists(EX_